web-dev-qa-db-ja.com

Mapキーとしてバイト配列を使用する

Mapキーとしてバイト配列を使用することに問題はありますか? new String(byte[])Stringによるハッシュもできますが、byte[]を使用する方が簡単です。

71
shikhar

問題はそれです byte[]equalsおよびhashCodeにオブジェクトIDを使用するため、

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

HashMapでは一致しません。次の3つのオプションがあります。

  1. Stringでラップしますが、エンコードの問題には注意する必要があります(バイト->文字列->バイトが同じバイトを与えることを確認する必要があります)。
  2. つかいます List<Byte>(メモリが高価になる可能性があります)。
  3. 独自のラッピングクラスを作成し、hashCodeおよびequalsを記述して、バイト配列の内容を使用します。
57
Kathy Van Stone

キーの参照の等価性のみが必要な場合は問題ありません。配列は、おそらく必要な方法で「値の等価性」を実装しません。例えば:

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

次のようなものを印刷します。

_false
1671711
11394033
_

(実際の数値は無関係です。数値が異なるという事実は重要です。)

あなたが実際に平等を望んでいると仮定すると、_byte[]_を含み、平等とハッシュコード生成を適切に実装する独自のラッパーを作成することをお勧めします:

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

ByteArrayWrapper(など)のキーとしてHashMapを使用した後にバイト配列内の値を変更すると、キーを再度検索する際に問題が発生することに注意してください...必要に応じてByteArrayWrapperコンストラクタのデータのコピーを取得しますが、wo n'tバイト配列の内容を変更している。

編集:コメントで述べたように、これにByteBufferを使用することもできます(特に、 ByteBuffer#wrap(byte[]) メソッド)。 ByteBuffersに必要のない追加の能力がすべて与えられているので、それが本当に正しいかどうかはわかりませんが、これはオプションです。

72
Jon Skeet

このためにByteBufferを使用できます(これは基本的にコンパレーターを備えたbyte []ラッパーです)

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

印刷します

true
41
byte_array

_Java.math.BigInteger_を使用できます。 BigInteger(byte[] val)コンストラクターがあります。これは参照型なので、ハッシュテーブルのキーとして使用できます。また、.equals().hashCode()はそれぞれの整数値として定義されています。つまり、BigIntegerにはbyte []配列と同じ一貫したセマンティクスがあります。

13
Artem Oboturov

私は、答えが最も単純な代替案を指摘していないことに非常に驚いています。

はい、HashMapを使用することはできませんが、代わりにSortedMapを使用することを妨げるものはいません。唯一のものは、配列を比較する必要があるコンパレータを書くことです。 HashMapほど高性能ではありませんが、簡単な代替手段が必要な場合は、ここに進みます(実装を非表示にする場合は、SortedMapをMapに置き換えることができます)。

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

この実装は、他の配列に合わせて調整できます。注意する必要があるのは、等しい配列(=等しいメンバーで等しい長さ)が0を返さなければならず、決定的な順序があることです

4
Thorsten S.

Javaの配列は必ずしもhashCode()およびequals(Object)メソッドを直感的に実装するとは限りません。つまり、2つの同一のバイト配列は必ずしも共有しませんこれらの2つの特性がないと、HashMapは予期しない動作をします。

したがって、againstを使用してbyte[]はHashMapのキーとして。

1
Adam Paynter

ByteArrKeyのようなクラスを作成し、ハッシュコードと同等のメソッドをオーバーロードする必要があります。それらの間のコントラクトを覚えておいてください。

これにより、特にバイト配列の一部のみをコピーする場合に、バイト配列の最後に追加される0エントリをスキップできるため、柔軟性が向上します。

この方法で、両方のオブジェクトを等しくする必要がある方法を決定します。

1
Milind Patil

たとえば、Base32またはBase64を使用してbyte []を「安全な」文字列に変換することもできます。次に例を示します。

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

もちろん、上記には多くのバリエーションがあります。

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

TreeMap、Comparatorインターフェイス、およびJavaメソッドJava.util.Arrays.equals(byte []、byte []);

注:マップ内の順序は、この方法とは関係ありません

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

デフォルトの配列実装の代わりにArrays.equalsとArray.hashCodeを使用する必要があるため、問題が発生します。

0
dfa

Arrays.toString(bytes)

0
df.

また、このような独自のカスタムByteHashMapを作成できます。

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

完全な実装は次のとおりです

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