Захватите сегмент массива в Java, не создавая новый массив в куче


181

Я ищу метод в 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
Как насчет того, чтобы заняться магией JNI в C ++? Может быть катастрофа от GC POV?
Алик Эльзин-килака

Должен ли это быть массив примитивных байтов?
Депутат Корстанье

Ответы:


185

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

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

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


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

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

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

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

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

10
это все еще динамически распределяет новый сегмент памяти и копирует диапазон в это.
Дэн

4
Спасибо, Дэн, - я забыл, что OP не хочет создавать новый массив, и я не смотрел на реализацию copyOfRange. Если бы это был закрытый источник, возможно, это могло бы пройти. :)
David J. Liszewski

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

2
фактически, copyOfRange все еще выделяет новый сегмент памяти
Кевинго Цай

167

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


9
Чтобы уточнить, он делегирует закрытый класс в Arraysвызывающе смущающем вызове ArrayList, но который на самом деле представляет Listсобой массив, в отличие от java.util.ArrayListкоторого можно было бы сделать копию. Нет новых распределений (содержимого списка) и сторонних зависимостей. Это, я считаю, самый правильный ответ.
dimo414

28
На самом деле, это не будет работать для массивов примитивного типа, как этого хотел OP ( byte[]в его случае). Все, что вы получите, будет List<byte[]>. И изменения byte[] bigArrayв Byte[] bigArrayможет наложить значительные накладные расходы памяти.
Дмитрий Автономов

2
Единственный способ по-настоящему достичь желаемого - через sun.misc.Unsafeкласс.
Дмитрий Автономов

39

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

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


3
да, я надеялся на какой-то метод указателя, так как я не хочу динамически распределять память. но похоже, что это то, что я собираюсь сделать.
JBU

1
Как предполагает @ unique72, кажется, что есть способы делать то, что вы хотите, используя тонкости в реализации различных типов списков / массивов Java. Кажется, что это возможно, но не в явной форме, и это заставляет меня не решаться на это слишком полагаться ...
Эндрю

Почему следует array*copy*()повторно использовать ту же память? Разве это не полная противоположность ожидания абонента?
Патрик Фавр,

23

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


1
Также стоит отметить, что объекты ByteBuffer могут быть довольно легко декодированы:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl

@Soulman спасибо за объяснение, но один вопрос, это более эффективно, чем использовать Arrays.copyOfRange?
ucMedia

1
@ucMedia для двухбайтового массива, Arrays.copyOfRangeвероятно, более эффективен. Как правило, вам придется измерять для вашего конкретного случая использования.
Соулман

20

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

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


14

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


23
Это то же самое, что Arrays.copyOfRange () в Java 1.6
newacct

10

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

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

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



7

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

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

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

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


6

Одним из вариантов будет передача всего массива и индексов начала и конца и итерация между ними вместо перебора всего переданного массива.

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

6

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

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

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


Спасибо за объяснение, почему это было бы невозможно. Кстати, String теперь копируется substringв HotSpot (забудьте, какая сборка изменила это). Почему вы говорите, что JDK7 допускает лучший синтаксис, чем ByteBuffer?
Александр Дубинский

@AleksandrDubinsky На момент написания статьи казалось, что Java SE 7 будет разрешать []нотацию массива для пользовательских типов, таких как Listи ByteBuffer. Все еще жду ...
Том Хотин - tackline

2

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

1

Как насчет тонкой 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;
      }
   };
}

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


Это повлечет за собой бокс-распаковку байтов. Может быть медленным
Депутат Корстанье

@mpkorstanje: в библиотеке Orable Java Byteобъекты для всех byteзначений кэшируются. Так что накладные расходы на бокс должны быть довольно медленными.
Лий

1

Мне нужно было перебрать конец массива и не хотел копировать массив. Мой подход состоял в том, чтобы сделать 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");
        }
      };
    }
  };
}

-1

Это немного легче, чем 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;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.