2009-06-29 14 views

risposta

51

Il problema è che byte[] usi oggetto di identità per equals e hashCode, in modo che

byte[] b1 = {1, 2, 3} 
byte[] b2 = {1, 2, 3} 

non corrisponde a HashMap. Vedo tre opzioni:

  1. Wrapping in un String, ma poi si deve essere attenti a codifica problemi (è necessario accertarsi che il byte -> String -> byte ti dà la stessa byte).
  2. Utilizzare List<Byte> (può essere costoso in memoria).
  3. Effettuare la propria classe di wrapping, scrivendo e equals per utilizzare il contenuto dell'array di byte.
+3

Ho risolto il problema del wrapping delle stringhe usando la codifica esadecimale. In alternativa puoi usare la codifica base64. – metadaddy

+0

L'opzione di classe wrapping/handling è semplice e dovrebbe essere molto leggibile. – ZX9

1

Credo che gli array in Java non implementino necessariamente i metodi hashCode() e equals(Object) in modo intuitivo. Cioè, due array di byte identici non necessariamente condividono lo stesso codice hash e non necessariamente dichiarano di essere uguali. Senza questi due tratti, la tua HashMap si comporterà inaspettatamente.

Pertanto, si consiglia di contro utilizzando byte[] come chiavi in ​​una HashMap.

+4

s/non necessariamente/non/ –

+0

Suppongo che la mia formulazione era un po 'fuori. Stavo calcolando la situazione in cui l'array di byte THE SAME viene utilizzato sia per l'inserimento nella mappa hash sia per il recupero dalla mappa hash. In tal caso, "entrambi" gli array di byte sono identici e condividono lo stesso codice hash. –

71

Va bene fino a quando si desidera solo l'uguaglianza di riferimento per la propria chiave - gli array non implementano "l'uguaglianza di valore" nel modo che probabilmente si vorrebbe. Per esempio:

byte[] array1 = new byte[1]; 
byte[] array2 = new byte[1]; 

System.out.println(array1.equals(array2)); 
System.out.println(array1.hashCode()); 
System.out.println(array2.hashCode()); 

stampe qualcosa di simile:

false 
1671711 
11394033 

(. I numeri reali sono irrilevanti, il fatto che sono diversi è importante)

si Supponendo realtà vogliono l'uguaglianza , Ti suggerisco di creare il tuo wrapper che contenga uno byte[] e implementa appropriatamente l'uguaglianza e la generazione del codice hash:

public final class ByteArrayWrapper 
{ 
    private final byte[] data; 

    public ByteArrayWrapper(byte[] data) 
    { 
     if (data == null) 
     { 
      throw new NullPointerException(); 
     } 
     this.data = data; 
    } 

    @Override 
    public boolean equals(Object other) 
    { 
     if (!(other instanceof ByteArrayWrapper)) 
     { 
      return false; 
     } 
     return Arrays.equals(data, ((ByteArrayWrapper)other).data); 
    } 

    @Override 
    public int hashCode() 
    { 
     return Arrays.hashCode(data); 
    } 
} 

Si noti che se si modificano i valori all'interno dell'array di byte dopo aver usato il ByteArrayWrapper, come una chiave in un HashMap (ecc) avrete problemi alzando la chiave di nuovo ... si potrebbe prendere una copia del dati nel costruttore ByteArrayWrapper se lo si desidera, ma ovviamente si tratterà di uno spreco di prestazioni se si sa che lo non sarà modificando il contenuto dell'array di byte.

MODIFICA: come indicato nei commenti, è possibile utilizzare anche ByteBuffer (in particolare, il metodo ByteBuffer#wrap(byte[])). Non so se sia davvero la cosa giusta, date tutte le abilità extra che hanno ByteBuffer s di cui non hai bisogno, ma è un'opzione.

+0

@dfa: il test "instanceof" gestisce il caso nullo. –

+3

Un altro paio di cose che potreste aggiungere all'implementazione del wrapper: 1. Prendere una copia del byte [] sulla costruzione, garantendo quindi che l'oggetto sia immutabile, il che significa che non c'è pericolo che il codice hash della vostra chiave cambi nel tempo. 2. Pre-calcolare e memorizzare il codice hash una volta (assumendo che la velocità sia più importante dell'overhead di memoria). – Adamski

+1

@Adamski: menziono la possibilità di copiare alla fine della risposta. In alcuni casi è la cosa giusta da fare, ma non negli altri. Probabilmente vorremmo renderlo un'opzione (possibilmente metodi statici invece di costruttori - copyOf e wrapperAround). Nota che * senza * copiare, puoi cambiare l'array sottostante fino a quando non prendi l'hash per la prima volta e controlli l'uguaglianza, che potrebbe essere utile in alcune situazioni. –

0

vedo problemi dato che si dovrebbe usare Arrays.equals e Array.hashCode, al posto di implementazioni di array predefinito

+0

E come faresti ad usare la HashMap? –

+0

vedere la risposta di Jon Skeet (un wrapper di array di byte) – dfa

0

Arrays.toString (byte)

+1

Potrebbe essere usato, ma non molto efficiente. Se vuoi andare in questo modo, potresti usare la codifica base64. –

11

Si potrebbe usare java.math.BigInteger. Ha un costruttore BigInteger(byte[] val). È un tipo di riferimento, quindi potrebbe essere utilizzato come chiave per la tabella hash. E .equals() e .hashCode() sono definiti come per i rispettivi numeri interi, il che significa che BigInteger ha una semantica uguale uguale a una matrice di byte [].

+0

grazie, ottima soluzione! –

+11

Suoni atractive, ma è sbagliato, poiché due array che differiscono solo per gli elementi zero iniziali (ad esempio, '{0,100}' e '{100}') daranno lo stesso BigInteger – leonbloy

+0

Good point @leonbloy. Potrebbe esserci una soluzione alternativa: aggiungendo una costante costante di byte non nulli fissi ad essa, ma richiederà di scrivere un wrapper attorno al costruttore BigInteger e ci riporterà alla risposta di Jon. –

34

Possiamo usare ByteBuffer per questo (Questo è fondamentalmente il byte [] involucro con un comparatore)

HashMap<ByteBuffer, byte[]> kvs = new HashMap<ByteBuffer, byte[]>(); 
byte[] k1 = new byte[]{1,2 ,3}; 
byte[] k2 = new byte[]{1,2 ,3}; 
byte[] val = new byte[]{12,23,43,4}; 

kvs.put(ByteBuffer.wrap(k1), val); 
System.out.println(kvs.containsKey(ByteBuffer.wrap(k2))); 

stamperà

true 
+1

+1 per il wrapper di array di byte leggero (penso ...) – Nicholas

+3

Questo funziona correttamente con ByteBuffer.wrap(), ma attenzione se i contenuti del ByteBuffer sono stati creati utilizzando un paio di chiamate put() per creare una chiave composta array di byte. In questo caso, l'ultima chiamata put() deve essere seguita da una chiamata rewind() - altrimenti equals() restituisce true anche quando gli array di byte sottostanti contengono dati diversi. – RenniePet

+0

Questa sarebbe una buona soluzione, ma se vuoi serializzare la mappa (come nel mio caso) non puoi usare questo approccio. –

0

Si potrebbe anche convertire il byte [] per un ' sicuro stringa' usando Base32 Base64 o, per esempio:

byte[] keyValue = new byte[] {…}; 
String key = javax.xml.bind.DatatypeConverter.printBase64Binary(keyValue); 

naturalmente ci sono molte varianti di cui sopra, li Ke:

String key = org.apache.commons.codec.binary.Base64.encodeBase64(keyValue); 
1

Si consiglia di utilizzare creare una classe di quacosa come ByteArrKey ed il sovraccarico codice hash e metodi uguali, ricorda il contratto tra loro.

Ciò consente una maggiore flessibilità in quanto è possibile saltare le voci 0 aggiunte alla fine della matrice di byte, specialmente se si copia solo una parte dell'altra buffer di byte.

In questo modo deciderai come entrambi gli oggetti DOVREBBE essere uguale.

2

Sono molto sorpreso che le risposte non indicano l'alternativa più semplice.

Sì, non è possibile utilizzare una HashMap, ma nessuno impedisce l'utilizzo di una SortedMap come alternativa. L'unica cosa è scrivere un comparatore che deve confrontare gli array. Non è così performante come un HashMap, ma se volete una semplice alternativa, qui si va (si può sostituire SortedMap con la mappa, se si desidera nascondere l'attuazione):

private SortedMap<int[], String> testMap = new TreeMap<>(new ArrayComparator()); 

private class ArrayComparator implements Comparator<int[]> { 
    @Override 
    public int compare(int[] o1, int[] o2) { 
     int result = 0; 
     int maxLength = Math.max(o1.length, o2.length); 
     for (int index = 0; index < maxLength; index++) { 
     int o1Value = index < o1.length ? o1[index] : 0; 
     int o2Value = index < o2.length ? o2[index] : 0; 
     int cmp  = Integer.compare(o1Value, o2Value); 
     if (cmp != 0) { 
      result = cmp; 
      break; 
     } 
     } 
     return result; 
    } 
    } 

Questa implementazione può essere regolata per altri array, l'unica cosa che devi sapere è che gli array uguali (= uguale lunghezza con membri uguali) devono restituire 0 e che hai un ordine deterministico

0

Ecco una soluzione che utilizza TreeMap, interfaccia Comparator e metodo java java. util.Arrays.equals (byte [], byte []);

NOTA: L'ordinamento nella mappa non è rilevante con questo metodo

SortedMap<byte[], String> testMap = new TreeMap<>(new ArrayComparator()); 

static class ArrayComparator implements Comparator<byte[]> { 
    @Override 
    public int compare(byte[] byteArray1, byte[] byteArray2) { 

     int result = 0; 

     boolean areEquals = Arrays.equals(byteArray1, byteArray2); 

     if (!areEquals) { 
      result = -1; 
     } 

     return result; 
    } 
} 
Problemi correlati