Récupérer un segment d'un tableau en Java sans créer un nouveau tableau sur le tas

181

Je recherche une méthode en Java qui retournera un segment d'un tableau. Un exemple serait d'obtenir le tableau d'octets contenant les 4e et 5e octets d'un tableau d'octets. Je ne veux pas avoir à créer un nouveau tableau d'octets dans la mémoire du tas juste pour le faire. En ce moment, j'ai le code suivant:

doSomethingWithTwoBytes(byte[] twoByteArray);

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

J'aimerais savoir s'il y avait un moyen de faire simplement doSomething(bigArray.getSubArray(4, 2))où 4 est le décalage et 2 est la longueur, par exemple.

jbu
la source
1
Qu'en est-il de la magie JNI en C ++? Cela pourrait être un désastre du GC POV?
AlikElzin-kilaka
Doit-il s'agir d'un tableau d'octets primitifs?
MP Korstanje

Réponses:

185

Avertissement: Cette réponse n'est pas conforme aux contraintes de la question:

Je ne veux pas avoir à créer un nouveau tableau d'octets dans la mémoire du tas juste pour le faire.

( Honnêtement, je pense que ma réponse mérite d'être supprimée. La réponse de @ unique72 est correcte. Je vais laisser cette modification reposer un peu, puis je supprimerai cette réponse. )


Je ne connais pas de moyen de le faire directement avec des tableaux sans allocation de tas supplémentaire, mais les autres réponses utilisant un wrapper de sous-liste ont une allocation supplémentaire pour le wrapper uniquement - mais pas le tableau - ce qui serait utile dans le cas de un large éventail.

Cela dit, si l'on recherche la brièveté, la méthode utilitaire a Arrays.copyOfRange()été introduite dans Java 6 (fin 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);
David J. Liszewski
la source
10
cela alloue toujours dynamiquement un nouveau segment de mémoire et copie la plage dans celui-ci.
Dan
4
Merci Dan - J'ai négligé qu'OP ne voulait pas créer de nouveau tableau et je n'ai pas regardé la mise en œuvre de copyOfRange. S'il s'agissait de sources fermées, cela aurait peut-être pu passer. :)
David J.Liszewski
7
Je pense que beaucoup de gens veulent créer un sous-tableau à partir d'un tableau et ne craignent pas qu'il utilise plus de mémoire. Ils rencontrent cette question et obtiennent la réponse qu'ils veulent - alors s'il vous plaît ne supprimez pas car c'est utile - je pense que ça va.
The Lonely Coder
2
en fait, copyOfRange alloue toujours un nouveau segment de mémoire
Kevingo Tsai
167

Arrays.asList(myArray)délègue à new ArrayList(myArray), qui ne copie pas le tableau mais stocke simplement la référence. Utiliser List.subList(start, end)après cela fait un SubListqui fait simplement référence à la liste d'origine (qui fait toujours référence au tableau). Pas de copie du tableau ou de son contenu, il suffit de créer un wrapper et toutes les listes impliquées sont sauvegardées par le tableau d'origine. (Je pensais que ce serait plus lourd.)

unique72
la source
9
Pour clarifier, il délègue à une classe privée dans Arraysappelé de manière confuse ArrayList, mais qui est vraiment un Listautour d'un tableau, par opposition à java.util.ArrayListce qui ferait une copie. Pas de nouvelles allocations (du contenu de la liste) et pas de dépendances tierces. C'est, je crois, la réponse la plus correcte.
dimo414
28
En fait, cela ne fonctionnera pas pour les tableaux de type primitif comme le souhaitait l'OP ( byte[]dans son cas). Tout ce que vous obtiendrez serait List<byte[]>. Et le passage byte[] bigArrayà Byte[] bigArraypourrait imposer une surcharge de mémoire importante.
Dmitry Avtonomov
2
La seule façon d'atteindre vraiment ce que l'on souhaite est de passer par la sun.misc.Unsafeclasse.
Dmitry Avtonomov
39

Si vous recherchez une approche d'alias de style pointeur, de sorte que vous n'ayez même pas besoin d'allouer de l'espace et de copier les données, je pense que vous n'avez pas de chance.

System.arraycopy() copiera de votre source à destination, et l'efficacité est réclamée pour cet utilitaire. Vous devez allouer le tableau de destination.

djna
la source
3
oui, j'espérais une sorte de méthode de pointeur car je ne veux pas allouer dynamiquement de la mémoire. mais il semble que c'est ce que je vais devoir faire.
jbu
1
Comme le suggère @ unique72, il semble y avoir des moyens de faire ce que vous voulez en exploitant les subtilités dans l'implémentation de divers types de listes / tableaux java. Cela semble possible, mais pas de manière explicite et cela me fait hésiter à trop m'y fier ...
Andrew
Pourquoi array*copy*()réutiliser la même mémoire? N'est-ce pas exactement le contraire de ce à quoi un appelant pourrait s'attendre?
Patrick Favre
23

Une façon consiste à envelopper le tableau dans java.nio.ByteBuffer , d'utiliser les fonctions put / get absolues et de découper le tampon pour qu'il fonctionne sur un sous-tableau.

Par exemple:

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

Notez que vous devez appeler les deux wrap()et slice(), puisque wrap()par lui-même n'affecte que les fonctions put / get relatives, pas les fonctions absolues.

ByteBuffer peut être un peu difficile à comprendre, mais il est très probablement mis en œuvre efficacement et mérite d'être appris.

Soulman
la source
1
Il convient également de noter que les objets ByteBuffer peuvent être assez facilement décodés:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl
@Soulman merci pour l'explication, mais une question est-ce plus efficace que d'utiliser Arrays.copyOfRange?
ucMedia le
1
@ucMedia pour un tableau de deux octets, Arrays.copyOfRangeest probablement plus efficace. En règle générale, vous devrez mesurer votre cas d'utilisation spécifique.
Soulman le
20

Utilisez java.nio.Buffer. C'est un wrapper léger pour les tampons de divers types primitifs et aide à gérer le découpage, la position, la conversion, l'ordre des octets, etc.

Si vos octets proviennent d'un Stream, les tampons NIO peuvent utiliser le "mode direct" qui crée un tampon sauvegardé par des ressources natives. Cela peut améliorer les performances dans de nombreux cas.

James Schek
la source
14

Vous pouvez utiliser ArrayUtils.subarray dans apache commons. Pas parfait mais un peu plus intuitif que System.arraycopy. l'inconvénient est qu'il introduit une autre dépendance dans votre code.

seth
la source
23
C'est la même chose que Arrays.copyOfRange () dans Java 1.6
newacct
10

Je vois que la réponse à la sous-liste est déjà là, mais voici le code qui démontre que c'est une vraie sous-liste, pas une copie:

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

Cependant, je ne pense pas qu'il existe un bon moyen de le faire directement avec des tableaux.

Carl Manaster
la source
9
List.subList(int startIndex, int endIndex)
Manuel Selva
la source
9
Vous devez d'abord envelopper le tableau sous forme de liste: Arrays.asList (...). Sublist (...);
camickr
7

Les Lists vous permettent d'utiliser et de travailler avec subListquelque chose de manière transparente. Les tableaux primitifs vous obligeraient à suivre une sorte de limite de décalage. ByteBuffers ont des options similaires à celles que j'ai entendues.

Edit: Si vous êtes en charge de la méthode utile, vous pouvez simplement la définir avec des limites (comme cela est fait dans de nombreuses méthodes liées aux tableaux dans Java lui-même:

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

Ce n'est pas clair, cependant, si vous travaillez sur les éléments du tableau eux-mêmes, par exemple si vous calculez quelque chose et écrivez le résultat?

Akarnokd
la source
6

Une option serait de passer le tableau entier et les index de début et de fin, et de faire une itération entre ceux-ci au lieu d'itérer sur tout le tableau passé.

void method1(byte[] array) {
    method2(array,4,5);
}
void method2(byte[] smallarray,int start,int end) {
    for ( int i = start; i <= end; i++ ) {
        ....
    }
}
Sam DeFabbia-Kane
la source
6

Les références Java pointent toujours vers un objet. L'objet a un en-tête qui, entre autres, identifie le type de béton (les casts peuvent donc échouer avecClassCastException ). Pour les tableaux, le début de l'objet inclut également la longueur, les données suivent alors immédiatement après en mémoire (techniquement une implémentation est libre de faire ce qu'elle veut, mais il serait stupide de faire autre chose). Donc, vous pouvez; t avoir une référence qui pointe quelque part dans un tableau.

En C, les pointeurs pointent n'importe où et vers n'importe quoi, et vous pouvez pointer vers le milieu d'un tableau. Mais vous ne pouvez pas lancer en toute sécurité ou découvrir la longueur de la matrice. En D, le pointeur contient un décalage dans le bloc de mémoire et la longueur (ou de manière équivalente un pointeur vers la fin, je ne me souviens pas de ce que fait réellement l'implémentation). Cela permet à D de découper des tableaux. En C ++, vous auriez deux itérateurs pointant vers le début et la fin, mais C ++ est un peu étrange comme ça.

Donc, pour revenir à Java, non, vous ne pouvez pas. Comme mentionné, NIO ByteBuffervous permet d'envelopper un tableau puis de le découper, mais donne une interface peu pratique. Vous pouvez bien sûr copier, ce qui est probablement beaucoup plus rapide que vous ne le pensez. Vous pouvez introduire votre propre Stringabstraction qui vous permet de découper un tableau (l'implémentation Sun actuelle de Stringa une char[]référence plus un décalage de début et une longueur, une implémentation plus performante a juste le char[]). byte[]est de bas niveau, mais toute abstraction basée sur les classes que vous mettez sur qui va faire un terrible désordre de la syntaxe, jusqu'à JDK7 (peut-être).

Tom Hawtin - Tacle
la source
Merci d'avoir expliqué pourquoi ce serait impossible. Btw, String se copie maintenant substringdans HotSpot (oubliez quelle version a changé cela). Pourquoi dites-vous que JDK7 permettrait une meilleure syntaxe que ByteBuffer?
Aleksandr Dubinsky
@AleksandrDubinsky Au moment de la rédaction de cet article, il semblait que Java SE 7 allait autoriser la []notation de tableau sur les types définis par l'utilisateur, tels que Listet ByteBuffer. Encore en attente ...
Tom Hawtin - tackline
2

@ unique72 answer comme une simple fonction ou ligne, vous devrez peut-être remplacer Object, par le type de classe respectif que vous souhaitez «découper». Deux variantes sont proposées pour répondre à divers besoins.

/// 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();
}
PicoCreator
la source
1

Que diriez-vous d'un Listemballage fin ?

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 testé)

RoToRa
la source
Cela entraînera un boxing-unboxing d'octets. Ça pourrait être lent.
MP Korstanje
@mpkorstanje: Dans la bibliothèque Orable Java, les Byteobjets de toutes les bytevaleurs sont mis en cache. Les frais généraux de boxe devraient donc être assez lents.
Lii
1

J'avais besoin d'itérer jusqu'à la fin d'un tableau et je ne voulais pas copier le tableau. Mon approche était de créer un Iterable sur le tableau.

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");
        }
      };
    }
  };
}
Owen O'Malley
la source
-1

C'est un peu plus léger que Arrays.copyOfRange - pas de plage ou négatif

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;
}
liaison
la source