Возьмите сегмент массива в Java без создания нового массива в куче

Я ищу метод на Java, который вернет сегмент массива. Примером может быть получение байтового массива, содержащего 4-й и 5-й байты массива байтов. Я не хочу создавать новый массив байтов в памяти кучи только для этого. Прямо сейчас у меня есть следующий код:

doSomethingWithTwoBytes(byte[] twoByteArray);

void someMethod(byte[] bigArray)
{
      byte[] x = {bigArray[4], bigArray[5]};
      doSomethingWithTwoBytes(x);
}

Я хотел бы знать, есть ли способ сделать только doSomething(bigArray.getSubArray(4, 2)), где 4 - смещение, а 2 - длина, например.

Ответ 1

Отказ от ответственности: этот ответ не соответствует ограничениям вопроса:

Я не хочу создавать новый массив байтов в памяти кучи только для этого.

(Честно говоря, я считаю, что мой ответ достоин удаления. Ответ @unique72 верен. Imma разрешил этому редактированию сидеть немного, а затем я удалю этот ответ.)


Я не знаю, как это сделать непосредственно с массивами без дополнительного распределения кучи, но другие ответы, использующие оболочку поддиректа, имеют дополнительное распределение только для обертки - но не массив - что было бы полезно в случай большого массива.

Тем не менее, если вы ищете краткость, в Java 6 (конец 2006 года) был введен метод утилиты Arrays.copyOfRange():

byte [] a = new byte [] {0, 1, 2, 3, 4, 5, 6, 7};

// get a[4], a[5]

byte [] subArray = Arrays.copyOfRange(a, 4, 6);

Ответ 2

Arrays.asList(myArray) делегирует новый ArrayList(myArray), который не копирует массив, а просто сохраняет ссылку. Используя List.subList(start, end) после этого делает SubList, который просто ссылается на исходный список (который все еще просто ссылается на массив). Никакое копирование массива или его содержимого, просто создание оболочки и все связанные списки не поддерживаются исходным массивом. (Я думал, что это будет тяжелее.)

Ответ 3

Если вы ищете подход с псевдонимом стиля указателя, так что вам даже не нужно выделять пространство и копировать данные, тогда я считаю, что вам не повезло.

System.arraycopy() будет копироваться из вашего источника в пункт назначения, и для этой утилиты испрашивается эффективность. Вам нужно выделить целевой массив.

Ответ 4

Используйте java.nio.Buffer's. Это легкая обертка для буферов различных примитивных типов и помогает управлять нарезкой, позицией, преобразованием, упорядочением байтов и т.д.

Если ваши байты исходят из потока, буферы NIO могут использовать "прямой режим", который создает буфер, поддерживаемый собственными ресурсами. Это может улучшить производительность во многих случаях.

Ответ 5

Один из способов заключается в том, чтобы обернуть массив в java.nio.ByteBuffer, использовать абсолютные функции put/get и срезать буфер для работы в подмассиве.

Например:

doSomething(ByteBuffer twoBytes) {
    byte b1 = twoBytes.get(0);
    byte b2 = twoBytes.get(1);
    ...
}

void someMethod(byte[] bigArray)
{
      int offset = 4;
      int length = 2;
      doSomething(ByteBuffer.wrap(bigArray, offset, length).slice());
}

Обратите внимание, что вы должны вызывать оба метода wrap() и slice(), так как wrap() сам по себе влияет только на относительные функции put/get, а не на абсолютные.

ByteBuffer может быть немного сложнее понять, но, скорее всего, эффективно реализован и хорошо стоит изучить.

Ответ 6

Вы можете использовать ArrayUtils.subarray в apache commons. Не идеальный, но немного более интуитивный, чем System.arraycopy. Недостатком является то, что он вводит другую зависимость в ваш код.

Ответ 7

Я вижу ответ subList уже здесь, но вот код, который демонстрирует, что это истинный подсписок, а не копия:

public class SubListTest extends TestCase {
    public void testSubarray() throws Exception {
        Integer[] array = {1, 2, 3, 4, 5};
        List<Integer> list = Arrays.asList(array);
        List<Integer> subList = list.subList(2, 4);
        assertEquals(2, subList.size());
        assertEquals((Integer) 3, subList.get(0));
        list.set(2, 7);
        assertEquals((Integer) 7, subList.get(0));
    }
}

Я не верю, что есть хороший способ сделать это непосредственно с массивами.

Ответ 8

List.subList(int startIndex, int endIndex)

Ответ 9

Один из вариантов - передать весь массив и начальный и конечный индексы, а также перебрать между ними вместо итерации по всему массиву.

void method1(byte[] array) {
    method2(array,4,5);
}
void method2(byte[] smallarray,int start,int end) {
    for ( int i = start; i <= end; i++ ) {
        ....
    }
}

Ответ 10

List позволяет вам прозрачно использовать и работать с subList. Примитивные массивы потребуют от вас отслеживания какого-либо смещения. ByteBuffer имеют аналогичные варианты, как я слышал.

Edit: Если вы отвечаете за полезный метод, вы можете просто определить его с помощью границ (как это сделано во многих связанных с массивом методах в самой java:

doUseful(byte[] arr, int start, int len) {
    // implementation here
}
doUseful(byte[] arr) {
    doUseful(arr, 0, arr.length);
}

Не ясно, однако, если вы работаете над сами элементами массива, например. вы что-то вычисляете и записываете результат?

Ответ 11

Ссылки Java всегда указывают на объект. Объект имеет заголовок, который, среди прочего, идентифицирует конкретный тип (так что сбрасывание может завершиться с ClassCastException). Для массивов начало объекта также включает длину, данные затем следуют сразу же в памяти (технически реализация может свободно делать то, что ей нравится, но было бы глупо делать что-либо еще). Итак, вы можете: t иметь ссылку, которая указывает где-то в массив.

В C указателях указывайте где угодно и на что угодно, и вы можете указать на середину массива. Но вы не можете безопасно бросить или узнать, как долго массив. В D указатель содержит смещение в блоке памяти и длине (или, что то же самое, указатель на конец, я не могу вспомнить, что на самом деле делает реализация). Это позволяет D массивам срезов. В С++ у вас будет два итератора, указывающих на начало и конец, но С++ немного странно.

Итак, вернемся к Java, вы не можете. Как уже упоминалось, NIO ByteBuffer позволяет вам обернуть массив, а затем разрезать его, но дает неудобный интерфейс. Вы можете, конечно, скопировать, что, вероятно, намного быстрее, чем вы думаете. Вы можете представить свою собственную абстракцию String, которая позволяет вам срезать массив (текущая реализация Sun String имеет ссылку char[] плюс начальное смещение и длину, более высокая реализация производительности имеет только char[])., byte[] - это низкий уровень, но любая абстракция на основе классов, которую вы наделаете, сделает ужасный беспорядок синтаксиса, пока JDK7 (возможно).

Ответ 12

@unique72 ответьте как простая функция или строка, вам может потребоваться заменить Object, с соответствующим типом класса, который вы хотите "нарезать". Два варианта даны для удовлетворения различных потребностей.

/// Extract out array from starting position onwards
public static Object[] sliceArray( Object[] inArr, int startPos ) {
    return Arrays.asList(inArr).subList(startPos, inArr.length).toArray();
}

/// Extract out array from starting position to ending position
public static Object[] sliceArray( Object[] inArr, int startPos, int endPos ) {
    return Arrays.asList(inArr).subList(startPos, endPos).toArray();
}

Ответ 13

Как насчет тонкой оболочки List?

List<Byte> getSubArrayList(byte[] array, int offset, int size) {
   return new AbstractList<Byte>() {
      Byte get(int index) {
         if (index < 0 || index >= size) 
           throw new IndexOutOfBoundsException();
         return array[offset+index];
      }
      int size() {
         return size;
      }
   };
}

(непроверенные)

Ответ 14

Мне нужно было выполнить итерацию в конце массива и не захотеть скопировать массив. Мой подход состоял в том, чтобы сделать Iterable над массивом.

public static Iterable<String> sliceArray(final String[] array, 
                                          final int start) {
  return new Iterable<String>() {
    String[] values = array;
    int posn = start;

    @Override
    public Iterator<String> iterator() {
      return new Iterator<String>() {
        @Override
        public boolean hasNext() {
          return posn < values.length;
        }

        @Override
        public String next() {
          return values[posn++];
        }

        @Override
        public void remove() {
          throw new UnsupportedOperationException("No remove");
        }
      };
    }
  };
}

Ответ 15

Это немного более легкий, чем Arrays.copyOfRange - нет диапазона или отрицательного

public static final byte[] copy(byte[] data, int pos, int length )
{
    byte[] transplant = new byte[length];

    System.arraycopy(data, pos, transplant, 0, length);

    return transplant;
}

Ответ 16

Пожалуйста, используйте

System.arrayCopy();

Он позволяет указать начальную позицию в исходном массиве и количество элементов, которые вы хотите скопировать.