【问题标题】:Can an array be used as a HashMap key?数组可以用作 HashMap 键吗?
【发布时间】:2013-05-26 04:57:16
【问题描述】:

如果HashMap 的键是String[] 数组:

HashMap<String[], String> pathMap;

您可以使用新创建的String[] 数组访问地图,还是必须是相同的String[] 对象?

pathMap = new HashMap<>(new String[]{"korey", "docs"}, "/home/korey/docs");
String path = pathMap.get(new String[]{"korey", "docs"});

【问题讨论】:

    标签: java arrays hashmap key


    【解决方案1】:

    它必须是同一个对象。 HashMap 使用 equals() 比较键,Java 中的两个数组只有当它们是同一个对象时才相等。

    如果您想要值相等,请编写您自己的容器类,该容器类包装 String[] 并为 equals()hashCode() 提供适当的语义。在这种情况下,最好使容器不可变,因为更改对象的哈希码会对基于哈希的容器类造成严重破坏。

    编辑

    正如其他人指出的那样,List&lt;String&gt; 具有您似乎想要的容器对象语义。所以你可以这样做:

    HashMap<List<String>, String> pathMap;
    
    pathMap.put(
        // unmodifiable so key cannot change hash code
        Collections.unmodifiableList(Arrays.asList("korey", "docs")),
        "/home/korey/docs"
    );
    
    // later:
    String dir = pathMap.get(Arrays.asList("korey", "docs"));
    

    【讨论】:

    • 我认为它还使用hashCode() 来确定对象的哈希值。
    • @pvorb - 确实。并且两个数组不太可能具有相同的哈希码。但是,这不是任何 Java 实现的要求。在任何情况下,使用equals() 比较具有相同hashCode() 的两个引用以确定它们是否是相同的键。
    • @TedHopp 我决定使用你的建议来创建一个容器类。谢谢!
    • @KoreyHinton - 不客气。 :) 不过,在此之前,请检查 List 是否满足您的需求。请参阅我编辑的答案。
    • @TedHopp Cool 我刚刚看到您的编辑,感谢您展示了一个工作示例!
    【解决方案2】:

    不,但您可以使用List&lt;String&gt;,它会按您的预期工作!

    【讨论】:

    • 这需要注意。如果您打算使用List&lt;String&gt; 作为基于散列的集合中的键,则该列表应该是不可修改的。如果对象的哈希码在对象被用作基于哈希的集合中的键时发生更改,则该集合通常会中断。
    • List 是一个接口,不能保证实现正确覆盖equals和hashCode
    • @SteveKuo - 是的。 List 的文档要求任何实现都使用 equals()hashCode() 的特定语义。所需的语义与 OP 似乎想要的相匹配。
    • 确实,作为所有列表实现继承自的超类的 AbstractList 类覆盖了 hashCode() 和 equals() 方法,因此反映的是逐元素比较,而不是引用比较。
    【解决方案3】:

    Java 中的数组使用ObjecthashCode() 并且不要覆盖它(与equals()toString() 相同)。所以不,你 cannot 不应该使用数组作为 hashmap 键。

    【讨论】:

    • 可以将它们用作键,它只会使用 Object 对其 hashCode 所做的任何事情...不是他想要的,但没有什么能阻止您这样做。
    【解决方案4】:

    您不能使用纯 Java Array 作为 HashMap 中的键。 (嗯,你可以,但它不会按预期工作。)

    但是您可以编写一个包装类,它具有对 Array 的引用并且还覆盖 hashCode()equals()

    【讨论】:

    • 无需编写新的数组包装类,已经存在一个 - ArrayList
    • @SteveKuo 是的,确实。但也许你想自己写,因为ArrayList 是可变的,并且可以在内部替换下面的数组,而你不会注意到它。
    • @pvorb - 始终可以使用Collections.unmodifiableList(someList)List 转换为不可变对象。
    • @TedHopp: unmodifiableList 不是不可变的。您仍然可以直接修改源列表。
    • @Alex - 是的,你是对的。这在this comment 中对我上面自己的回答指出了这一点。另请参阅my response to that comment
    【解决方案5】:

    Ted Hopp 是对的,它必须是同一个对象。

    有关信息,请参阅此示例:

    public static void main(String[] args) {
        HashMap<String[], String> pathMap;
        pathMap = new HashMap<String[], String>();
        String[] data = new String[]{"korey", "docs"};
        pathMap.put(data, "/home/korey/docs");
        String path = pathMap.get(data);
        System.out.println(path);
    }
    

    当你运行上面的代码时,它会打印“docs”。

    【讨论】:

      【解决方案6】:

      在大多数情况下,如果数组中的字符串不是病态的并且不包含逗号后跟空格,则可以使用 Arrays.toString() 作为唯一键。即您的Map 将是Map&lt;String, T&gt;。数组 myKeys[] 的 get/put 将是

      T t = myMap.get(Arrays.toString(myKeys));
      
      myMap.put(Arrays.toString(myKeys), myT);
      

      显然,如果需要,您可以放入一些包装代码。

      一个不错的副作用是您的密钥现在是不可变的。当然,你改变你的数组 myKeys 然后尝试get(),你不会找到它。

      字符串的散列是高度优化的。所以我的猜测是,这个解决方案虽然感觉有点慢和笨拙,但比使用不可变列表的@Ted Hopp 解决方案更快,内存效率更高(对象分配更少)。想想Arrays.toString() 是否对您的密钥是唯一的。如果没有,或者有任何疑问,(例如 String[] 来自用户输入),请使用 List。

      【讨论】:

        【解决方案7】:

        Java 9 开始,您可以使用Arrays::compare 方法作为TreeMap 的比较器,用于比较数组的内容

        Map<String[], String> map = new TreeMap<>(Arrays::compare);
        
        String[] key1 = {"one", "two"};
        String[] key2 = {"one", "two"};
        String[] key3 = {"one", "two"};
        
        map.put(key1, "value1");
        map.put(key2, "value2");
        
        System.out.println(map.size()); // 1
        System.out.println(map.get(key1)); // value2
        System.out.println(map.get(key2)); // value2
        System.out.println(map.get(key3)); // value2
        

        另见:How to make a Set of arrays in Java?

        【讨论】:

          【解决方案8】:

          就像说你需要一个围绕你的数组的包装类来覆盖相等和 hashCode。

          例如

          /** 
           * We can use this instance as HashKey,
           * the same anagram string will refer the same value in the map.
           */
          class Anagram implements CharSequence {
          
              private final char[] anagram;
          
              public Anagram(String anagram) {
          
                  this.anagram = anagram.toCharArray();
                  Arrays.sort(this.anagram);
              }
          
              @Override
              public boolean equals(Object o) {
          
                  if (this == o) {
                      return true;
                  }
                  if (o == null || getClass() != o.getClass()) {
                      return false;
                  }
                  Anagram that = (Anagram) o;
                  return Arrays.equals(this.anagram, that.anagram);
              }
          
              @Override
              public int hashCode() {
          
                  return Arrays.hashCode(this.anagram);
              }
          
              @Override
              public int length() {
          
                  return anagram.length;
              }
          
              @Override
              public char charAt(int index) {
          
                  return anagram[index];
              }
          
              @Override
              public CharSequence subSequence(int start, int end) {
          
                  return new String(anagram).subSequence(start, end);
              }
          
              @Override
              public String toString() {
          
                  return Arrays.toString(anagram);
              }
          }
          

          否则将您的地图声明为IdentityHashMap,然后用户知道我们需要为您的 CRUD 使用相同的实例。

          【讨论】:

            【解决方案9】:

            使用Arrays 实用程序及其提供的哈希码的运行示例:

            String[] key1 = { "korey", "docs" };
            String value1 = "/home/korey/docs";
            HashMap<Integer, String> map = new HashMap<Integer, String>();
            map.put(Arrays.hashCode(key1), value1);
            System.out.println(map);
            

            {-1122550406=/home/korey/docs}

            如果您只关注存储,这种方法很有用。使用可读(原始)密钥检索很简单:

            String retrievedValue = map.get(Arrays.hashCode(key1));
            System.out.println(retrievedValue);
            

            /home/korey/docs

            【讨论】:

            • 这种方式的问题是,当Arrays.hashCode()下2个string[]的hashCode相同时,无法处理hash冲突。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-06-16
            • 2013-03-12
            • 2020-03-30
            • 2011-03-31
            • 1970-01-01
            • 2015-03-05
            相关资源
            最近更新 更多