我正在尝试实施像
这样的地图Map<<key1, key2>, List<value>>
地图应包含2个键,相应的值将是一个列表。我想在同一个列表中添加记录,如果更改一个键值相等 例如,考虑以下记录
R1[key1, key2]
R2[key1, null/empty] - Key1 is equal
R3[null/empty, key2] - Key2 is equal
R4[key1, key2] - Key1 and Key2 both are equal.
所有应该插入相同的列表,如
Key = <Key1,Key2>
Value = <R1, R2, R3, R4>
我无法使用Guava table或commons MulitKeyMap(不要只为此包含整个库)。
我尝试实现一个类(我可以用作键),它将key1
和key2
作为属性,但实现了一个不考虑key1和key2的有效哈希码似乎有点(或可能很多)棘手
public class Key {
private int key1;
private int key2;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
// Cant include key1 and key2 in hashcode
/* result = prime * result + key1;
result = prime * result + key2;*/
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if(key2 and other.key2 both not blank/null){ // pseudo code
if (key2 == other.key2)
return true;
}
if(key1 and other.key1 both not blank/null){ //pseudo code
if (key1 == other.key1)
return true;
}
return true;
}
}
如果我对所有人使用相同的哈希码,它会起作用,但是由于我有数千条记录,它会影响性能。
编辑:
我无法使用嵌套地图,如
Map<key1, Map< key2, List<value>>>
由于某些记录可能只有一个密钥。
R1[key1, key2] - Have both keys
R2[key1, null/empty] - Key1 is equal
R3[null/empty, key2] - Key1 is missing and key2 is equal
这里R3没有key1,因此无法插入与R1和R2相同的位置
编辑2:
我也希望维持互动顺序。
答案 0 :(得分:4)
根据定义,地图每个值有1个键。
你可以有一张地图地图,或者你的钥匙可能是一个有2个字段的对象,但是这个地方尽可能接近。
地图地图:
Map myMap<key, Map<otherkey, value>>
自定义对象
public class MapKey {
public Object keyFirstPart;
public Object keySecondPart;
// You'll need to implement equals, hashcode, etc
}
Map myyMap <MapKey, value>
答案 1 :(得分:1)
使用TreeMap,这样就可以为CustomKey类而不是Hashcode使用自定义比较器。
TreeMap<CustomKey, List<value>> map = new TreeMap<CustomKey, List<value>>(myComparator);
eta:您可以使CustomKey类实现Comparable
,而不是创建比较器类答案 2 :(得分:1)
如果需要像HashMap一样的行为,我会创建两个地图,并且在处理集合时也能发挥作用(同样,我建议使用集合......):
public class MyMap<K1, K2, V> {
Map<K1, Collection<V>> map1;
Map<K2, Collection<V>> map2;
//have to add to both lists
put(K1 k1, K2 k2, V v) {
addToCollection(map1, k1, v);
addToCollection(map2, k2, v);
}
//notice T param
<T> void addToCollection(Map<T, Collection<V>> map, T key, V value ) {
Collection<V> collection= map.get(key);
if(collection==null) {
collection= new HashSet<V>();
map.put(key, collection);
}
collection.add(value );
}
public Collection<V> get(K1 k1, K2 k2) {
Collection<V> toReturn = new HashSet<V>();
Collection<V> coll1 = map1.get(k1);
if(coll1!=null) {
toReturn.addAll(coll1);
}
Collection<V> coll2 = map2.get(k2);
if(coll2!=null) {
toReturn.addAll(coll2);
}
return toReturn;
}
}
答案 3 :(得分:1)
试试这个......
为Map
的键创建一个类public class MapKey {
private Object key1;
private Object key2;
@Override
public boolean equals(Object object) {
boolean equals = false;
if (((MapKey) object).key1 == null && ((MapKey) object).key2 == null) {
equals = true;
}
if (((MapKey) object).key1.equals(this.key1) && ((MapKey) object).key2.equals(this.key2)) {
equals = true;
}
if (((MapKey) object).key1 == null && ((MapKey) object).key2.equals(this.key2)) {
equals = true;
}
if (((MapKey) object).key1.equals(this.key1) && ((MapKey) object).key2 == null) {
equals = true;
}
return equals;
}
@Override
public int hashCode() {
return 1;
}
public Object getKey1() {
return key1;
}
public void setKey1(Object key1) {
this.key1 = key1;
}
public Object getKey2() {
return key2;
}
public void setKey2(Object key2) {
this.key2 = key2;
}
}
在上面的类中,您可以根据需要修改key1和key2的DataTypes。这是执行所需逻辑的主类
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MapWithTwoKeys {
private static final Map<MapKey, List<Object>> mapWithTwoKeys = new HashMap<MapKey, List<Object>>();
public static void main(String[] args) {
// Create first map entry with key <A,B>.
MapKey mapKey1 = new MapKey();
mapKey1.setKey1("A");
mapKey1.setKey2("B");
List<Object> list1 = new ArrayList<Object>();
list1.add("List1 Entry");
put(mapKey1, list1);
// Create second map entry with key <A,B>, append value.
MapKey mapKey2 = new MapKey();
mapKey2.setKey1("A");
mapKey2.setKey2("B");
List<Object> list2 = new ArrayList<Object>();
list2.add("List2 Entry");
put(mapKey2, list2);
// Create third map entry with key <A,>.
MapKey mapKey3 = new MapKey();
mapKey3.setKey1("A");
mapKey3.setKey2("");
List<Object> list3 = new ArrayList<Object>();
list3.add("List3 Entry");
put(mapKey3, list3);
// Create forth map entry with key <,>.
MapKey mapKey4 = new MapKey();
mapKey4.setKey1("");
mapKey4.setKey2("");
List<Object> list4 = new ArrayList<Object>();
list4.add("List4 Entry");
put(mapKey4, list4);
// Create forth map entry with key <,B>.
MapKey mapKey5 = new MapKey();
mapKey5.setKey1("");
mapKey5.setKey2("B");
List<Object> list5 = new ArrayList<Object>();
list5.add("List5 Entry");
put(mapKey5, list5);
for (Map.Entry<MapKey, List<Object>> entry : mapWithTwoKeys.entrySet()) {
System.out.println("MapKey Key: <" + entry.getKey().getKey1() + ","
+ entry.getKey().getKey2() + ">");
System.out.println("Value: " + entry.getValue());
System.out.println("---------------------------------------");
}
}
/**
* Custom put method for the map.
* @param mapKey2 (MapKey... the key object of the Map).
* @param list (List of Object... the value of the Map).
*/
private static void put(MapKey mapKey2, List<Object> list) {
if (mapWithTwoKeys.get(mapKey2) == null) {
mapWithTwoKeys.put(mapKey2, new ArrayList<Object>());
}
mapWithTwoKeys.get(mapKey2).add(list);
}
}
代码非常简单易懂。如果这符合您的要求,请告诉我。
答案 4 :(得分:1)
我认为以下解决方案对您有用 - 我使用 MyKey.java 对象作为HashMap的键。它包含密钥和哈希码。哈希代码将用于标识您在问题中列出的不同密钥组合的值列表。首次注册两个密钥时会生成此哈希码。它存储在每个密钥中,因此即使其中任何一个密钥为空,您也将获得相同的哈希代码。
MultiKeyMap.java =&gt;扩展HashMap并覆盖'put'和'get'方法。 populateHashKey() - 此方法将为您想要的键的不同组合生成/返回相同的哈希码。
注意:插入顺序由Arraylist维护。此外,每个组合键的所有值都将存储在Map中的相同列表中。
package test.map;
public class MyKey {
private String myKey1;
private String myKey2;
private int hashKey;
public MyKey(String key1, String key2) {
this.myKey1 = key1;
this.myKey2 = key2;
}
/**
* @return the myKey1
*/
public String getMyKey1() {
return this.myKey1;
}
/**
* @param tmpMyKey1 the myKey1 to set
*/
public void setMyKey1(String tmpMyKey1) {
this.myKey1 = tmpMyKey1;
}
/**
* @return the myKey2
*/
public String getMyKey2() {
return this.myKey2;
}
/**
* @param tmpMyKey2 the myKey2 to set
*/
public void setMyKey2(String tmpMyKey2) {
this.myKey2 = tmpMyKey2;
}
/**
* Returns the hash key.
*/
@Override
public int hashCode() {
return this.hashKey;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyKey other = (MyKey) obj;
if(checkEqual(this.myKey1, other.myKey1)
|| checkEqual(this.myKey2, other.myKey2)) {
return true;
}
return false;
}
/*
* Checks whether key1 equals key2.
*/
private boolean checkEqual(String key1, String key2) {
if(key1 != null && key2 != null) {
return key1.equals(key2);
}
return false;
}
/**
* @return the hashKey
*/
public int getHashKey() {
return this.hashKey;
}
/**
* @param tmpHashKey the hashKey to set
*/
public void setHashKey(int tmpHashKey) {
this.hashKey = tmpHashKey;
}
}
MultiKeyMap.java -
package test.map;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MultiKeyMap extends HashMap<MyKey, List<String>>{
private static final long serialVersionUID = 3523468186955908397L;
Map<String, Integer> hashKeyMap = new HashMap<String, Integer>();
/*
* Adds single value in the List of values against the key
*/
public List<String> addValue(MyKey tmpKey, String tmpValue) {
populateHashKey(tmpKey);
List<String> orgValue = null;
if(tmpKey.getHashKey() != -1) {
orgValue = super.get(tmpKey);
if(orgValue == null) {
orgValue = new ArrayList<String>();
super.put(tmpKey, orgValue);
}
orgValue.add(tmpValue);
}
return orgValue;
}
@Override
public List<String> put(MyKey tmpKey, List<String> tmpValue) {
populateHashKey(tmpKey);
List<String> orgValue = null;
if(tmpKey.getHashKey() != -1) {
orgValue = super.get(tmpKey);
if(orgValue == null) {
orgValue = new ArrayList<String>();
super.put(tmpKey, orgValue);
}
orgValue.addAll(tmpValue);
}
return orgValue;
}
@Override
public List<String> get(Object tmpKey) {
if(!(tmpKey instanceof MyKey)) {
return null;
}
MyKey key = (MyKey) tmpKey;
populateHashKey(key);
return super.get(key);
}
/**
* Populates the hashKey generated for the MyKey combination. If the both Key1 and Key 2 are not null and its hash key is not generated
* earlier then it will generate the hash key using both keys and stores it in class level map 'hashKeyMap' against both keys.
* @param tmpKey
*/
public void populateHashKey(MyKey tmpKey) {
int hashKey = -1;
if(tmpKey.getMyKey1() != null && this.hashKeyMap.containsKey(tmpKey.getMyKey1()+"_")) {
hashKey = this.hashKeyMap.get(tmpKey.getMyKey1()+"_");
} else if(tmpKey.getMyKey2() != null && this.hashKeyMap.containsKey("_"+tmpKey.getMyKey2())) {
hashKey = this.hashKeyMap.get("_"+tmpKey.getMyKey2());
}
/*
* Assumption - While insertion you will always add first value with Key1 and Key2 both as not null. Hash key will be build only
* when both keys are not null and its not generated earlier.
*/
if(hashKey == -1 && tmpKey.getMyKey1() != null && tmpKey.getMyKey2() != null) {
hashKey = buildHashKey(tmpKey);
this.hashKeyMap.put(tmpKey.getMyKey1()+"_", hashKey);
this.hashKeyMap.put("_"+tmpKey.getMyKey2(), hashKey);
}
tmpKey.setHashKey(hashKey);
}
public int buildHashKey(MyKey tmpKey) {
final int prime = 31;
int result = 1;
result = prime * result + ((tmpKey.getMyKey1() == null) ? 0 : tmpKey.getMyKey1().hashCode());
result = prime * result + ((tmpKey.getMyKey1() == null) ? 0 : tmpKey.getMyKey1().hashCode());
return result;
}
}
测试课 -
import test.map.MultiKeyMap;
import test.map.MyKey;
public class TestMultiKeyMap {
public static void main(String[] args) {
System.out.println("=====Add values for each type of key==========");
MyKey regKey = new MyKey("Key1", "Key2");
MultiKeyMap myMap = new MultiKeyMap();
//Register the MyKey having both keys as NOT null.
System.out.println("Entry 1:"+myMap.addValue(regKey, "Key Reg"));
MyKey key1 = new MyKey("Key1", null);
//Add value against MyKey with only Key2
System.out.println("Entry 2:"+myMap.addValue(key1, "Key1"));
MyKey key2 = new MyKey(null, "Key2");
//Add value against MyKey with only Key1
System.out.println("Entry 3:"+myMap.addValue(key2, "Key2"));
MyKey bothKey = new MyKey("Key1", "Key2");
//Add value against MyKey with only Key1
System.out.println("Entry 4:"+myMap.addValue(bothKey, "both keys"));
System.out.println("=====Retrieve values for each type of key==========");
MyKey getKey1 = new MyKey("Key1", null);
System.out.println("Values for Key1:"+myMap.get(getKey1));
MyKey getKey2 = new MyKey(null, "Key2");
System.out.println("Values for Key2:"+myMap.get(getKey2));
MyKey getBothKey = new MyKey("Key1", "Key2");
System.out.println("Values for both keys:"+myMap.get(getBothKey));
}
}
输出 -
=====Add values for each type of key==========
Entry 1:[Key Reg]
Entry 2:[Key Reg, Key1]
Entry 3:[Key Reg, Key1, Key2]
Entry 4:[Key Reg, Key1, Key2, both keys]
=====Retrieve values for each type of key==========
Values for Key1:[Key Reg, Key1, Key2, both keys]
Values for Key2:[Key Reg, Key1, Key2, both keys]
Values for both keys:[Key Reg, Key1, Key2, both keys]
答案 5 :(得分:0)
只检查key1的哈希码,如果匹配,那么我们无论如何都要测试equals方法。
@Override
public int hashCode() {
return key1.hashCode();
}
@Override
public boolean equals(Object obj) {
Key k = (Key)obj;
// Generic equals code goes here
if(this.key1.equals(k.key1) && this.key2.equals(k.key2) )
return true;
return false;
}
答案 6 :(得分:0)
基本上,实现这一目标的想法是通过值本身映射键。
所以你可以有一个内部地图来做这个(这里我有一组键而不是2)。
Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>();
所以你的Map
实现看起来像这样:
public class MultiKeyMap<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 1L;
private Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>();
@Override
public V put(K key, V value) {
V v = null;
Set<K> keySet = keySetMap.get(value);
if(keySet == null) {
keySet = new LinkedHashSet<K>();
keySetMap.put(value, keySet);
}
keySet.add(key);
v = super.put(key, value);
// update the old keys to reference the new value
Set<K> oldKeySet = keySetMap.get(v);
if(oldKeySet != null) {
for(K k : oldKeySet) {
super.put(k, value);
}
}
return v;
}
}
这适用于简单(不可变)对象:
@Test
public void multiKeyMapString() {
MultiKeyMap<String, String> m = new MultiKeyMap<String, String>();
m.put("1", "A");
m.put("2", "B");
for(Entry<String, String> e : m.entrySet()) {
System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString());
}
m.put("3", "A");
System.out.println("----");
for(Entry<String, String> e : m.entrySet()) {
System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString());
}
m.put("4", "C");
System.out.println("----");
for(Entry<String, String> e : m.entrySet()) {
System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString());
}
m.put("3", "D");
System.out.println("----");
for(Entry<String, String> e : m.entrySet()) {
System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString());
}
System.out.println("----");
System.out.println("values=" + m.values());
System.out.println();
System.out.println();
}
通过上面的测试,输出看起来像
K=1, V=A
K=2, V=B
----
K=1, V=A
K=2, V=B
K=3, V=A
----
K=1, V=A
K=2, V=B
K=3, V=A
K=4, V=C
----
K=1, V=D
K=2, V=B
K=3, V=D
K=4, V=C
----
values=[D, B, C]
正如您在上一个输出中看到的那样,键1
现在映射了值D
,因为3
之前映射的值与1
映射的值相同在之前的步骤。
但是当你想在地图中放置一个列表(或任何可变对象)时会变得棘手,因为如果你改变列表(添加/删除一个元素),那么列表将有另一个hashCode
作为一个用于映射以前的键:
@Test
public void multiKeyMapList() {
List<String> l = new ArrayList<String>();
l.add("foo");
l.add("bar");
MultiKeyMap<String, List<String>> m = new MultiKeyMap<String, List<String>>();
m.put("1", l);
m.put("2", l);
for(Entry<String, List<String>> e : m.entrySet()) {
System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString());
}
m.get("1").add("foobar");
m.put("3", l);
System.out.println("----");
for(Entry<String, List<String>> e : m.entrySet()) {
System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString());
}
l = new ArrayList<String>();
l.add("bla");
m.put("4", l);
System.out.println("----");
for(Entry<String, List<String>> e : m.entrySet()) {
System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString());
}
m.put("3", l);
System.out.println("----");
for(Entry<String, List<String>> e : m.entrySet()) {
System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString());
}
System.out.println("----");
System.out.println("values=" + m.values());
}
上面的测试将输出如下内容:
K=1, V=[foo, bar]
K=2, V=[foo, bar]
----
K=1, V=[foo, bar, foobar]
K=2, V=[foo, bar, foobar]
K=3, V=[foo, bar, foobar]
----
K=1, V=[foo, bar, foobar]
K=2, V=[foo, bar, foobar]
K=3, V=[foo, bar, foobar]
K=4, V=[bla]
----
K=1, V=[foo, bar, foobar]
K=2, V=[foo, bar, foobar]
K=3, V=[bla]
K=4, V=[bla]
----
values=[[foo, bar, foobar], [bla]]
如您所见,1
和2
映射的值尚未更新,只需将密钥3
转换为映射另一个值。原因是hashCode
的{{1}}结果与[foo, bar]
的{{1}}结果不同,导致[foo, bar, foobar]
无法返回正确的结果。要克服这一点,您需要通过与实际值进行比较来获得一组键。
Map#get
现在再次运行两个测试会得到以下输出:
对于简单(不可变)对象
public class MultiKeyMap<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 1L;
private Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>();
@Override
public V put(K key, V value) {
V v = null;
Set<K> keySet = keySetMap.get(value);
if (keySet == null) {
keySet = new LinkedHashSet<K>();
keySetMap.put(value, keySet);
}
keySet.add(key);
v = super.put(key, value);
// update the old keys to reference the new value
for (K k : getKeySetByValue(v)) {
super.put(k, value);
}
return v;
}
@Override
public Collection<V> values() {
// distinct values
return new LinkedHashSet<V>(super.values());
}
private Set<K> getKeySetByValue(V v) {
Set<K> set = null;
if (v != null) {
for (Map.Entry<V, Set<K>> e : keySetMap.entrySet()) {
if (v.equals(e.getKey())) {
set = e.getValue();
break;
}
}
}
return set == null ? Collections.<K> emptySet() : set;
}
}
对于可以更改的对象
K=1, V=A
K=2, V=B
----
K=1, V=A
K=2, V=B
K=3, V=A
----
K=1, V=A
K=2, V=B
K=3, V=A
K=4, V=C
----
K=1, V=D
K=2, V=B
K=3, V=D
K=4, V=C
----
values=[D, B, C]
我希望这可以帮助您找到实现Map的方法。您可以代替扩展现有实现实现K=1, V=[foo, bar]
K=2, V=[foo, bar]
----
K=1, V=[foo, bar, foobar]
K=2, V=[foo, bar, foobar]
K=3, V=[foo, bar, foobar]
----
K=1, V=[foo, bar, foobar]
K=2, V=[foo, bar, foobar]
K=3, V=[foo, bar, foobar]
K=4, V=[bla]
----
K=1, V=[bla]
K=2, V=[bla]
K=3, V=[bla]
K=4, V=[bla]
----
values=[[bla]]
接口,以便您可以为其合同提供所有方法的实现,并将您选择的实现作为成员来处理实际映射。
答案 7 :(得分:0)
请使用Apache Commons Lang库中的优秀助手类EqualsBuilder和HashCodeBuilder。一个例子:
public class Person {
private String name;
private int age;
// ...
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
if (!(obj instanceof Person))
return false;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
答案 8 :(得分:-1)
创建另一个保存关系key-&gt; intermediateKey的Map
。中间密钥可以是GUID或其他自动生成并保证唯一的东西。
Map<String, GUID> first = new HashMap<String, GUID>();
first.put(key1, guid1);
first.put(key2, guid1);
Map<GUID, ValueType> second = new HashMap<GUID, ValueType>();
second.put(guid1, value1);
或者(虽然我发现它更复杂,灵活性更低),你可以玩钥匙。如果key1.equals(key2)
(以及key2.equals(key1)
&amp;&amp;(key1.hashCode()== key2.hashCode ) then
Map.get(key1)will return the same value than
Map.get (KEY2)`。