web-dev-qa-db-fra.com

Saisir un segment d'un tableau dans Java sans créer de nouveau tableau sur le tas

Je cherche une méthode dans Java qui renverra un segment d'un tableau. Un exemple serait d'obtenir le tableau d'octets contenant les 4ème et 5ème octets d'un tableau d'octets. Je ne veux pas avoir à créer un nouveau tableau d'octets dans la mémoire heap juste pour faire cela. 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 simplement faire doSomething(bigArray.getSubArray(4, 2)) où 4 est le décalage et 2 la longueur, par exemple.

180
jbu

Disclaimer: Cette réponse ne correspond pas aux contraintes de la question:

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

( Honnêtement, j'estime que ma réponse mérite d'être supprimée. La réponse de @ unique72 est correcte. Imma a laissé cette édition reposer pendant un moment, puis je la supprimerai. )


Je ne sais pas comment faire cela 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 grand tableau.

Cela dit, si l’on cherche à être bref, la méthode de l’utilitaire Arrays.copyOfRange() a é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);
183
David J. Liszewski

Arrays.asList(myArray) délégués à la nouvelle ArrayList(myArray), qui ne copie pas le tableau, mais stocke simplement la référence. Utiliser List.subList(start, end) après cela crée un SubList qui ne fait que référencer la liste d'origine (qui ne fait que référencer le tableau). Aucune copie du tableau ou de son contenu, juste la création d'encapsuleurs et toutes les listes impliquées sont sauvegardées par le tableau d'origine. (Je pensais que ce serait plus lourd.)

165
unique72

Si vous recherchez une approche de type aliasing de type 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, l'efficacité est revendiquée pour cet utilitaire. Vous devez affecter le tableau de destination.

39
djna

Une solution consiste à envelopper le tableau dans Java.nio.ByteBuffer, à utiliser les fonctions absolues put/get et à découper le tampon en tranches pour qu'il fonctionne dans 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 wrap() et slice(), étant donné que wrap() n'affecte que les fonctions put/get relatives et non les fonctions absolues.

ByteBuffer peut être un peu difficile à comprendre, mais il est probablement implémenté de manière efficace et vaut la peine d'être appris.

22
Soulman

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

Si vos octets proviennent d'un flux, 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.

20
James Schek

Vous pouvez utiliser le 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.

14
seth

Je vois que la réponse de subList est déjà là, mais voici un code qui montre qu'il s'agit d'une vraie sous-liste, pas d'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));
    }
}

Je ne crois pas qu'il existe un bon moyen de faire cela directement avec les tableaux, cependant.

10
Carl Manaster
List.subList(int startIndex, int endIndex)
9
Manuel Selva

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

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

Les références Java pointent toujours sur un objet. L'objet a un en-tête qui identifie, entre autres choses, le type concret (ainsi, les conversions peuvent échouer avec ClassCastException). Pour les tableaux, le début de l'objet inclut également la longueur, les données suivent ensuite immédiatement en mémoire (techniquement, une implémentation est libre de faire ce que bon lui semble, mais il serait idiot de faire autre chose). Donc, vous ne pouvez pas avoir une référence qui pointe quelque part dans un tableau.

En C, les pointeurs pointent n'importe où et n'importe où, et vous pouvez pointer vers le milieu d'un tableau. Mais vous ne pouvez pas lancer en toute sécurité ou savoir combien de temps le tableau est. En D, le pointeur contient un décalage dans le bloc de mémoire et sa longueur (ou, de manière équivalente, un pointeur vers la fin, je ne me souviens pas de ce que l’implémentation fait réellement). Cela permet à D de découper des tableaux. En C++, deux itérateurs indiquent 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é précédemment, NIO ByteBuffer vous permet d’envelopper un tableau, puis de le découper en tranches, mais offre 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 abstraction de type String- qui vous permet de découper un tableau (l'implémentation Sun actuelle de String a une référence char[] ainsi qu'une longueur et un décalage de début, une implémentation plus performante avoir le char[]). byte[] est de bas niveau, mais toute abstraction basée sur la classe que vous utiliserez produira un fouillis de syntaxe, jusqu'au JDK7 (peut-être).

6

Les Lists vous permettent d'utiliser et de travailler avec subList de quelque chose de manière transparente. Les tableaux primitifs nécessiteraient que vous gardiez trace d'une sorte de limite de décalage. ByteBuffers ont des options similaires à celles que j'ai entendues.

Edit: Si vous êtes responsable de la méthode utile, vous pouvez simplement la définir avec des limites (comme dans beaucoup de 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. vous calculez quelque chose et écrivez le résultat?

6
akarnokd

@ unique72 comme une simple fonction ou ligne, vous devrez peut-être remplacer Object par le type de classe que vous souhaitez "découper". Deux variantes sont proposées pour répondre aux différents 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();
}
2
PicoCreator

J'avais besoin de parcourir la fin d'un tableau et je ne voulais pas copier le tableau. Mon approche était de faire 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");
        }
      };
    }
  };
}
1
Owen O'Malley

Que diriez-vous d'un wrapper mince 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;
      }
   };
}

(Non testé)

1
RoToRa