数组可以用作 HashMap 键吗?

Posted

技术标签:

【中文标题】数组可以用作 HashMap 键吗?【英文标题】:Can an array be used as a HashMap key? 【发布时间】: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");

【问题讨论】:

【参考方案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冲突。

以上是关于数组可以用作 HashMap 键吗?的主要内容,如果未能解决你的问题,请参考以下文章

我可以使用实例化的对象作为数组键吗?

hashmap和hashtable,arraylist和vector的区别

hashmap中的value变量可以是数组吗?想要value中有两个变量。

HashMap

HashMap(数组+链表+红黑树)

HashMap的理解