如何实现具有多个键的地图?

我需要一个像Map一样的数据结构,但是使用多个(不同types的)键来访问它的值。
(让我们不要太笼统,让我们说两个键)

密钥保证是唯一的。

就像是:

MyMap<K1,K2,V> ... 

使用如下方法:

 getByKey1(K1 key)... getByKey2(K2 key)... containsKey1(K1 key)... containsKey2(K2 key)... 

你有什么build议吗?

我能想到的唯一的事情是:
写一个内部使用两个地图的类。

编辑有些人build议我使用一个元组 ,一或类似的Java的地图的关键,但这适合我:
如上所述,我必须能够仅通过指定的两个键中的一个来search值。
地图使用密钥的哈希码并检查其相等性。

两张地图。 一个Map<K1, V>和一个Map<K2, V> 。 如果你必须有一个接口,写一个包装类来实现上述方法。

Commons-collections提供了您正在寻找的内容: http : //commons.apache.org/proper/commons-collections/javadocs/api-release/index.html

看起来像现在公用集合键入。

键入的版本可以在https://github.com/megamattron/collections-genericfind

这将完全支持你的用例:

  MultiKeyMap<k1,k2,...,kn,v> multiMap = ?? 

我仍然build议2地图解决scheme,但与tweest

 Map<K2, K1> m2; Map<K1, V> m1; 

这种scheme可以让你有任意数量的关键“别名”。

它还允许您通过任何键更新值,而不会导致地图不同步。

另一个解决scheme是使用谷歌的番石榴

 import com.google.common.collect.Table; import com.google.common.collect.HashBasedTable; Table<String, String, Integer> table = HashBasedTable.create(); 

用法很简单:

 String row = "a"; String column = "b"; int value = 1; if (!table.contains(row, column)) { table.put(row, column, value); } System.out.println("value = " + table.get(row, column)); 

方法HashBasedTable.create()基本上是这样做的:

 Table<String, String, Integer> table = Tables.newCustomTable( Maps.<String, Map<String, Integer>>newHashMap(), new Supplier<Map<String, Integer>>() { public Map<String, Integer> get() { return Maps.newHashMap(); } }); 

如果你想创build一些自定义的地图,你应该去第二个选项(如@Karatheodorybuild议),否则你应该罚款的第一个。

你怎么样宣布下面的“关键”类:

 public class Key { public Object key1, key2, ..., keyN; public Key(Object key1, Object key2, ..., Object keyN) { this.key1 = key1; this.key2 = key2; ... this.keyN = keyN; } @Override public boolean equals(Object obj) { if (!(obj instanceof Key)) return false; Key ref = (Key) obj; return this.key1.equals(ref.key1) && this.key2.equals(ref.key2) && ... this.keyN.equals(ref.keyN) } @Override public int hashCode() { return key1.hashCode() ^ key2.hashCode() ^ ... ^ keyN.hashCode(); } } 

声明地图

 Map<Key, Double> map = new HashMap<Key,Double>(); 

声明关键对象

 Key key = new Key(key1, key2, ..., keyN) 

填充地图

 map.put(key, new Double(0)) 

从地图获取对象

 Double result = map.get(key); 

build议,正如一些答复者所build议的那样:

 public interface IDualMap<K1, K2, V> { /** * @return Unmodifiable version of underlying map1 */ Map<K1, V> getMap1(); /** * @return Unmodifiable version of underlying map2 */ Map<K2, V> getMap2(); void put(K1 key1, K2 key2, V value); } public final class DualMap<K1, K2, V> implements IDualMap<K1, K2, V> { private final Map<K1, V> map1 = new HashMap<K1, V>(); private final Map<K2, V> map2 = new HashMap<K2, V>(); @Override public Map<K1, V> getMap1() { return Collections.unmodifiableMap(map1); } @Override public Map<K2, V> getMap2() { return Collections.unmodifiableMap(map2); } @Override public void put(K1 key1, K2 key2, V value) { map1.put(key1, value); map2.put(key2, value); } } 

为什么不放弃关键必须是特定types的要求,即只使用Map <Object,V>。

有时generics只是不值得的额外工作。

我创build了这个来解决类似的问题。

数据结构

 import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; public class HashBucket { HashMap<Object, ArrayList<Object>> hmap; public HashBucket() { hmap = new HashMap<Object, ArrayList<Object>>(); } public void add(Object key, Object value) { if (hmap.containsKey(key)) { ArrayList al = hmap.get(key); al.add(value); } else { ArrayList al = new ArrayList<Object>(); al.add(value); hmap.put(key, al); } } public Iterator getIterator(Object key) { ArrayList al = hmap.get(key); return hmap.get(key).iterator(); } } 

检索一个值:

(注意*将对象转换回插入的types,在我的情况下是我的事件对象)

  public Iterator getIterator(Object key) { ArrayList al = hmap.get(key); if (al != null) { return hmap.get(key).iterator(); } else { List<Object> empty = Collections.emptyList(); return empty.iterator(); } } 

插入

 Event e1 = new Event(); e1.setName("Bob"); e1.setTitle("Test"); map.add("key",e1); 

我可以看到以下方法:

a)使用2个不同的地图。 你可以按照你的build议将它们包装在一个class级中,但即使这样也可能是一个过度的杀伤力。 直接使用地图:key1Map.getValue(k1),key2Map.getValue(k2)

b)你可以创build一个types感知的密钥类,并使用它(未经testing)。

 public class Key { public static enum KeyType { KEY_1, KEY_2 } public final Object k; public final KeyType t; public Key(Object k, KeyType t) { this.k = k; this.t= t; } public boolean equals(Object obj) { KeyType kt = (KeyType)obj; return k.equals(kt.k) && t == kt.t; } public int hashCode() { return k.hashCode() ^ t.hashCode(); } } 

顺便说一句,在很多常见的情况下, key1的空间和key2的空间不会相交。 在这种情况下,你并不需要做任何特别的事情。 只需定义一个具有条目key1=>v以及key2=>v的映射

sol:cancatenate这两个键,并作出最后一个键,使用这个键。

对于关键的价值,

连接ket-1,key-2与之间来一个“,”,用这个作为原始密钥。

key = key-1 +“,”+ key-2;

myMap.put(键,值);

同样重视价值。

听起来像一个Python元组。 本着这样的精神,你可以创build一个你自己devise的不可变类来实现Comparable,你将拥有它。

所有multy键可能会失败,导致put([key1,key2],val)和get([null,key2])使用[key1,key2]和[null,key2]的等号结束。 如果支持映射不包含每个键的哈希桶,那么查找真的很慢。

我想要的方式是使用索引装饰器(请参阅上面的key1,key2示例),如果额外的索引键是存储值的属性,则可以使用属性名称和reflection来构build第二个映射,val)并添加一个额外的方法get(propertyname,propertyvalue)来使用该索引。

get(propertyname,propertyvalue)的返回types可能是一个Collection,所以即使没有唯一的键被索引。

我用这种实现多个关键对象。 它允许我使用无数的键的地图。 这是可扩展的,非常简单。 但它有一些限制:按照构造函数中的参数顺序sorting,因为使用了Arrays.equals(),所以不能用于二维数组。 要解决它 – 你可以使用Arrays.deepEquals();

希望它会帮助你。 如果您知道为什么它不能用作解决此类问题的任何原因,请告诉我!

 public class Test { private static Map<InnumerableKey, Object> sampleMap = new HashMap<InnumerableKey, Object>(); private static class InnumerableKey { private final Object[] keyParts; private InnumerableKey(Object... keyParts) { this.keyParts = keyParts; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof InnumerableKey)) return false; InnumerableKey key = (InnumerableKey) o; if (!Arrays.equals(keyParts, key.keyParts)) return false; return true; } @Override public int hashCode() { return keyParts != null ? Arrays.hashCode(keyParts) : 0; } } public static void main(String... args) { boolean keyBoolean = true; double keyDouble = 1d; Object keyObject = new Object(); InnumerableKey doubleKey = new InnumerableKey(keyBoolean, keyDouble); InnumerableKey tripleKey = new InnumerableKey(keyBoolean, keyDouble, keyObject); sampleMap.put(doubleKey, "DOUBLE KEY"); sampleMap.put(tripleKey, "TRIPLE KEY"); // prints "DOUBLE KEY" System.out.println(sampleMap.get(new InnumerableKey(true, 1d))); // prints "TRIPLE KEY" System.out.println(sampleMap.get(new InnumerableKey(true, 1d, keyObject))); // prints null System.out.println(sampleMap.get(new InnumerableKey(keyObject, 1d, true))); } } 

来自Commons或Guava的MultiMap或MultiKeyMap都可以使用。

但是,考虑到键是原始types,一个简单而快速的解决scheme可以扩展Map类处理组合键。

定义一个具有K1和K2实例的类。 然后使用它作为你的键类的类。

查看Googlecollections集 。 或者,如您所build议的那样,在内部使用地图,并使用该地图使用Pair。 你必须写或findPair <>; 这很容易,但不是标准集合的一部分。

听起来像你的解决scheme是相当合理的这种需求,我真的不觉得有问题,如果你的两个关键types是非常明显的。 只是让你写你自己的实现,并在需要时处理同步问题。

如果你打算使用几个键的组合作为一个,那么也许apache commnons MultiKey是你的朋友。 我不认为它会一个接一个地工作..

根据将如何使用,可以使用两个映射Map<K1, V>Map<K2, V>或使用两个映射Map<K1, V>Map<K2, K1> 。 如果其中一个键比另一个键更持久,则第二个选项可能更有意义。

在我看来,你想在你的问题的方法是直接支持地图。 你似乎想要的是(s)

 put(K1 key, K2 key, V value) put(K1 key, V value) put(K2 key, V value) 

请注意,在地图中, get()containsKey()等都带有Object参数。 没有什么能阻止你使用get()方法委托给你合并的所有合成地图(如你的问题和其他答案中所述)。 也许你会需要types注册,所以你不会得到类铸造问题(如果他们是特殊+天真地执行。

基于types的注册也可以让你检索“正确的”地图使用:

 Map<T,V> getMapForKey(Class<T> keyClass){ //Completely naive implementation - you actually need to //iterate through the keys of the maps, and see if the keyClass argument //is a sub-class of the defined map type. And then ordering matters with //classes that implement multiple interfaces... Map<T,V> specificTypeMap = (Map<T,V) maps.get(keyClass); if (specificTypeMap == null){ throw new IllegalArgumentException("There is no map keyed by class" + keyClass); } return maps.get(keyClass); } V put(Object key, V value) { //This bit requires generic suppression magic - but //nothing leaves this class and you're testing it right? //(You can assert that it *is* type-safe) Map map = getMapForKey(key.getClass()); map.put(object, key); } void put(Object[] keys, V value) { //Or put(V value, Object ... keys) //Might want to catch exceptions for unsupported keys and log instead? ..... } 

只是一些想法…

我会build议的结构

 Map<K1, Map<K2, V>> 

尽pipesearch第二个密钥可能效率不高

我推荐这样的东西:

  public class MyMap { Map<Object, V> map = new HashMap<Object, V>(); public V put(K1 key,V value){ return map.put(key, value); } public V put(K2 key,V value){ return map.put(key, value); } public V get(K1 key){ return map.get(key); } public V get(K2 key){ return map.get(key); } //Same for conatains } 

那么你可以像这样使用它:
myMap.put(k1,value)myMap.put(k2,value)

优点 :它很简单,强制types安全,不存储重复的数据(如两个地图解决scheme一样,虽然仍然存储重复值)。
缺点 :不通用。

提供更复杂键的可能性的另一个解决scheme可以在这里find: http : //insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html

如何使用trie数据结构?

http://en.wikipedia.org/wiki/Trie

trie的根将会空白。 第一级兄弟会是你的地图主键,第二级兄弟会是你的第二级键,第三级是终点节点,它们的值将为零,表示该分支的终止。 您也可以使用相同的scheme添加两个以上的密钥。

查阅简单的DFS。

怎么样这样的事情:

他的声明说密钥是唯一的,所以将相同的值对象保存在不同的密钥中是完全可能的,当你发送与所述值匹配的任何密钥时,我们将能够返回值对象。

见下面的代码:

值对象类,

  public class Bond { public Bond() { System.out.println("The Name is Bond... James Bond..."); } private String name; public String getName() { return name;} public void setName(String name) { this.name = name; } } public class HashMapValueTest { public static void main(String[] args) { String key1 = "A"; String key2 = "B"; String key3 = "C"; Bond bond = new Bond(); bond.setName("James Bond Mutual Fund"); Map<String, Bond> bondsById = new HashMap<>(); bondsById.put(key1, bond); bondsById.put(key2, bond); bondsById.put(key3, bond); bond.setName("Alfred Hitchcock"); for (Map.Entry<String, Bond> entry : bondsById.entrySet()) { System.out.println(entry.getValue().getName()); } } } 

结果是:

 The Name is Bond... James Bond... Alfred HitchCock Alfred HitchCock Alfred HitchCock 

如果密钥是唯一的,则不需要2个地图,地图的地图,mapOfWhateverThereIs。 需要只有一个单一的地图,只需要一个简单的包装方法,将您的键和值放入该地图。 例:

 Map<String, String> map = new HashMap<>(); public void addKeysAndValue(String key1, String key2, String value){ map.put(key1, value); map.put(key2, value); } public void testIt(){ addKeysAndValue("behemoth", "hipopotam", "hornless rhino"); } 

然后像平常一样使用你的地图。 你甚至不需要那些花哨的getByKeyN和containsKeyN。

一个肮脏而简单的解决scheme,如果你使用地图只是为了sorting可以说,是添加一个非常小的值,直到该值不存在,但不添加最小值(例如Double.MIN_VALUE),因为它会造成一个错误。 就像我说的,这是一个非常肮脏的解决scheme,但它使代码更简单。