Візьміть сегмент масиву на 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
Дякую Ден - я нехтував тим, що ОП не хотів створювати новий масив, і не розглядав реалізацію copyOfRange. Якби це закрите джерело, можливо, це могло б пройти. :)
Девід Дж. Лішевський

7
Я думаю, що багато людей хочуть створити підмасив з масиву і не переживають, що він використовує ще трохи пам'яті. Вони стикаються з цим питанням і отримують потрібну відповідь - тому, будь ласка, не видаляйте, як це корисно - я думаю, що це нормально.
The Lonely Coder

2
насправді copyOfRange все ще виділяє новий сегмент пам’яті
Kevingo Tsai

167

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


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

28
Насправді, це не спрацює для масивів примітивного типу, як цього хотів ОП ( 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ймовірно, більш ефективний. Як правило, вам доведеться вимірювати ваш конкретний випадок використання.
Soulman

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

Однак я не вірю, що є хороший спосіб це зробити безпосередньо з масивами.



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

В Lists дозволяють використовувати і працювати з subListчим - то прозоро. Примітивні масиви вимагають від вас відстежувати якусь межу зміщення. ByteBuffers мають аналогічні варіанти, як я чув.

Редагувати: Якщо ви відповідаєте за корисний метод, ви можете просто визначити його межею (як це робиться у багатьох методах, пов'язаних з масивом у самій Java:

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

Однак незрозуміло, якщо ви працюєте над самими елементами масиву, наприклад, щось обчислюєте і записуєте результат?


6

Посилання Java завжди вказують на об'єкт. Об'єкт має заголовок, який серед іншого ідентифікує конкретний тип (тому касти можуть не мати успіху ClassCastException). Для масивів початок об'єкта також включає довжину, дані потім випливають відразу в пам'ять (технічно реалізація вільна робити те, що заманеться, але було б сміливо робити все інше). Отже, ви не можете мати t посилання, яке вказує десь на масив.

У вказівниках C вказуйте будь-де і на що завгодно, і ви можете вказувати на середину масиву. Але ви не можете безпечно відкинути або дізнатись, який довгий масив. У D покажчик містить зміщення в блоці пам'яті та довжині (або рівнозначно вказівник до кінця, я не можу пригадати, що реально робить реалізація). Це дозволяє D нарізати масиви. У C ++ у вас є два ітератори, що вказують на початок і кінець, але C ++ трохи дивним чином.

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


Дякую за пояснення, чому це було б неможливо. Btw, String тепер копіюється substringв HotSpot (забудьте, яка збірка змінила цю ситуацію). Чому ви кажете, що JDK7 дозволив би синтаксис краще, ніж ByteBuffer?
Олександр Дубінський

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

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.