拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 在Java中使用字节数组作为hashmap键

在Java中使用字节数组作为hashmap键

白鹭 - 2021-11-24 476 0 0

1.简介

在本教程中,我们将学习如何在HashMap使用字节数组作为键。由于HashMap工作方式,不幸的是,我们无法直接做到这一点。我们将调查为什么会这样,并探讨解决该问题的几种方法。

2.为HashMap设计一个好的Key

2.1。 HashMap如何工作

HashMap使用散列机制从自身存储和检索值。当我们调用put(key, value)方法时, HashMap根据键的hashCode()方法计算哈希码。此哈希用于标识最终将值存储在其中的存储桶:

public V put(K key, V value) {

 if (key == null)

 return putForNullKey(value);

 int hash = hash(key.hashCode());

 int i = indexFor(hash, table.length);

 for (Entry e = table[i]; e != null; e = e.next) {

 Object k;

 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

 V oldValue = e.value;

 e.value = value;

 e.recordAccess(this);

 return oldValue;

 }

 }



 modCount++;

 addEntry(hash, key, value, i);

 return null;

 }

当我们使用get(key)方法检索值时,将涉及类似的过程。该密钥用于计算哈希码,然后找到存储桶。然后,使用equals()方法检查存储桶中的每个条目是否相等。最后,返回匹配条目的值:

public V get(Object key) {

 if (key == null)

 return getForNullKey();

 int hash = hash(key.hashCode());

 for (Entry e = table[indexFor(hash, table.length)]; e != null; e = e.next) {

 Object k;

 if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

 return e.value;

 }

 return null;

 }

2.2。 equals ()和hashCode ()之间的契约

equalshashCode方法都具有应遵守的约定。在HashMaps的上下文中,一方面特别重要:彼此相等的对象必须返回相同的hashCode 。但是,返回相同hashCode对像不必彼此相等。这就是为什么我们可以将多个值存储在一个存储桶中的原因。

2.3。不变性

HashMap中的键的hashCode不应更改。尽管它不是强制性的,但强烈建议键是不可变的。如果对象是不可变的,则无论hashCode方法的实现如何,其hashCode都没有机会进行更改。

默认情况下,哈希是根据对象的所有字段计算的。如果我们想要一个可变键,则需要重写hashCode方法,以确保在其计算中不使用可变字段。为了维持合同,我们还需要更改equals方法。

2.4。有意义的equal

为了能够从地图成功检索值,相等性必须有意义。在大多数情况下,我们需要能够创建一个新的键对象,该对象将等于地图中的某些现有键。因此,对象标识在这种情况下不是很有用。

这也是使用原始字节数组并不是真正的选择的主要原因。 Java中的数组使用对象标识来确定相等性。如果我们使用字节数组作为键来创建HashMap ,我们将只能使用完全相同的数组对象来检索值。

让我们创建一个简单的实现,将字节数组作为键:

byte[] key1 = {1, 2, 3};

 byte[] key2 = {1, 2, 3};

 Map<byte[], String> map = new HashMap<>();

 map.put(key1, "value1");

 map.put(key2, "value2");

我们不仅有两个条目具有几乎相同的键,而且我们不能使用新创建的具有相同值的数组来检索任何内容:

String retrievedValue1 = map.get(key1);

 String retrievedValue2 = map.get(key2);

 String retrievedValue3 = map.get(new byte[]{1, 2, 3});



 assertThat(retrievedValue1).isEqualTo("value1");

 assertThat(retrievedValue2).isEqualTo("value2");

 assertThat(retrievedValue3).isNull();

3.使用现有容器

代替字节数组,我们可以使用现有的类,其相等性实现是基于内容而不是对象标识的。

3.1。 String

String相等性基于字符数组的内容:

public boolean equals(Object anObject) {

 if (this == anObject) {

 return true;

 }

 if (anObject instanceof String) {

 String anotherString = (String)anObject;

 int n = count;

 if (n == anotherString.count) {

 char v1[] = value;

 char v2[] = anotherString.value;

 int i = offset;

 int j = anotherString.offset;

 while (n-- != 0) {

 if (v1[i++] != v2[j++])

 return false;

 }

 return true;

 }

 }

 return false;

 }

String也是不可变的,并且基于字节数组创建String非常简单。我们可以使用Base64方案轻松地对String进行编码和解码:

String key1 = Base64.getEncoder().encodeToString(new byte[]{1, 2, 3});

 String key2 = Base64.getEncoder().encodeToString(new byte[]{1, 2, 3});

现在,我们可以创建一个以String为键而不是字节数组的HashMap 。我们将以类似于上一个示例的方式将值放入Map中:

Map<String, String> map = new HashMap<>();

 map.put(key1, "value1");

 map.put(key2, "value2");

然后,我们可以从地图中检索一个值。对于这两个键,我们将获得相同的第二个值。我们还可以检查两个键是否真正相等:

String retrievedValue1 = map.get(key1);

 String retrievedValue2 = map.get(key2);



 assertThat(key1).isEqualTo(key2);

 assertThat(retrievedValue1).isEqualTo("value2");

 assertThat(retrievedValue2).isEqualTo("value2");

3.2。Lists

String相似, List#equals方法将检查其每个元素的相等性。如果这些元素具有明智的equals()方法并且是不可变的,则List将作为HashMap键正确工作。我们只需要确保我们使用的是不可变的List实现

List<Byte> key1 = ImmutableList.of((byte)1, (byte)2, (byte)3);

 List<Byte> key2 = ImmutableList.of((byte)1, (byte)2, (byte)3);

 Map<List<Byte>, String> map = new HashMap<>();

 map.put(key1, "value1");

 map.put(key2, "value2");



 assertThat(map.get(key1)).isEqualTo(map.get(key2));

请注意, Byte对象List将比byte基元数组占用更多的内存。因此,该解决方案虽然方便,但在大多数情况下并不可行。

4.实施自定义容器

我们还可以实现自己的包装器,以完全控制哈希码的计算和相等性。这样,我们可以确保解决方案是快速的,并且不会占用大量内存。

让我们创建一个带有最后一个私有byte数组字段的类。它没有二传手,它的getter会进行防御性复制以确保完全不变:

public final class BytesKey {

 private final byte[] array;



 public BytesKey(byte[] array) {

 this.array = array;

 }



 public byte[] getArray() {

 return array.clone();

 }

 }

我们还需要实现自己的equalshashCode方法。幸运的是,我们可以将Arrays实用程序类用于以下两个任务:

@Override

 public boolean equals(Object o) {

 if (this == o) return true;

 if (o == null || getClass() != o.getClass()) return false;

 BytesKey bytesKey = (BytesKey) o;

 return Arrays.equals(array, bytesKey.array);

 }



 @Override

 public int hashCode() {

 return Arrays.hashCode(array);

 }

最后,我们可以将包装器用作HashMap的键:

BytesKey key1 = new BytesKey(new byte[]{1, 2, 3});

 BytesKey key2 = new BytesKey(new byte[]{1, 2, 3});

 Map<BytesKey, String> map = new HashMap<>();

 map.put(key1, "value1");

 map.put(key2, "value2");

然后,我们可以使用已声明的键之一检索第二个值,也可以使用动态创建的键:

String retrievedValue1 = map.get(key1);

 String retrievedValue2 = map.get(key2);

 String retrievedValue3 = map.get(new BytesKey(new byte[]{1, 2, 3}));



 assertThat(retrievedValue1).isEqualTo("value2");

 assertThat(retrievedValue2).isEqualTo("value2");

 assertThat(retrievedValue3).isEqualTo("value2");

5.结论

在本教程中,我们研究了在HashMap使用byte数组作为键的不同问题和解决方案。首先,我们研究了为什么不能使用数组作为键。然后,我们使用了一些内置容器来缓解该问题,最后实现了自己的包装器。

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *