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

一、前言

先说结论:

  • 数组不能直接当做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 map1 = new HashMap<>();
    map1.put(ints1, 1);

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

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

从数组当做map的key引发的思考_第1张图片

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

题目信息如下所示

从数组当做map的key引发的思考_第2张图片

解题思路

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

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

从数组当做map的key引发的思考_第3张图片

从数组当做map的key引发的思考_第4张图片

问题分析

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

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

从数组当做map的key引发的思考_第5张图片

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

从数组当做map的key引发的思考_第6张图片

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

从数组当做map的key引发的思考_第7张图片

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

从数组当做map的key引发的思考_第8张图片

从数组当做map的key引发的思考_第9张图片

迈入另一个坑

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

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

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

从数组当做map的key引发的思考_第10张图片

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

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

从数组当做map的key引发的思考_第11张图片

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

从数组当做map的key引发的思考_第12张图片

解决该问题

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

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

从数组当做map的key引发的思考_第13张图片

代码展示

    public static List> groupAnagrams(String[] strs) {
        // 定义返回值
        List> result = new ArrayList<>();
        // 存储相同字母的单词
        Map> map = new HashMap<>();
        // 遍历 strs
        for (String str : strs) {
            int[] array = new int[26];
            for (char c : str.toCharArray()) {
                array[c - 'a']++;
            }
            List 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> listEntry : map.entrySet()) {
            result.add(listEntry.getValue());
        }
        return result;
    }

LeetCode提交记录

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

从数组当做map的key引发的思考_第14张图片

三、总结

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

测试toString

从数组当做map的key引发的思考_第15张图片

测试修改后的代码

从数组当做map的key引发的思考_第16张图片

从数组当做map的key引发的思考_第17张图片

你可能感兴趣的:(leetcode,java,算法)