Chwyć segment tablicy w Javie bez tworzenia nowej tablicy na stercie


181

Szukam metody w Javie, która zwróci segment tablicy. Przykładem może być tablica bajtów zawierająca 4. i 5. bajt tablicy bajtów. Nie chcę tworzyć nowej tablicy bajtów w pamięci sterty, aby to zrobić. W tej chwili mam następujący kod:

doSomethingWithTwoBytes(byte[] twoByteArray);

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

Chciałbym wiedzieć, czy istnieje sposób, aby po prostu zrobić, doSomething(bigArray.getSubArray(4, 2))gdzie 4 to przesunięcie, a 2 to na przykład długość.


1
Co powiesz na wykonanie magii JNI w C ++? Może to być katastrofa z GC POV?
AlikElzin-kilaka

Czy to musi być tablica pierwotnych bajtów?
MP Korstanje

Odpowiedzi:


185

Zastrzeżenie: Ta odpowiedź nie jest zgodna z ograniczeniami pytania:

Nie chcę tworzyć nowej tablicy bajtów w pamięci sterty, aby to zrobić.

( Szczerze mówiąc, uważam, że moja odpowiedź zasługuje na usunięcie. Odpowiedź @ Unique72 jest poprawna. Imma pozwól, aby ta edycja trochę usiadła, a następnie usunę tę odpowiedź. )


Nie wiem, jak to zrobić bezpośrednio z tablicami bez dodatkowego przydziału sterty, ale inne odpowiedzi korzystające z otoki podlisty mają dodatkowy przydział tylko dla otoki - ale nie dla tablicy - co byłoby przydatne w przypadku duża tablica.

To powiedziawszy, jeśli ktoś szuka zwięzłości, metoda użytkowa Arrays.copyOfRange()została wprowadzona w Javie 6 (pod koniec 2006 roku?):

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
to wciąż dynamicznie przydziela nowy segment pamięci i kopiuje do niego zakres.
Dan

4
Dzięki Dan - zaniedbałem to, że OP nie chciał stworzyć nowej tablicy i nie patrzyłem na implementację copyOfRange. Gdyby było to źródło zamknięte, może by przeszło. :)
David J. Liszewski,

7
Myślę, że wiele osób chce utworzyć podrzędną tablicę z tablicy i nie martwi się, że zużywa ona więcej pamięci. Napotykają to pytanie i otrzymują odpowiedź, której chcą - więc proszę nie usuwać, ponieważ jest to przydatne - myślę, że to w porządku.
The Lonely Coder

2
faktycznie, copyOfRange wciąż przydziela nowy segment pamięci
Kevingo Tsai

167

Arrays.asList(myArray)deleguje do new ArrayList(myArray), który nie kopiuje tablicy, a jedynie przechowuje referencję. Używanie List.subList(start, end)później powoduje, że po SubListprostu odwołuje się do oryginalnej listy (która wciąż tylko odwołuje się do tablicy). Brak kopiowania tablicy lub jej zawartości, po prostu tworzenie opakowania, a wszystkie zaangażowane listy są wspierane przez oryginalną tablicę. (Myślałem, że będzie cięższy.)


9
Aby to wyjaśnić, deleguje do prywatnej klasy w Arraysmyląco nazywanej ArrayList, ale która tak naprawdę jest Listwokół tablicy, w przeciwieństwie do java.util.ArrayListktórej tworzy kopię. Brak nowych przydziałów (zawartości listy) i brak zależności stron trzecich. To, moim zdaniem, najbardziej poprawna odpowiedź.
dimo414

28
W rzeczywistości nie zadziała to dla tablic typu pierwotnego, tak jak chciał OP ( byte[]w jego przypadku). Wszystko, co dostaniesz, to List<byte[]>. A zmiana byte[] bigArrayna Byte[] bigArraymoże spowodować znaczne obciążenie pamięci.
Dmitry Avtonomov

2
Jedynym sposobem, aby naprawdę osiągnąć to, co jest pożądane, jest sun.misc.Unsafeklasa.
Dmitry Avtonomov

39

Jeśli szukasz sposobu aliasingu w stylu wskaźnika, abyś nawet nie musiał przydzielać miejsca i kopiować danych, to uważam, że nie masz szczęścia.

System.arraycopy() skopiuje ze źródła do miejsca docelowego, a wydajność tego narzędzia jest deklarowana. Musisz przydzielić tablicę docelową.


3
tak, liczyłem na jakąś metodę wskaźnika, ponieważ nie chcę dynamicznie przydzielać pamięci. ale wygląda na to, że muszę to zrobić.
jbu

1
Jak sugeruje @ unique72, wydaje się, że istnieją sposoby robienia tego, co chcesz, wykorzystując subtelności w implementacji różnych typów list / tablic Java. Wydaje się to możliwe, ale nie w sposób jawny, co sprawia, że ​​waham się za bardzo polegać na nim ...
Andrew,

Dlaczego warto array*copy*()ponownie używać tej samej pamięci? Czy to nie jest dokładnie odwrotne, niż oczekiwałby dzwoniący?
Patrick Favre

23

Jednym ze sposobów jest zawinięcie tablicy java.nio.ByteBuffer, użycie absolutnych funkcji put / get i podzielenie bufora na pracę na podtablicy.

Na przykład:

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

Należy pamiętać, że trzeba zadzwonić zarówno wrap()i slice(), ponieważ wrap()sama ma wpływ jedynie względną put / uzyskać funkcje, nie tych bezwzględnych.

ByteBuffer może być nieco trudny do zrozumienia, ale najprawdopodobniej jest skutecznie wdrożony i warto się go nauczyć.


1
Warto również zauważyć, że obiekty ByteBuffer można dość łatwo odkodować:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl

@Soulman dziękuje za wyjaśnienie, ale jedno pytanie jest bardziej wydajne niż używanie Arrays.copyOfRange?
ucMedia

1
@ucMedia dla tablicy dwubajtowej Arrays.copyOfRangejest prawdopodobnie bardziej wydajna. Ogólnie rzecz biorąc, musisz zmierzyć dla konkretnego przypadku użycia.
Soulman

20

Użyj java.nio.Buffer. Jest to lekkie opakowanie do buforów różnych pierwotnych typów i pomaga zarządzać krojeniem, pozycją, konwersją, kolejnością bajtów itp.

Jeśli bajty pochodzą ze strumienia, bufory NIO mogą używać „trybu bezpośredniego”, który tworzy bufor wspierany przez zasoby rodzime. Może to poprawić wydajność w wielu przypadkach.


14

Możesz użyć ArrayUtils.subarray w apache commons. Nie jest idealny, ale jest nieco bardziej intuicyjny niż System.arraycopy. Minusem jest to, że wprowadza on kolejną zależność do kodu.


23
Jest taki sam jak Arrays.copyOfRange () w Javie 1.6
newacct

10

Widzę, że odpowiedź na podlistę jest już tutaj, ale oto kod, który pokazuje, że jest to prawdziwa lista podrzędna, a nie kopia:

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

Nie sądzę jednak, że istnieje dobry sposób, aby zrobić to bezpośrednio z tablicami.


9
List.subList(int startIndex, int endIndex)

9
Najpierw musisz zawinąć tablicę jako listę: Arrays.asList (...). Sublist (...);
camickr

6

Jedną z opcji byłoby przekazanie całej tablicy oraz indeksów początkowej i końcowej oraz iteracja między nimi zamiast iteracji po całej przekazanej tablicy.

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

6

Do Lists pozwalają na wykorzystanie i pracę z subListczegoś przejrzysty. Prymitywne tablice wymagałyby śledzenia pewnego limitu przesunięcia. ByteBuffers mają podobne opcje, jak słyszałem.

Edycja: jeśli jesteś odpowiedzialny za przydatną metodę, możesz po prostu zdefiniować ją za pomocą granic (tak jak w wielu metodach związanych z tablicą w samej Javie:

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

Nie jest jednak jasne, czy pracujesz nad elementami tablicy, np. Obliczasz coś i zapisujesz wynik?


6

Odwołania Java zawsze wskazują na obiekt. Obiekt ma nagłówek, który między innymi identyfikuje konkretny typ (więc rzutowanie może się nie powieść ClassCastException). W przypadku tablic początek obiektu obejmuje również długość, następnie dane są zapisywane bezpośrednio w pamięci (technicznie implementacja może robić, co chce, ale głupio byłoby robić cokolwiek innego). Więc możesz; t mieć odwołanie, które wskazuje gdzieś w tablicy.

Wskaźniki w C wskazują gdziekolwiek i na cokolwiek, a możesz wskazać środek tablicy. Ale nie możesz bezpiecznie rzucić ani dowiedzieć się, jak długa jest tablica. W D wskaźnik zawiera przesunięcie w bloku pamięci i długości (lub równoważnie wskaźnik do końca, nie pamiętam, co faktycznie robi implementacja). Pozwala to D na dzielenie tablic. W C ++ miałbyś dwa iteratory wskazujące początek i koniec, ale C ++ jest trochę dziwne.

Wracając do Javy, nie, nie możesz. Jak wspomniano, NIO ByteBufferpozwala owinąć tablicę, a następnie pokroić ją, ale daje niezręczny interfejs. Możesz oczywiście kopiować, co jest prawdopodobnie znacznie szybsze niż mogłoby się wydawać. Możesz wprowadzić swoją własną Stringabstrakcję, która pozwala pokroić tablicę (obecna implementacja Sun Stringma char[]odniesienie oraz przesunięcie początkowe i długość, implementacja o wyższej wydajności ma po prostu char[]). byte[]jest niski poziom, ale każda abstrakcja oparta na klasach spowoduje okropny bałagan w składni, aż do JDK7 (być może).


Dziękujemy za wyjaśnienie, dlaczego byłoby to niemożliwe. Btw, String teraz kopiuje się substringw HotSpot (zapomnij, która kompilacja to zmieniła). Dlaczego mówisz, że JDK7 pozwala na lepszą składnię niż ByteBuffer?
Aleksandr Dubinsky,

@AleksandrDubinsky W czasie pisania wyglądało na to, że Java SE 7 pozwoli na []notację tablicową na typach zdefiniowanych przez użytkownika, takich jak Listi ByteBuffer. Wciąż czekam ...
Tom Hawtin - tackline

2

@ unikalna72 odpowiedź jako prosta funkcja lub linia, może być konieczne zastąpienie obiektu odpowiednim typem klasy, który chcesz „pokroić”. Dostępne są dwa warianty odpowiadające różnym potrzebom.

/// 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

Co powiesz na cienkie Listopakowanie?

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

(Nieprzetestowane)


Spowoduje to boksowanie-rozpakowywanie bajtów. Może być powolny.
MP Korstanje

@mpkorstanje: W bibliotece Orable Java Byteobiekty dla wszystkich bytewartości są buforowane. Więc boks nad głową powinien być raczej powolny.
Lii

1

Musiałem iterować przez koniec tablicy i nie chciałem jej kopiować. Moje podejście polegało na stworzeniu iterowalnego ciągu tablicy.

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

Jest to nieco lżejsze niż Arrays.copyOfRange - brak zasięgu lub wartość ujemna

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;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.