Utilizzo di un array di byte come chiave Mappa

Vedi qualche problema con l’utilizzo di un array di byte come chiave Mappa? Potrei anche fare una new String(byte[]) e hash per String ma è più semplice usare byte[] .

Il problema è che byte[] usa l’id quadro dell’object per equals e hashCode , così

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

non HashMap una HashMap . Vedo tre opzioni:

  1. Avvolgere in una String , ma poi devi stare attento ai problemi di codifica (devi assicurarti che il byte -> String -> byte ti dia gli stessi byte).
  2. Usa List (può essere costoso in memoria).
  3. Crea la tua class di wrapping, scrivendo hashCode ed è equals per usare il contenuto dell’array di byte.

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 in cui 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()); 

stampa qualcosa come:

 false 1671711 11394033 

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

Supponendo che tu voglia davvero l’ uguaglianza, ti suggerisco di creare il tuo wrapper che contiene un 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 della matrice di byte dopo aver usato ByteArrayWrapper , come chiave in una HashMap (ecc.) Si avranno problemi a cercare nuovamente la chiave … si potrebbe prendere una copia dei dati nel costruttore ByteArrayWrapper se vuoi, ma ovviamente questo sarà uno spreco di prestazioni se sai che non cambierai il contenuto della matrice di byte.

EDIT: Come menzionato nei commenti, potresti anche utilizzare ByteBuffer per questo (in particolare, il suo ByteBuffer#wrap(byte[]) ). Non so se sia davvero la cosa giusta, date tutte le abilità extra di ByteBuffer che non ti servono, ma è un’opzione.

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

 HashMap kvs = new HashMap(); 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 

È ansible utilizzare java.math.BigInteger . Ha un 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 semantica equivalente uguale a array byte [].

Sono molto sorpreso che le risposte non stiano rilevando l’alternativa più semplice.

Sì, non è ansible utilizzare una HashMap, ma nessuno ti impedisce di usare una SortedMap come alternativa. L’unica cosa è scrivere un comparatore che deve confrontare gli array. Non è performante come una HashMap, ma se vuoi un’alternativa semplice, eccoti (puoi sostituire SortedMap con Map se vuoi hide l’implementazione):

  private SortedMap testMap = new TreeMap<>(new ArrayComparator()); private class ArrayComparator implements Comparator { @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 di cui devi essere a conoscenza è che gli array uguali (= lunghezza uguale con membri uguali) devono restituire 0 e che hai un ordine deterministico

Credo che gli array in Java non implementino intuitivamente i metodi hashCode() ed equals(Object) . 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, consiglio di non utilizzare byte[] come chiavi in ​​una HashMap.

Dovresti usare una class come ByteArrKey e sovraccaricare hashcode e metodi uguali, ricorda il contratto tra loro.

Ciò ti darà una maggiore flessibilità in quanto puoi saltare 0 voci che vengono aggiunte alla fine dell’array di byte, specialmente se copi solo una parte dall’altra buffer di byte.

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

Vedo problemi poiché dovresti utilizzare Array.equals e Array.hashCode, al posto delle implementazioni di array predefinite

Arrays.toString (bytes)

È anche ansible convertire il byte [] in una stringa “sicura” utilizzando Base32 o Base64, ad esempio:

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

ovviamente ci sono molte varianti di quanto sopra, come:

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

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

NOTA: l’ordine nella mappa non è rilevante con questo metodo

 SortedMap testMap = new TreeMap<>(new ArrayComparator()); static class ArrayComparator implements Comparator { @Override public int compare(byte[] byteArray1, byte[] byteArray2) { int result = 0; boolean areEquals = Arrays.equals(byteArray1, byteArray2); if (!areEquals) { result = -1; } return result; } }