Prendi un segmento di un array in Java senza creare un nuovo array su heap


181

Sto cercando un metodo in Java che restituirà un segmento di un array. Un esempio potrebbe essere quello di ottenere l'array di byte contenente il quarto e il quinto byte di un array di byte. Non voglio creare un nuovo array di byte nella memoria dell'heap solo per farlo. In questo momento ho il seguente codice:

doSomethingWithTwoBytes(byte[] twoByteArray);

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

Vorrei sapere se c'era un modo per fare solo doSomething(bigArray.getSubArray(4, 2))dove 4 è l'offset e 2 è la lunghezza, ad esempio.


1
Che ne dici di fare un po 'di magia JNI in C ++? Potrebbe essere un disastro dal GC POV?
AlikElzin-Kilaka

Deve essere una matrice di byte primitivi?
MP Korstanje,

Risposte:


185

Disclaimer: questa risposta non è conforme ai vincoli della domanda:

Non voglio creare un nuovo array di byte nella memoria dell'heap solo per farlo.

( Onestamente, ritengo che la mia risposta sia degna di essere cancellata. La risposta di @ unique72 è corretta. Imma lascio che questa modifica rimanga per un po 'e poi eliminerò questa risposta. )


Non conosco un modo per farlo direttamente con array senza allocazione di heap aggiuntiva, ma le altre risposte che usano un wrapper di sotto-elenco hanno allocazioni aggiuntive solo per il wrapper - ma non per l'array - che sarebbe utile nel caso di una vasta gamma.

Detto questo, se si è alla ricerca di brevità, il metodo di utilità è Arrays.copyOfRange()stato introdotto in Java 6 (fine 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
questo alloca ancora in modo dinamico un nuovo segmento di memoria e ne copia l'intervallo.
Dan

4
Grazie Dan - ho trascurato che OP non voleva creare un nuovo array e non ho considerato l'implementazione di copyOfRange. Se fosse stato chiuso potrebbe forse essere passato. :)
David J. Liszewski,

7
Penso che molte persone vogliano creare un sub array da un array e non sono preoccupati che usi un po 'più di memoria. Si imbattono in questa domanda e ottengono la risposta che vogliono - quindi, per favore, non cancellare perché è utile - Penso che sia ok.
The Lonely Coder,

2
in realtà, copyOfRange alloca ancora il nuovo segmento di memoria
Kevingo Tsai il

167

Arrays.asList(myArray)delega a nuovo ArrayList(myArray), che non copia l'array ma memorizza solo il riferimento. Usando List.subList(start, end)after that crea un che fa SubListsolo riferimento all'elenco originale (che fa ancora solo riferimento all'array). Nessuna copia dell'array o del suo contenuto, solo la creazione di wrapper e tutti gli elenchi coinvolti sono supportati dall'array originale. (Ho pensato che sarebbe stato più pesante.)


9
Per chiarire, si delega a una classe privata in modo Arraysconfuso chiamato ArrayList, ma che è davvero un Listintorno a un array, al contrario del java.util.ArrayListquale farebbe una copia. Nessuna nuova allocazione (del contenuto dell'elenco) e nessuna dipendenza di terze parti. Questa è, credo, la risposta più corretta.
dimo414

28
In realtà, questo non funzionerà per le matrici di tipo primitivo come voleva l'OP ( byte[]nel suo caso). Tutto ciò che otterrai sarebbe List<byte[]>. E il passaggio byte[] bigArraya Byte[] bigArraypotrebbe imporre un notevole sovraccarico di memoria.
Dmitry Avtonomov

2
L'unico modo per ottenere veramente ciò che si desidera è attraverso la sun.misc.Unsafeclasse.
Dmitry Avtonomov

39

Se stai cercando un approccio di aliasing in stile puntatore, in modo da non dover nemmeno allocare spazio e copiare i dati, credo che tu sia sfortunato.

System.arraycopy() copierà dalla sorgente alla destinazione e viene richiesta efficienza per questa utility. È necessario allocare l'array di destinazione.


3
sì, speravo in qualche tipo di metodo del puntatore poiché non volevo allocare dinamicamente memoria. ma sembra che sia quello che dovrò fare.
jbu,

1
Come suggerisce @ unique72, sembrano esserci modi per fare ciò che vuoi sfruttando le sottigliezze nell'implementazione di vari tipi di array / elenchi Java. Questo sembra essere possibile, ma non in modo esplicito e ciò mi rende riluttante a fare troppo affidamento su di esso ...
Andrew

Perché array*copy*()riutilizzare la stessa memoria? Non è esattamente l'opposto di ciò che un chiamante si aspetterebbe?
Patrick Favre,

23

Un modo è avvolgere l'array java.nio.ByteBuffer, usare le funzioni put / get assolute e suddividere il buffer per lavorare su un subarray.

Per esempio:

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

Si noti che è necessario chiamare entrambi wrap()e slice(), poiché di wrap()per sé influisce solo sulle relative funzioni put / get, non su quelle assolute.

ByteBuffer può essere un po 'complicato da capire, ma molto probabilmente è implementato in modo efficiente e vale la pena imparare.


1
Vale anche la pena notare che gli oggetti ByteBuffer possono essere facilmente decodificati:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl,

@Soulman grazie per la spiegazione, ma una domanda è più efficiente dell'uso Arrays.copyOfRange?
ucMedia,

1
@ucMedia per un array a due byte, Arrays.copyOfRangeè probabilmente più efficiente. Generalmente, dovresti misurare per il tuo caso d'uso specifico.
Soulman,

20

Usa java.nio.Buffer's. È un involucro leggero per buffer di vari tipi primitivi e aiuta a gestire lo slicing, la posizione, la conversione, l'ordinamento dei byte, ecc.

Se i byte provengono da uno stream, i buffer NIO possono utilizzare la "modalità diretta" che crea un buffer supportato da risorse native. Ciò può migliorare le prestazioni in molti casi.


14

È possibile utilizzare ArrayUtils.subarray in beni comuni apache. Non perfetto ma un po 'più intuitivo di System.arraycopy. Il lato negativo è che introduce un'altra dipendenza nel codice.


23
È lo stesso di Arrays.copyOfRange () in Java 1.6
newacct

10

Vedo che la risposta dell'elenco secondario è già qui, ma ecco il codice che dimostra che è un elenco secondario vero, non una copia:

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

Tuttavia, non credo che ci sia un buon modo per farlo direttamente con gli array.


9
List.subList(int startIndex, int endIndex)

9
Dovresti prima avvolgere l'Array come Elenco: Arrays.asList (...). Sublist (...);
Camickr,

7

I Lists consentono di utilizzare e lavorare con subListqualcosa di trasparente. Le matrici primitive richiedono di tenere traccia di una sorta di offset - limite. ByteBuffers hanno opzioni simili a quelle che ho sentito.

Modifica: se sei responsabile del metodo utile, potresti semplicemente definirlo con limiti (come fatto in molti metodi relativi all'array in Java stesso:

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

Non è chiaro, tuttavia, se si lavora sugli elementi dell'array stessi, ad esempio si calcola qualcosa e si riscrive il risultato?


6

Un'opzione sarebbe quella di passare l'intero array e gli indici di inizio e fine e iterare tra quelli invece di iterare sull'intero array passato.

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

6

I riferimenti Java puntano sempre a un oggetto. L'oggetto ha un'intestazione che identifica tra l'altro il tipo di calcestruzzo (quindi i cast possono fallire ClassCastException). Per le matrici, l'inizio dell'oggetto include anche la lunghezza, i dati seguono immediatamente dopo in memoria (tecnicamente un'implementazione è libera di fare ciò che gli piace, ma sarebbe stupido fare qualsiasi altra cosa). Quindi, non puoi avere un riferimento che punti da qualche parte in un array.

Nei puntatori C puntano ovunque e verso qualsiasi cosa e si può puntare al centro di un array. Ma non puoi eseguire il cast o scoprire in sicurezza la durata dell'array. In D il puntatore contiene un offset nel blocco di memoria e nella lunghezza (o equivalentemente un puntatore alla fine, non ricordo cosa fa effettivamente l'implementazione). Ciò consente a D di tagliare le matrici. In C ++ avresti due iteratori che indicano l'inizio e la fine, ma C ++ è un po 'strano.

Quindi, tornando a Java, no, non puoi. Come accennato, NIO ByteBufferti consente di avvolgere un array e quindi dividerlo, ma fornisce un'interfaccia scomoda. Ovviamente puoi copiare, che è probabilmente molto più veloce di quanto pensi. Potresti introdurre la tua Stringastrazione simile a quella che ti consente di suddividere un array (l'attuale implementazione Sun Stringha un char[]riferimento più un offset iniziale e una lunghezza, un'implementazione con prestazioni più elevate ha solo char[]). byte[]è di basso livello, ma qualsiasi astrazione basata sulla classe che metterai su farà un disastro terribile della sintassi, fino a JDK7 (forse).


Grazie per aver spiegato perché sarebbe impossibile. A proposito, String ora copia substringin HotSpot (dimentica quale build ha cambiato questo). Perché dici che JDK7 consentirebbe una sintassi migliore di ByteBuffer?
Aleksandr Dubinsky,

@AleksandrDubinsky Al momento della scrittura sembrava che Java SE 7 stesse per consentire la []notazione di array su tipi definiti dall'utente, come Liste ByteBuffer.
Sto

2

@ risposta unique72 come semplice funzione o riga, potrebbe essere necessario sostituire Object, con il rispettivo tipo di classe che si desidera "suddividere". Sono disponibili due varianti per soddisfare le diverse esigenze.

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

Che ne dici di un Listinvolucro sottile ?

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

(Non testato)


Ciò comporterà il boxing-unboxing dei byte. Potrebbe essere lento.
MP Korstanje,

@mpkorstanje: nella libreria Java Orable gli Byteoggetti per tutti i bytevalori sono memorizzati nella cache. Quindi il sovraccarico di boxe dovrebbe essere piuttosto lento.
Lii

1

Avevo bisogno di iterare fino alla fine di un array e non volevo copiare l'array. Il mio approccio era quello di creare un Iterable sull'array.

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

Questo è un po 'più leggero di Arrays.copyOfRange - nessun intervallo o negativo

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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.