wake-up-neo.com

Verwenden eines Byte-Arrays als Map-Schlüssel

Haben Sie Probleme mit der Verwendung eines Byte-Arrays als Map-Schlüssel? Ich könnte auch new String(byte[]) und hash von String machen, aber die Verwendung von byte[] ist einfacher.

69
shikhar

Das Problem ist, dass byte[] Objektidentität für equals und hashCode verwendet, so dass 

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

wird in einer HashMap nicht übereinstimmen. Ich sehe drei Möglichkeiten:

  1. Wrapping in einer String, aber dann müssen Sie bei Codierungsproblemen vorsichtig sein (Sie müssen sicherstellen, dass Byte -> String -> Byte dieselben Bytes enthält).
  2. Verwenden Sie List<Byte> (kann im Speicher teuer sein).
  3. Führen Sie Ihre eigene Wrapping-Klasse aus und schreiben Sie hashCode und equals, um den Inhalt des Bytearrays zu verwenden.
56
Kathy Van Stone

Dies ist in Ordnung, solange Sie nur Referenzgleichheit für Ihren Schlüssel wünschen - Arrays implementieren "Wertgleichheit" nicht so, wie Sie es wahrscheinlich möchten. Zum Beispiel:

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

druckt etwas wie:

false
1671711
11394033

(Die tatsächlichen Zahlen sind irrelevant; die Tatsache, dass sie sich unterscheiden, ist wichtig.)

Wenn Sie davon ausgehen, dass Sie eigentlich Gleichheit wünschen, sollten Sie Ihren eigenen Wrapper erstellen, der einen byte[] enthält und die Gleichheits- und Hashcode-Generierung entsprechend implementiert:

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

Wenn Sie die Werte innerhalb des Bytearrays nach der Verwendung der ByteArrayWrapper ändern, haben Sie als Schlüssel in einer HashMap (etc) Probleme, den Schlüssel erneut zu suchen ... Sie könnten eine Kopie der Daten im ByteArrayWrapper-Konstruktor erstellen Wenn Sie möchten, ist dies natürlich eine Verschwendung von Leistung, wenn Sie wissen, dass Sie nicht den Inhalt des Byte-Arrays ändern.

BEARBEITEN: Wie in den Kommentaren erwähnt, können Sie auch ByteBuffer verwenden (insbesondere seine ByteBuffer#wrap(byte[]) -Methode). Ich weiß nicht, ob es wirklich das Richtige ist, angesichts der zusätzlichen Fähigkeiten, die ByteBuffers haben, die Sie nicht benötigen, aber es ist eine Option.

72
Jon Skeet

Wir können dazu ByteBuffer verwenden (dies ist im Grunde der Byte-Wrapper [] mit einem Komparator).

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

wird drucken 

true
39
byte_array

Sie könnten Java.math.BigInteger verwenden. Es hat einen BigInteger(byte[] val)-Konstruktor. Dies ist ein Referenztyp, der als Schlüssel für die Hashtabelle verwendet werden kann. Und .equals() und .hashCode() sind wie für die jeweiligen Integer-Zahlen definiert, was bedeutet, dass BigInteger als Byte [] -Array konsistente Gleichheits-Semantiken hat.

12
Artem Oboturov

Ich bin sehr überrascht, dass die Antworten nicht die einfachste Alternative darstellen.

Ja, es ist nicht möglich, eine HashMap zu verwenden, aber niemand hindert Sie daran, eine SortedMap als Alternative zu verwenden. Die einzige Sache ist, einen Comparator zu schreiben, der die Arrays vergleichen muss. Es ist nicht so performant wie eine HashMap, aber wenn Sie eine einfache Alternative wünschen, können Sie hier gehen (Sie können SortedMap durch Map ersetzen, wenn Sie die Implementierung ausblenden möchten):

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

Diese Implementierung kann für andere Arrays angepasst werden. Das einzige, was Sie beachten müssen, ist, dass gleiche Arrays (= gleiche Länge mit gleichen Mitgliedern) 0 zurückgeben müssen und dass Sie eine bestimmte Reihenfolge haben 

3
Thorsten S.

Ich glaube, dass Arrays in Java die Methoden hashCode() und equals(Object) nicht unbedingt intuitiv implementieren. Das heißt, dass zwei identische Byte-Arrays nicht notwendigerweise denselben Hash-Code verwenden und nicht unbedingt behaupten müssen, gleich zu sein. Ohne diese beiden Eigenschaften verhält sich Ihre HashMap unerwartet.

Daher empfehle ich against , dass byte[] als Schlüssel in einer HashMap verwendet wird.

1
Adam Paynter

Sie sollten eine Klasse wie ByteArrKey erstellen und Hashcode für Überladung und gleichwertige Methoden verwenden. Den Vertrag zwischen ihnen berücksichtigen.

Dies gibt Ihnen mehr Flexibilität, da Sie 0 Einträge überspringen können, die am Ende des Byte-Arrays angehängt werden, insbesondere wenn Sie nur einen Teil des anderen Byte-Puffers kopieren.

Auf diese Weise entscheiden Sie, wie beide Objekte gleich sein sollten.

1
Milind Patil

Sie können das Byte [] auch mithilfe von Base32 oder Base64 in eine "sichere" Zeichenfolge konvertieren. Beispiel:

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

natürlich gibt es viele Varianten der oben genannten, wie: 

String key = org.Apache.commons.codec.binary.Base64.encodeBase64(keyValue);
0
Christof R

Hier ist eine Lösung, die TreeMap, Comparator-Schnittstelle und Java-Methode verwendet. Java.util.Arrays.equals (Byte [], Byte []);

HINWEIS: Die Reihenfolge in der Karte ist bei dieser Methode nicht relevant

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

Ich sehe Probleme, da Sie Arrays.equals und Array.hashCode anstelle von Standard-Array-Implementierungen verwenden sollten

0
dfa

Arrays.toString (Bytes)

0
df.

Wir können auch eine eigene ByteHashMap wie diese erstellen.

ByteHashMap byteMap = new ByteHashMap();
byteMap.put(keybyteArray,valueByteArray);

Hier ist die vollständige Implementierung

public class ByteHashMap implements Map<byte[], byte[]>, Cloneable,
        Serializable {

    private Map<ByteArrayWrapper, byte[]> internalMap = new HashMap<ByteArrayWrapper, byte[]>();

    public void clear() {
        internalMap.clear();
    }

    public boolean containsKey(Object key) {
        if (key instanceof byte[])
            return internalMap.containsKey(new ByteArrayWrapper((byte[]) key));
        return internalMap.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return internalMap.containsValue(value);
    }

    public Set<Java.util.Map.Entry<byte[], byte[]>> entrySet() {
        Iterator<Java.util.Map.Entry<ByteArrayWrapper, byte[]>> iterator = internalMap
                .entrySet().iterator();
        HashSet<Entry<byte[], byte[]>> hashSet = new HashSet<Java.util.Map.Entry<byte[], byte[]>>();
        while (iterator.hasNext()) {
            Entry<ByteArrayWrapper, byte[]> entry = iterator.next();
            hashSet.add(new ByteEntry(entry.getKey().data, entry
                    .getValue()));
        }
        return hashSet;
    }

    public byte[] get(Object key) {
        if (key instanceof byte[])
            return internalMap.get(new ByteArrayWrapper((byte[]) key));
        return internalMap.get(key);
    }

    public boolean isEmpty() {
        return internalMap.isEmpty();
    }

    public Set<byte[]> keySet() {
        Set<byte[]> keySet = new HashSet<byte[]>();
        Iterator<ByteArrayWrapper> iterator = internalMap.keySet().iterator();
        while (iterator.hasNext()) {
            keySet.add(iterator.next().data);
        }
        return keySet;
    }

    public byte[] put(byte[] key, byte[] value) {
        return internalMap.put(new ByteArrayWrapper(key), value);
    }

    @SuppressWarnings("unchecked")
    public void putAll(Map<? extends byte[], ? extends byte[]> m) {
        Iterator<?> iterator = m.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<? extends byte[], ? extends byte[]> next = (Entry<? extends byte[], ? extends byte[]>) iterator
                    .next();
            internalMap.put(new ByteArrayWrapper(next.getKey()), next
                    .getValue());
        }
    }

    public byte[] remove(Object key) {
        if (key instanceof byte[])
            return internalMap.remove(new ByteArrayWrapper((byte[]) key));
        return internalMap.remove(key);
    }

    public int size() {
        return internalMap.size();
    }

    public Collection<byte[]> values() {
        return internalMap.values();
    }

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

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

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

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

    private final class ByteEntry implements Entry<byte[], byte[]> {
        private byte[] value;
        private byte[] key;

        public ByteEntry(byte[] key, byte[] value) {
            this.key = key;
            this.value = value;
        }

        public byte[] getKey() {
            return this.key;
        }

        public byte[] getValue() {
            return this.value;
        }

        public byte[] setValue(byte[] value) {
            this.value = value;
            return value;
        }

    }
}
0