[英]Java: Composite key in hashmaps
我想將一組對象存儲在哈希圖中,其中鍵應該是兩個字符串值的組合。 有沒有辦法做到這一點?
我可以簡單地連接兩個字符串,但我確信有更好的方法來做到這一點。
您可以有一個包含兩個字符串的自定義對象:
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();
}
}
您無需重新發明輪子。 根據您的需要,只需使用Table<R,C,V>
接口的Guava的HashBasedTable<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();
}
這似乎是一種生成哈希代碼的糟糕方法:每次計算哈希代碼時都創建一個新的字符串實例是糟糕的。 (即使生成一次字符串實例並緩存結果也是不好的做法。)
這里有很多建議:
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();
這是一個非常基本的實現。 通過鏈接提供很多建議,以建議更好的調整策略。
為什么不創建一個(比如) Pair
對象,其中包含兩個字符串作為成員,然后將其用作鍵?
例如
public class Pair {
private final String str1;
private final String str2;
// this object should be immutable to reliably perform subsequent lookups
}
不要忘記equals()和hashCode() 。 有關 HashMap 和鍵的更多信息,包括不變性要求的背景,請參閱此博客條目。 如果您的密鑰不是不可變的,那么您可以更改其組件,隨后的查找將無法找到它(這就是為什么不可變對象(例如String
是密鑰的良好候選者)
你是對的,串聯並不理想。 在某些情況下它會起作用,但它通常是一個不可靠且脆弱的解決方案(例如, AB/C是與A/BC不同的密鑰嗎?)。
我有一個類似的案例。 我所做的就是連接兩個由波浪號 (~) 分隔的字符串。
所以當客戶端調用服務函數從地圖中獲取對象時,它看起來是這樣的:
MyObject getMyObject(String key1, String key2) {
String cacheKey = key1 + "~" + key2;
return map.get(cachekey);
}
這很簡單,但很管用。
我看到很多人使用嵌套地圖。 也就是說,要映射Key1 -> Key2 -> Value
(我使用計算機科學/ aka haskell curring notation for (Key1 x Key2) -> Value
mapping 有兩個參數並產生一個值),你首先提供第一個鍵 - - 這會返回一個(部分)映射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)
}
我不確定它是否比普通復合鍵結構更好。 你可以對此發表評論。
閱讀有關 spaguetti/cactus 堆棧的信息,我想出了一個可能用於此目的的變體,包括以任何順序映射您的鍵的可能性,以便 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 = "~";
INPUT: fakeMapKey("keyPart1","keyPart2","keyPart3");
OUTPUT: keyPart1~keyPart2~keyPart3
我想提一下我認為其他答案中沒有涵蓋的兩個選項。 它們是否適合您的目的,您必須自己決定。
您可以使用地圖的地圖,在外部地圖中使用字符串 1 作為鍵,在每個內部地圖中使用字符串 2 作為鍵。
我不認為它在語法方面是一個很好的解決方案,但它很簡單而且我已經看到它在某些地方使用過。 它在時間和內存方面也應該是高效的,但在 99% 的情況下這不應該是主要原因。 我不喜歡的是我們丟失了關於密鑰類型的明確信息:它只是從代碼中推斷出有效密鑰是兩個字符串,閱讀起來並不清晰。
這是針對特殊情況的。 這種情況我遇到過不止一次,所以沒有比這更特別的了。 如果您的對象包含用作鍵的兩個字符串,並且基於這兩者定義對象相等性是有意義的,則相應地定義equals
和hashCode
並將該對象用作鍵和值。
在這種情況下,人們可能希望使用Set
而不是Map
,但是 Java HashSet
不提供任何方法來從基於相等對象的集合中檢索對象。 所以我們確實需要地圖。
一個責任是您需要創建一個新對象才能進行查找。 這也適用於許多其他答案中的解決方案。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.