AI 帮我避免了 Map 计算哈希值的一个大坑

2023-05-18 09:34:39 浏览数 (1)

一、背景

一个业务场景需要对 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 :

代码语言:javascript复制
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 帮我们编写,有时候会有意外收获。


创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。

0 人点赞