likes
comments
collection
share

从数组当做map的key引发的思考

作者站长头像
站长
· 阅读数 20

一、前言

先说结论:

  • 数组不能直接当做map的key去使用
  • 也不能使用 String.valueOf(int[] ints)的方法将数组转为String去当做map的key使用

在今天刷力扣题的过程中发现了以下的问题,算是很基础的知识了,但是在日常的代码开发过程中并没有实际的碰到过这种情况, 大概的的代码和运行结果如下所示:

public static void main(String[] args) {
    // 设置两个相同的数组
    int[] ints1 = {1};
    int[] ints2 = {1};

    // 使用 String.valueOf方法获取两个 String对象
    String s = String.valueOf(ints1);
    String t = String.valueOf(ints2);

    // 为 map1赋值
    Map<int[], Integer> map1 = new HashMap<>();
    map1.put(ints1, 1);

    // 为 map2赋值
    Map<String, Integer> map2 = new HashMap<>();
    map2.put(s, 1);

    System.out.println(map1.containsKey(ints2));
    System.out.println("=====分割线=====");
    System.out.println(map2.containsKey(t));
}

从数组当做map的key引发的思考

二、LeetCode 49. 字母异位词分组

题目信息如下所示

从数组当做map的key引发的思考

解题思路

  • 定义返回的 list集合 result
  • 新建一个 Map<String, List<String>>,key为判断单词为字母异位词的标志点,value为字母异位词相同的集合
  • 遍历 List集合获取字符串 str
  • 设置一个 char数组,该数组的下标表示字母ASCII表 - ‘a’之后的数值
  • 将当前字符串转为 char数组并遍历,使字母对应的 array数组下标++
  • 由上面的过程可知,如果这个单词的字母组成相同,那么他的 array数组肯定也相同
  • 所以我们可将数组当做 map的 key
  • 然后判断当前 key在map中有没有
  • 没有就新增,value为当前字符串
  • 有则取出 value,把当前字符串加入 value
  • 最后遍历 map,将 value加入到最初创建的返回集合 result中
  • 返回

大家可以看到,在上面的解题思路中,有一步我进行了加粗,没错,就是那里出现了问题,代码实现及测试结果如下图所示:

从数组当做map的key引发的思考

从数组当做map的key引发的思考

问题分析

由上面的测试结果可以看出,出参肯定是错误的, eat和 tea肯定是字母异位词, 测试案例也是 LeetCode题中的示例1, 大家也可以对照一下

那么问题来了,为什么我们的 array数据结构都一致,但是在 map.put时计算出来的hash不一致呢,于是我打了个断点到 HashMap的源码里面看到了以下的情况

从数组当做map的key引发的思考

我们注意看这个 key,他是一个内存地址指向啊,下图是当 map的 key为 String的情况下,key就是我们设置的值

从数组当做map的key引发的思考

这个时候我猜你也想到原因了,数组的 toString方法返回的就是当前对象的内存地址,那么每一次我们遍历当前字符串的时候都会对 array数组进行一次 new对象的操作,所以,我们每一次操作的都不是同一个对象,这也导致了 key的不一致

从数组当做map的key引发的思考

为了验证我们的想法,我对齐进行了验证,在不改变原有对象的情况下去改变数组的值,发现他们的哈希值是一样的,这也验证了我们的 Map.put方法中, key哈希值的计算是依赖于对象的 toString方法的

从数组当做map的key引发的思考

从数组当做map的key引发的思考

迈入另一个坑

问题找到了,我就想既然数组的 toString方法 java没有为我们重写,惹不起还能躲不起?

我直接把当前数组转换为 String不就好了,使用我们真实的数值当做 key去计算 hash,而不是地址,于是我买入了第二个坑, 我使用了 String.value()方法

代码如下图所示, 代码在红框位置进行了相应的更改

从数组当做map的key引发的思考

可是我运行代码之后发现,问题并没有解决

从数组当做map的key引发的思考

有了之前的分析,这次我们可以确定还是 内存地址的问题,但是我点了以下 String.value()方法之后,发现不是所有的数组的 toString方法都没有进行重写

从数组当做map的key引发的思考

于是我对其进行了验证, 可以看到,我们将字符串转化为 char数组之后,它打印出来的就是当前字符串,说明了 java内部是对 char数组的 toString方法进行了重写的

从数组当做map的key引发的思考

解决该问题

那么我又想除了 String.value()方法之外还有什么方法能让我当前的数组转换为 String对象,java.util包下的Arrays这个工具类映入我的脑海

在这个工具类下有一个方法 Arrays.toString()方法,是专门用来对数组进行 toString格式转换的,我们修改我们的代码,发现问题解决了

从数组当做map的key引发的思考

代码展示

    public static List<List<String>> groupAnagrams(String[] strs) {
        // 定义返回值
        List<List<String>> result = new ArrayList<>();
        // 存储相同字母的单词
        Map<String, List<String>> map = new HashMap<>();
        // 遍历 strs
        for (String str : strs) {
            int[] array = new int[26];
            for (char c : str.toCharArray()) {
                array[c - 'a']++;
            }
            List<String> strings;
            String s = Arrays.toString(array);
            if (map.containsKey(s)) {
                strings = map.get(s);
            }else{
                strings = new ArrayList<>();
            }
            strings.add(str);
            map.put(s, strings);
        }
        for (Map.Entry<String, List<String>> listEntry : map.entrySet()) {
            result.add(listEntry.getValue());
        }
        return result;
    }

LeetCode提交记录

讲实话,不推荐这种方式,因为我们可以在 str.toCharArray()的时候对 数组进行排序, 然后再转回字符串,当做 key存储到 map中,但是我第一想法就是使用文章介绍的方式做,大佬们勿喷 从数组当做map的key引发的思考

三、总结

  • 测试了几种数组,发现只有char数组是重写了 toString方法的, 测试结果可见下图
  • String.valueOf()方法除了传递 char[] 之外, 其他的数组返回值都是内存地址
  • Arrays工具类一定要常用,尽量保持熟悉,里面有很多使用的方法, 例如: toString()方法、 equals()方法和 sort方法
  • LeetCode 49题建议使用 Arrays.sort()方法对字符串转成的 char[]进行重新排序,测试代码如下图

测试toString

从数组当做map的key引发的思考

测试修改后的代码

从数组当做map的key引发的思考

从数组当做map的key引发的思考

本文内容到此结束了

如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问💬欢迎各位大佬指出。

我是 宁轩 , 我们下次再见

转载自:https://juejin.cn/post/7137726283005296677
评论
请登录