我想把一组对象存储在哈希映射表中,其中键(key)应该是由两个字符串值组成的复合键(composite key)。有没有方法可以实现这一点?
我可以简单地将这两个字符串连接起来,但我相信有更好的方法来实现这一点。
我想把一组对象存储在哈希映射表中,其中键(key)应该是由两个字符串值组成的复合键(composite key)。有没有方法可以实现这一点?
我可以简单地将这两个字符串连接起来,但我相信有更好的方法来实现这一点。
你可以创建一个自定义对象,其中包含这两个字符串:
class StringKey {
private String str1;
private String str2;
}
问题是,您需要确定两个这样的对象的相等测试和哈希码。
相等性可以匹配两个字符串,并且哈希码可以是连接成员的哈希码(这是有争议的):
class StringKey {
private String str1;
private String str2;
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof StringKey) {
StringKey s = (StringKey)obj;
return str1.equals(s.str1) && str2.equals(s.str2);
}
return false;
}
@Override
public int hashCode() {
return (str1 + str2).hashCode();
}
}
您无需重复发明轮子。只需使用Guava的HashBasedTable<R,C,V>
实现Table<R,C,V>
接口,满足您的需求。以下是一个示例:
Table<String, String, Integer> table = HashBasedTable.create();
table.put("key-1", "lock-1", 50);
table.put("lock-1", "key-1", 100);
System.out.println(table.get("key-1", "lock-1")); //prints 50
System.out.println(table.get("lock-1", "key-1")); //prints 100
table.put("key-1", "lock-1", 150); //replaces 50 with 150
public int hashCode() {
return (str1 + str2).hashCode();
}
这似乎是一种可怕的生成hashCode的方式:每次计算hashCode时创建一个新的字符串实例是糟糕的!(即使只生成字符串实例一次并缓存结果也是不好的做法。)public int hashCode() {
final int prime = 31;
int result = 1;
for ( String s : strings ) {
result = result * prime + s.hashCode();
}
return result;
}
对于一对字符串,变成:
return string1.hashCode() * 31 + string2.hashCode();
这是一个非常基础的实现。链接中有很多建议,可以提供更好的优化策略。
*
和+
计算,是否会有任何区别,因为String hashCode在哈希码中使用了大多数位? - SOFePair
对象,其中包含两个字符串作为成员,然后将其用作键?例如:public class Pair {
private final String str1;
private final String str2;
// this object should be immutable to reliably perform subsequent lookups
}
String
是键的好选择)。您是正确的,串联字符串并不理想。对于某些情况,它可能有效,但通常是不可靠和脆弱的解决方案(例如,AB/C 与 A/BC 是不同的键吗?)。MyObject getMyObject(String key1, String key2) {
String cacheKey = key1 + "~" + key2;
return map.get(cachekey);
}
这很简单,但它有效。
我发现许多人使用嵌套映射。也就是说,将Key1 -> Key2 -> Value
(我使用计算机科学/即 Haskell 的柯里化符号来表示(Key1 x Key2) -> Value
映射,它有两个参数并产生一个值),您首先提供第一个键——这会返回一个(部分)映射 Key2 -> Value
,然后在下一步中展开。
例如,
Map<File, Map<Integer, String>> table = new HashMap(); // maps (File, Int) -> Distance
add(k1, k2, value) {
table2 = table1.get(k1);
if (table2 == null) table2 = table1.add(k1, new HashMap())
table2.add(k2, value)
}
get(k1, k2) {
table2 = table1.get(k1);
return table2.get(k2)
}
阅读关于意大利面条/仙人掌堆栈的文章,我想到了一种变体,可以用于此目的,包括将键映射为任意顺序的可能性,以便 map.lookup("a","b") 和 map.lookup("b","a") 返回相同的元素。它还适用于任意数量的键,而不仅仅是两个。
我将其用作数据流编程实验的堆栈,但这是一个快速而脏乱的版本,可以用作多键映射(应该改进:应该使用集合而不是数组来避免查找重复出现的键)。
public class MultiKeyMap <K,E> {
class Mapping {
E element;
int numKeys;
public Mapping(E element,int numKeys){
this.element = element;
this.numKeys = numKeys;
}
}
class KeySlot{
Mapping parent;
public KeySlot(Mapping mapping) {
parent = mapping;
}
}
class KeySlotList extends LinkedList<KeySlot>{}
class MultiMap extends HashMap<K,KeySlotList>{}
class MappingTrackMap extends HashMap<Mapping,Integer>{}
MultiMap map = new MultiMap();
public void put(E element, K ...keys){
Mapping mapping = new Mapping(element,keys.length);
for(int i=0;i<keys.length;i++){
KeySlot k = new KeySlot(mapping);
KeySlotList l = map.get(keys[i]);
if(l==null){
l = new KeySlotList();
map.put(keys[i], l);
}
l.add(k);
}
}
public E lookup(K ...keys){
MappingTrackMap tmp = new MappingTrackMap();
for(K key:keys){
KeySlotList l = map.get(key);
if(l==null)return null;
for(KeySlot keySlot:l){
Mapping parent = keySlot.parent;
Integer count = tmp.get(parent);
if(parent.numKeys!=keys.length)continue;
if(count == null){
count = parent.numKeys-1;
}else{
count--;
}
if(count == 0){
return parent.element;
}else{
tmp.put(parent, count);
}
}
}
return null;
}
public static void main(String[] args) {
MultiKeyMap<String,String> m = new MultiKeyMap<String,String>();
m.put("brazil", "yellow", "green");
m.put("canada", "red", "white");
m.put("USA", "red" ,"white" ,"blue");
m.put("argentina", "white","blue");
System.out.println(m.lookup("red","white")); // canada
System.out.println(m.lookup("white","red")); // canada
System.out.println(m.lookup("white","red","blue")); // USA
}
}
public static String fakeMapKey(final String... arrayKey) {
String[] keys = arrayKey;
if (keys == null || keys.length == 0)
return null;
if (keys.length == 1)
return keys[0];
String key = "";
for (int i = 0; i < keys.length; i++)
key += "{" + i + "}" + (i == keys.length - 1 ? "" : "{" + keys.length + "}");
keys = Arrays.copyOf(keys, keys.length + 1);
keys[keys.length - 1] = FAKE_KEY_SEPARATOR;
return MessageFormat.format(key, (Object[]) keys);}
public static string FAKE_KEY_SEPARATOR = "~";
输入: fakeMapKey("keyPart1","keyPart2","keyPart3");
输出:keyPart1~keyPart2~keyPart3
我想提到两个选项,我认为其他答案中没有涉及。它们是否适合您的目的,您必须自己决定。
您可以使用映射的映射,将字符串1用作外部映射中的键,将字符串2用作每个内部映射中的键。
我不认为这是一个非常好的语法解决方案,但它很简单,我在一些地方看到过它的使用。它也应该在时间和内存上高效,虽然在99%的情况下这不应该是主要原因。我不喜欢它的原因是我们失去了关于键类型的明确信息:它仅从代码中推断出有效键是两个字符串,阅读时不够清晰。
这是一个特殊情况。我已经遇到过这种情况不止一次,所以它并不比那更特殊。如果您的对象包含用作键的两个字符串,并且根据这两个字符串定义对象相等性是有意义的,则按照规定定义equals
和hashCode
,并将对象用作键和值。
在这种情况下,人们希望使用Set
而不是Map
,但Java的HashSet
没有提供基于相等对象从集合中检索对象的任何方法。因此,我们确实需要使用Map。
一个缺点是您需要创建一个新对象才能进行查找。这也适用于其他答案中的许多解决方案。
Jerónimo López: HashMaps中的复合键关于映射效率的讨论。