使用字节数组作为Map键

shi*_*har 72 java bytearray hashmap

你看到使用字节数组作为Map键有什么问题吗?我也可以做new String(byte[])和哈希,String但它更直接使用byte[].

Jon*_*eet 72

没关系,只要你只需要你的密钥的引用相等 - 数组不会以你可能想要的方式实现"值相等".例如:

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());
Run Code Online (Sandbox Code Playgroud)

打印像:

false
1671711
11394033
Run Code Online (Sandbox Code Playgroud)

(实际数字无关紧要;它们不同的事实很重要.)

假设你真的想要相等,我建议你创建自己的包装器,它包含一个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);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果在使用之后更改字节数组中的值ByteArrayWrapper,作为HashMap(等)中的键,您将无法再次查找该键... ByteArrayWrapper如果您需要,可以在构造函数中获取数据的副本,但是如果您知道不会更改字节数组的内容,那么这显然会浪费性能.

编辑:正如评论中所提到的,你也可以使用ByteBuffer它(特别是它的ByteBuffer#wrap(byte[])方法).考虑到ByteBuffer你不需要的所有额外能力,我不知道它是否真的是正确的,但这是一个选择.

  • 您可以添加到包装器实现中的其他一些事项:1.在构造上获取byte []的副本,从而保证对象是不可变的,这意味着您的密钥的哈希代码将随着时间的推移而变化.2.预先计算并存储一次哈希码(假设速度比存储开销更重要). (4认同)
  • @Adamski:我在答案的结尾提到了复制的可能性。在某些情况下,这是正确的做法,但在其他情况下则不然。我可能希望使其成为一个选项(可能是静态方法,而不是构造函数-copyOf和wrapperAround)。请注意,*无需*复制,您可以更改基础数组,直到您首先获取哈希并检查是否相等为止,这在某些情况下可能很有用。 (2认同)
  • 只是想指出,java.nio.ByteBuffer类实际上完成了包装程序的所有工作,尽管有同样的警告,只有在字节数组的内容不变的情况下才应使用它。您可能需要修改答案以提及它。 (2认同)

Kat*_*one 60

问题是byte[]使用对象标识equalshashCode,所以

byte[] b1 = {1, 2, 3}
byte[] b2 = {1, 2, 3}
Run Code Online (Sandbox Code Playgroud)

不会匹配的HashMap.我看到三个选择:

  1. 包装在a中String,但是你必须要小心编码问题(你需要确保字节 - > String - > byte给你相同的字节).
  2. 使用List<Byte>(在内存中可能很昂贵).
  3. 做你自己的包装类,编写hashCodeequals使用字节数组的内容.

  • 我通过使用十六进制编码解决了字符串包装问题.您也可以使用base64编码. (3认同)

byt*_*ray 41

我们可以使用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)));
Run Code Online (Sandbox Code Playgroud)

将打印

true
Run Code Online (Sandbox Code Playgroud)

  • 这与ByteBuffer.wrap()一起工作正常,但是如果使用几个put()调用创建ByteBuffer的内容来创建复合键字节数组,则要小心.在这种情况下,最后一个put()调用必须后跟一个rewind()调用 - 否则即使底层字节数组包含不同的数据,equals()也会返回true. (4认同)
  • 大多数轻量级字节数组包装器的+1(我认为...) (2认同)

Art*_*rov 12

你可以用java.math.BigInteger.它有一个BigInteger(byte[] val)构造函数.它是一个引用类型,因此可以用作哈希表的键.和.equals().hashCode()被定义为用于各个整数,这意味着具有BigInteger的一致等于语义byte []数组.

  • 听起来很有吸引力,但这是错误的,因为只有前导零元素(例如,`{0,100}`和`{100}`)不同的两个数组会给出相同的BigInteger (14认同)

Tho*_* S. 5

我很惊讶答案没有指出最简单的选择。

是的,不可能使用 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;
    }
  }
Run Code Online (Sandbox Code Playgroud)

此实现可以针对其他数组进行调整,您唯一必须注意的是相等的数组(= 相等的长度和相等的成员)必须返回 0 并且您具有确定性顺序