Захватите сегмент массива в Java, не создавая новый массив в куче - PullRequest
180 голосов
/ 09 июля 2009

Я ищу метод в 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 - это, например, длина.

Ответы [ 15 ]

183 голосов
/ 09 июля 2009

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

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

( Честно говоря, я чувствую, что мой ответ достоин удаления. Ответ @ 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);
165 голосов
/ 11 января 2011

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

39 голосов
/ 09 июля 2009

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

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

21 голосов
/ 08 ноября 2011

Один из способов - обернуть массив в 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 может быть немного сложным для понимания, но, скорее всего, он эффективно реализован и заслуживает изучения.

20 голосов
/ 09 июля 2009

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

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

14 голосов
/ 09 июля 2009

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

10 голосов
/ 09 июля 2009

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

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));
    }
}

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

9 голосов
/ 09 июля 2009
List.subList(int startIndex, int endIndex)
7 голосов
/ 09 июля 2009

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

void method1(byte[] array) {
    method2(array,4,5);
}
void method2(byte[] smallarray,int start,int end) {
    for ( int i = start; i <= end; i++ ) {
        ....
    }
}
6 голосов
/ 09 июля 2009

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

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

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

...