数组可以用作 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<String>
具有您似乎想要的容器对象语义。所以你可以这样做:
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<String>
,它会按您的预期工作!
【讨论】:
这需要注意。如果您打算使用List<String>
作为基于散列的集合中的键,则该列表应该是不可修改的。如果对象的哈希码在对象被用作基于哈希的集合中的键时发生更改,则该集合通常会中断。
List
是一个接口,不能保证实现正确覆盖equals和hashCode
@SteveKuo - 是的。 List
的文档要求任何实现都使用 equals()
和 hashCode()
的特定语义。所需的语义与 OP 似乎想要的相匹配。
确实,作为所有列表实现继承自的超类的 AbstractList 类覆盖了 hashCode() 和 equals() 方法,因此反映的是逐元素比较,而不是引用比较。
【参考方案3】:
Java 中的数组使用Object
的hashCode()
并且不要覆盖它(与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<String, T>
。数组 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的区别