一、背景
一个业务场景需要对 Map 计算哈希值作为缓存 key 的构成部分。思路是将 Map 转为字符串,然后对字符串取 Hash 值。 不过这里有个很大的坑,即 Map 中 Entry 的顺序问题,即仅仅 Key 的顺序不同而值相同时哈希值应该相同。 如果使用 AI 大概率会注意到这个问题,如果直自己写很容易忽略这个问题。
二、问题复现
构造两个“键值”等价的 Map,发现其 hash 值不同。
代码语言:javascript复制import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
Map<String, String> map1 = new LinkedHashMap<>();
map1.put("key1", "value1");
map1.put("key2", "value2");
Map<String, String> map2 = new LinkedHashMap<>();
map2.put("key2", "value2");
map2.put("key1", "value1");
String mapString1 = map1.entrySet()
.stream()
.map(e -> e.getKey() "=" e.getValue())
.collect(Collectors.joining(", "));
String mapString2 = map2.entrySet()
.stream()
.map(e -> e.getKey() "=" e.getValue())
.collect(Collectors.joining(", "));
String hash1 = Hashing.sha256()
.hashString(mapString1, Charsets.UTF_8)
.toString();
String hash2 = Hashing.sha256()
.hashString(mapString2, Charsets.UTF_8)
.toString();
System.out.println("The hash of the first map is: " hash1);
System.out.println("The hash of the second map is: " hash2);
System.out.println(hash1.equals(hash2));
}
}
输出的结果:
The hash of the first map is: 500ccf8d28b6857c91a01a1e6d751e89d3872af97a8079cecd1dce0497ab084d The hash of the second map is: d7d67e36f33ebf3f51e1d0c6e016a17a2d839e897951bb98fa5cd190a135b331 false
有些同学可能会说,你这里用到的是 LinkedHashMap
当然会有问题。
但问题在于,通常我们定义接口时,参数为 Map 类型,两个“键值”等价,其 Entry 的顺序未必相等。
三、解决办法
对 Entry 或者 map 后的字符串排序,保证“键值”等价的两个 Map 在 hash 前拼接出的字符串相同。
代码语言:javascript复制import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class MapHashDemo {
public static void main(String[] args) {
Map<String, String> map1 = new LinkedHashMap<>();
map1.put("key1", "value1");
map1.put("key2", "value2");
Map<String, String> map2 = new LinkedHashMap<>();
map2.put("key2", "value2");
map2.put("key1", "value1");
String hash1 = getSha256(map1);
String hash2 = getSha256(map2);
System.out.println("The hash of the first map is: " hash1);
System.out.println("The hash of the second map is: " hash2);
System.out.println(hash1.equals(hash2));
}
private static String getSha256(Map<String, String> map) {
String mapString1 = map.entrySet()
.stream()
.map(e -> e.getKey() "=" e.getValue())
.sorted()
.collect(Collectors.joining(", "));
return Hashing.sha256()
.hashString(mapString1, Charsets.UTF_8)
.toString();
}
}
The hash of the first map is: 500ccf8d28b6857c91a01a1e6d751e89d3872af97a8079cecd1dce0497ab084d The hash of the second map is: 500ccf8d28b6857c91a01a1e6d751e89d3872af97a8079cecd1dce0497ab084d true
这样哪怕 Map 中的 Entry 顺序不一致,计算出来的 sha256 的哈希值也相同。
三、AI 助力
另外,再次倡议大家日常工作和学习中可以尝试使用 ChatGPT 、 New Bing 这类 AI 工具,这类简单的代码可以让它们给出 Demo,它们给出的 Demo 时有很大概率可以给出正确的代码。
如何使用 com.google.common.hash.Hashing 计算一个 Map 的 hash 值?
ChatGPT 给出的答案:
com.google.common.hash.Hashing
是 Google Guava库中的一个类,它提供了一系列的哈希函数。
计算一个Map的哈希值,我们可以先将Map的key和value转化为字符串,然后使用Hashing的哈希函数进行计算。
以下是一个简单的例子,假设我们有一个 Map :
import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
String mapString = map.entrySet()
.stream()
.map(e -> e.getKey() "=" e.getValue())
.sorted()
.collect(Collectors.joining(", "));
String hash = Hashing.sha256()
.hashString(mapString, Charsets.UTF_8)
.toString();
System.out.println("The hash of the map is: " hash);
}
}
这个例子中,我们首先将Map的每一项(key和value)转化为一个字符串,格式为"key=value",然后将这些字符串排序并用逗号连接起来,生成一个总的字符串。最后,我们用Guava的Hashing.sha256()函数计算这个字符串的哈希值。
**需要注意的是,由于 Map 的元素没有固定的顺序,所以在生成字符串的时候我们需要对其进行排序,以保证相同的 Ma p能够得到相同的哈希值。**同时,使用哈希函数时,需要选择适当的字符集,这里我们选择了UTF-8。
此外,还要注意哈希冲突的问题,尽管对于大多数情况来说,哈希冲突的概率非常小,但理论上还是存在可能的。所以在使用哈希值时,需要根据具体情况判断是否可以接受这种潜在的冲突风险。
四、总结
大家在对 Map 计算其哈希值作为缓存 key 时,需要特别注意这个问题。 建议大家在日常开发中一些简单的代码示例,一些验证性的代码,推荐大家优先让 AI 帮我们编写,有时候会有意外收获。
创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。