这个问题在这里已有答案:
我需要一个行为类似于Map的数据结构,但使用多个(不同类型的)键来访问其值 .
(让's not be too general, let' s说 two 键)
Keys are guaranteed to be unique.
就像是:
MyMap<K1,K2,V> ...
使用以下方法:
getByKey1(K1 key)...
getByKey2(K2 key)...
containsKey1(K1 key)...
containsKey2(K2 key)...
Do you have any suggestions?
我唯一能想到的是:
编写一个内部使用两个Maps的类 .
EDIT 有些人建议我使用 tuple , pair 或类似的作为Java Map 的关键,但这对我来说是 would not work :
如上所述,我必须能够仅通过指定的两个键中的一个来搜索值 .
Map 使用密钥的哈希码并检查它们的相等性 .
27 回答
为什么不放弃密钥必须是特定类型的要求,即只使用Map <Object,V> .
有时,仿制药不值得额外的工作 .
一个脏的和一个简单的解决方案,如果你只是为了排序而使用 Map ,那么就是给一个键添加一个非常小的值,直到该值不存在,但不添加最小值(例如Double.MIN_VALUE),因为它会导致错误 . 就像我说的,这是一个非常脏的解决方案,但它使代码更简单 .
那么你宣布以下“Key”类:
声明 Map
声明关键对象
填写 Map
从 Map 中获取对象
我可以看到以下方法:
a)使用2个不同的 Map . 你可以按照你的建议将它们包装在一个类中,但即便如此也可能是一种矫枉过正 . 只需直接使用 Map :key1Map.getValue(k1),key2Map.getValue(k2)
b)您可以创建一个类型感知密钥类,并使用它(未经测试) .
顺便说一句,在很多常见的情况下,
key1
的空间和key2
的空间不相交 . 在这种情况下,您实际上不需要做任何特别的事情 . 只需定义一个包含条目key1=>v
以及key2=>v
的 Mapsol:cancatenate两个键并制作一个最终键,使用它作为键 .
对于关键值,
在beetween中连接ket-1和key-2以及“,”,将其用作原始密钥 .
key = key-1“,”key-2;
myMap.put(键,值);
同样在重温 Value 观的同时 .
见Google Collections . 或者,如您所知,在内部使用 Map ,并使该 Map 使用一对 . 你很容易,但不是标准集合的一部分 .
我推荐这样的东西:
然后你就可以使用它:
myMap.put(k1,value)
或myMap.put(k2,value)
Advantages :它很简单,强制执行类型安全,并且不存储重复数据(因为两个 Map 解决方案都有,但仍存储重复值) .
Drawbacks :不通用 .
两张 Map . 一个
Map<K1, V>
和一个Map<K2, V>
. 如果必须有单个接口,请编写实现所述方法的包装类 .听起来你的解决方案对于这种需求是非常合理的,老实说,如果你的两个关键类型真的不同,我就不会发现问题 . 只需要为此编写自己的实现,并在需要时处理同步问题 .
Commons-collections提供您正在寻找的东西:https://commons.apache.org/proper/commons-collections/apidocs/
看起来现在是commons-collections的类型 .
可在以下位置找到打字版本:https://github.com/megamattron/collections-generic
这将完全支持您的用例:
如果键是唯一的,则不需要2个 Map , Map Map ,mapOfWhateverThereIs . 只需要一个单一的 Map ,只需要一个简单的包装方法,将您的键和值放入该 Map 中 . 例:
然后像往常一样使用你的 Map . 你甚至不需要那些花哨的getByKeyN和containsKeyN .
我创建了这个来解决类似的问题 .
数据结构
检索值:
(注意*将对象强制转换为插入的类型 . 在我的情况下,它是我的事件对象)
插入
听起来像Python元组 . 遵循这种精神,你可以创建一个自己设计的不可变类,实现Comparable,你就拥有它 .
这样的事情怎么样:
他的声明说键是唯一的,所以很可能在不同的键上保存相同的值对象,当你发送任何匹配上述值的键时,我们就可以回到值对象了 .
见下面的代码:
一个值Object Class,
结果是:
另一种解决方案是使用Google's Guava
用法非常简单:
方法
HashBasedTable.create()
基本上是这样做的:如果你想创建一些自定义 Map ,你应该选择第二个选项(如@Karatheodory建议的那样),否则你应该对第一个选项没问题 .
所有multy键可能都会失败,导致put([key1,key2],val)和get([null,key2])最后使用[key1,key2]和[null,key2]的等号 . 如果支持映射不包含每个密钥的散列桶,那么查找真的很慢 .
我认为要走的路是使用一个索引装饰器(参见上面的key1,key2示例),如果额外的索引键是存储值的属性,你可以使用属性名称和反射来构建第二个映射,当你放入(key,val)并添加一个额外的方法get(propertyname,propertyvalue)使用该索引 .
get(propertyname,propertyvalue)的返回类型可以是Collection,因此即使没有唯一键也被索引....
Commons或Guava的MultiMap或MultiKeyMap都可以使用 .
但是,考虑到键是原始类型,快速简单的解决方案可能是自己扩展Map类购买处理复合键 .
可以在此处找到另一种可能提供更复杂密钥的可能解决方案:http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html
我仍然会建议2 Map 解决方案,但有一个推特
此方案允许您拥有任意数量的键“别名” .
它还允许您通过任何键更新值,而不会使 Map 不同步 .
如果你打算使用几个键的组合作为一个,那么也许apache commnons MultiKey是你的朋友 . 我不认为它会一个接一个地工作..
在我看来,您在问题中想要的方法直接由Map支持 . 你似乎想要的是
请注意,在map中,
get()
和containsKey()
等都需要Object
参数 . 没有什么可以阻止您使用一个get()
方法委派给您组合的所有复合 Map (如您的问题和其他答案中所述) . 也许你得到了类铸造问题(如果他们特别天真地实施的话) .基于类型的注册还允许您检索要使用的“正确”映射:
只是一些想法......
定义具有K1和K2实例的类 . 然后将其用作类作为键类型 .
如何使用trie数据结构?
http://en.wikipedia.org/wiki/Trie
trie的根将是空白的 . 第一级兄弟将成为 Map 的主键,第二级兄弟将成为您的辅助键,第三级将是具有值的终端节点将为null以指示该分支的终止 . 您还可以使用相同的方案添加两个以上的密钥 .
查找是简单的DFS .
我会建议结构
虽然搜索第二个密钥可能效率不高
我将这种实现用于多个关键对象 . 它允许我使用无数个键来映射 . 它可扩展且非常简单 . 但它有局限性:键是按照构造函数中的参数顺序排序的,因为使用了Arrays.equals(),它不能用于2D数组 . 要修复它 - 你可以使用Arrays.deepEquals();
希望它会对你有所帮助 . 如果您知道为什么它不能用作解决此类问题的任何原因 - 请告诉我!
根据它的使用方式,您可以使用两个 Map
Map<K1, V>
和Map<K2, V>
或两个 MapMap<K1, V>
和Map<K2, K1>
来执行此操作 . 如果其中一个键比另一个键更永久,则第二个选项可能更有意义 .Proposal, as suggested by some answerers: