如何实现具有多个键的 Map?

我需要一个类似于 Map 的数据结构, 但使用多个(不同类型的)键来访问其值。
(让我们不要太一般,让我们说 键)

密钥保证是唯一的。

比如:

MyMap<K1,K2,V> ...

方法如下:

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

你有什么建议吗?

我唯一能想到的就是:
编写一个在内部使用两个 Map 的类。

剪辑 有些人建议我使用 Tuple一对或类似的键 Java 的地图,但这个 没用的对我来说:
如上所述,我必须能够只通过指定的两个键中的一个来搜索值。
映射使用键的哈希代码并检查它们是否相等。

323619 次浏览

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

参见 谷歌收藏。或者,就像您建议的那样,在内部使用一个映射,并让该映射使用一对映射。您必须编写或者找到 Pair < > ; 它非常简单,但不是标准集合的一部分。

听起来像 Python 元组。遵循这种精神,您可以创建一个您自己设计的不可变类,该类实现了 Compable,并且您将拥有它。

两张地图。一个 Map<K1, V>和一个 Map<K2, V>。如果必须有单个接口,则编写一个实现所述方法的包装类。

听起来你的解决方案对于这种需求是相当合理的,老实说,如果你的两个关键类型是真正不同的,我不认为这有什么问题。只要确保您为此编写了自己的实现,并在需要时处理同步问题。

如果您打算使用多个键的组合作为一个,那么也许 Apache 公共 < strong > MultiKey 是您的朋友。但我觉得一个接一个不行。.

我可以看到以下几种方法:

A)使用两种不同的地图。您可以按照您的建议将它们包装在一个类中,但即使这样也可能有些过头了。只需直接使用映射: key1Map.getValue (k1)、 key2Map.getValue (k2)

B)您可以创建一个类型感知的键类,并使用它(未经测试)。

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=>vkey2=>v条目的映射

正如一些回答者所建议的那样:

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);
}
}

根据使用方式的不同,您可以使用两个映射 Map<K1, V>Map<K2, V>或两个映射 Map<K1, V>Map<K2, K1>来完成这项工作。如果其中一个键比另一个键更永久,则第二个选项可能更有意义。

我仍然会建议2地图解决方案,但有一个 twest

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

这个方案允许您使用任意数量的密钥“别名”。

它还允许您通过任何键更新值,而不会使映射失去同步。

在我看来,Map 直接支持您在问题中需要的方法。你似乎想要的是

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

注意,在 map 中,get()containsKey()等都采用 Object参数。没有什么可以阻止您使用一个 get()方法来委托给您组合的所有复合映射(正如您的问题和其他答案中指出的那样)。也许您需要类型注册,这样您就不会遇到类转换问题(如果它们是特殊 + 天真实现的)。

基于输入的注册还允许您检索要使用的“正确”地图:

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?
.....
}

只是一些想法..。

Commons-Collection 提供了您正在寻找的内容: Https://commons.apache.org/proper/commons-collections/apidocs/

看起来现在 commons-Collection 已被键入。

打印版本可在以下网址找到: Https://github.com/megamattron/collections-generic

这将完全支持您的用例:

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

为什么不直接放弃键必须是特定类型的要求呢,也就是说只要使用 Map < Object,V > 。

有时候泛型并不值得额外的工作。

溶胶: 取消两个键和作出最后的关键,使用这个作为关键。

对于键值,

连接 ket-1和 key-2,在两者之间加一个 come“ ,”,使用它作为原始密钥。

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

Put (key,value) ;

同样地,在检索值的时候。

所有多键都可能失败,因为 put ([ key1,key2] ,val)和 get ([ null,key2])最终使用等于[ key1,key2]和[ null,key2]。 如果后台映射不包含每个键的散列桶,那么查找将非常缓慢。

我认为方法是使用一个索引修饰器(参见上面的 key1,key2例子) ,如果额外的索引键是存储值的属性,你可以使用属性名和反射来构建二级映射,当你放置(key,val)并添加一个额外的方法 get (property tyname,property tyvalue)来使用该索引时。

Get (property tyname,property tyvalue)的返回类型可以是 Collection,因此即使没有唯一键的索引... ..。

我创建这个是为了解决类似的问题。

数据结构

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();


}


}

检索一个值:

(注意 * 将对象抛回到插入的类型。在我的例子中,它是我的事件对象)

    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);

我建议你用这种结构

Map<K1, Map<K2, V>>

虽然搜索第二把钥匙可能效率不高

我推荐这样的东西:

    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)

优点 : 它很简单,强制类型安全,并且不存储重复的数据(就像两个映射解决方案那样,尽管仍然存储重复的值)。
缺点 : 不是通用的。

声明下面的“ Key”类怎么样:

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);

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

还有一种解决方案是使用 谷歌的番石榴

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();
}
});

如果你试图创建一些自定义地图,你应该选择第二个选项(如@Karatheodory 所建议的) ,否则你应该可以选择第一个。

使用 trie 数据结构如何?

Http://en.wikipedia.org/wiki/trie

尝试的根将由空白。第一级兄弟节点将是映射的主键,第二级兄弟节点将是次要键,第三级节点将是终端节点,这些终端节点的值将为 null,表示该分支的终止。还可以使用同一方案添加两个以上的键。

查找是简单的 DFS。

我对多个键对象使用了这样的实现。 它允许我使用无数的键为地图。它是可伸缩的,相当简单。 但是它也有局限性: 键是按照构造函数中参数的顺序排列的,而且由于使用了 Arrays.equals () ,它不能用于2D 数组。要修复它,可以使用 Arrays.deep Equals () ;

希望对你有帮助。如果你知道任何原因,为什么它不能被用作这些问题的解决方案-请让我知道!

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)));
}
}

这样如何:

他的语句说键是唯一的,因此将相同的值对象保存到不同的键是很有可能的,当您发送任何与所述值相匹配的键时,我们将能够返回到值对象。

见下面的代码:

值 Object Class,

    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 和包含 KeyN。

MultiMap 或者 MultiKeyMap from Commons 或者 Guava 都可以工作。

然而,一个快速而简单的解决方案可能是扩展 Map 类,自己处理一个组合键,因为键是基元类型的。

如果你仅仅使用映射来排序,一个肮脏而简单的解决方案是给键添加一个非常小的值,直到该值不存在为止,但是不要添加最小值(例如 Double.MIN _ VALUE) ,因为它会导致 bug。正如我所说,这是一个非常肮脏的解决方案,但它使代码更简单。