使用重复键的映射实现

我想要一张有复制钥匙的地图。

我知道有很多地图实现(Eclipse 给我展示了大约50个) ,所以我打赌一定有一个允许这样做。我知道编写自己的映射来实现这一点很容易,但我宁愿使用一些现有的解决方案。

也许是公共收藏品或谷歌收藏品?

293717 次浏览

您可以简单地在常规 HashMap 中为值传递一个值数组,从而模拟重复的键,然后由您决定使用什么数据。

您也可以只使用 多重地图,虽然我不喜欢的想法,重复的关键我自己。

您还能解释一下您试图实现带有重复键的映射的上下文吗?我相信还有更好的解决办法。地图的目的是保持独特的关键是有充分的理由的。虽然如果你真的想这样做,你总是可以扩展类编写一个简单的自定义映射类,它有一个冲突缓解功能,并将使你能够保持多个条目与相同的关键字。

注意: 您必须实现碰撞缓解功能,以便将碰撞键转换为唯一的集合“总是”。一些简单的东西,比如用对象散列码附加键之类的?

您正在搜索一个 multimap,实际上 commons-Collection 和 Guava 都有多个实现。多重映射通过维护每个键值的集合来支持多个键,也就是说,您可以将一个对象放入映射中,但是您可以检索一个集合。

如果您可以使用 Java5,我更喜欢 Guava 的 Multimap,因为它支持泛型。

如果您希望对键-值对列表进行迭代(正如您在注释中所写的那样) ,那么 List 或数组应该更好。首先组合你的键和值:

public class Pair
{
public Class1 key;
public Class2 value;


public Pair(Class1 key, Class2 value)
{
this.key = key;
this.value = value;
}


}

用要用于键和值的类型替换 Class1和 Class2。

现在,您可以将它们放入数组或列表中,并对它们进行迭代:

Pair[] pairs = new Pair[10];
...
for (Pair pair : pairs)
{
...
}

为了完整起见,ApacheCommons 集合还有一个 多重地图。当然,缺点是 ApacheCommons 不使用泛型。

如果有重复的键,那么一个键可能对应于多个值。显而易见的解决方案是将键映射到这些值的列表。

例如在 Python 中:

map = dict()
map["driver"] = list()
map["driver"].append("john")
map["driver"].append("mike")
print map["driver"]          # It shows john and mike
print map["driver"][0]       # It shows john
print map["driver"][1]       # It shows mike

我们不需要依赖 Google Collection 外部库:

Map<String, ArrayList<String>> hashMap = new HashMap<String, ArrayList>();


public static void main(String... arg) {
// Add data with duplicate keys
addValues("A", "a1");
addValues("A", "a2");
addValues("B", "b");
// View data.
Iterator it = hashMap.keySet().iterator();
ArrayList tempList = null;


while (it.hasNext()) {
String key = it.next().toString();
tempList = hashMap.get(key);
if (tempList != null) {
for (String value: tempList) {
System.out.println("Key : "+key+ " , Value : "+value);
}
}
}
}


private void addValues(String key, String value) {
ArrayList tempList = null;
if (hashMap.containsKey(key)) {
tempList = hashMap.get(key);
if(tempList == null)
tempList = new ArrayList();
tempList.add(value);
} else {
tempList = new ArrayList();
tempList.add(value);
}
hashMap.put(key,tempList);
}

请确保对代码进行微调。

从我的错误中吸取教训... 请不要独自执行。 番石榴多图是正确的选择。

多重映射中需要的一个常见增强是不允许重复的键-值对。

在您的实现中实现/更改它可能很烦人。

番石榴的原理很简单:

HashMultimap<String, Integer> no_dupe_key_plus_val = HashMultimap.create();


ArrayListMultimap<String, Integer> allow_dupe_key_plus_val = ArrayListMultimap.create();

这个问题有一个稍微不同的变体: 需要将两个不同的值与相同的键关联起来。为了帮助其他人,我在这里引入了一个 HashMap 作为值:

/* @param frameTypeHash: Key -> Integer (frameID), Value -> HashMap (innerMap)
@param innerMap: Key -> String (extIP), Value -> String
If the key exists, retrieve the stored HashMap innerMap
and put the constructed key, value pair
*/
if (frameTypeHash.containsKey(frameID)){
//Key exists, add the key/value to innerHashMap
HashMap innerMap = (HashMap)frameTypeHash.get(frameID);
innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);


} else {
HashMap<String, String> innerMap = new HashMap<String, String>();
innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);
// This means the key doesn't exists, adding it for the first time
frameTypeHash.put(frameID, innerMap );
}
}

在上面的代码中,关键帧 ID 是从每行输入文件的第一个字符串中读取的,frameTypeHash 的值是通过拆分剩余的行构造的,最初是作为 String 对象存储的,在一段时间内,文件开始有多行(具有不同的值)与相同的 frameID 键相关联,所以 frameTypeHash 被覆盖,最后一行作为值。我将 String 对象替换为另一个 HashMap 对象作为 value 字段,这有助于维护到不同值映射的单个键。

Multimap<Integer, String> multimap = ArrayListMultimap.create();


multimap.put(1, "A");
multimap.put(1, "B");
multimap.put(1, "C");
multimap.put(1, "A");


multimap.put(2, "A");
multimap.put(2, "B");
multimap.put(2, "C");


multimap.put(3, "A");


System.out.println(multimap.get(1));
System.out.println(multimap.get(2));
System.out.println(multimap.get(3));

产出为:

[A,B,C,A]
[A,B,C]
[A]

注意: 我们需要导入库文件。

Http://www.java2s.com/code/jar/g/downloadgooglecollectionsjar.htm

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

https://commons.apache.org/proper/commons-collections/download_collections.cgi

import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;

通过一点技巧,你可以使用带有重复键的 HashSet。

class MultiKeyPair {
Object key;
Object value;


public MultiKeyPair(Object key, Object value) {
this.key = key;
this.value = value;
}


@Override
public int hashCode() {
return key.hashCode();
}
}


class MultiKeyList extends MultiKeyPair {
ArrayList<MultiKeyPair> list = new ArrayList<MultiKeyPair>();


public MultiKeyList(Object key) {
super(key, null);
}


@Override
public boolean equals(Object obj) {
list.add((MultiKeyPair) obj);
return false;
}
}


public static void main(String[] args) {
HashSet<MultiKeyPair> set = new HashSet<MultiKeyPair>();
set.add(new MultiKeyPair("A","a1"));
set.add(new MultiKeyPair("A","a2"));
set.add(new MultiKeyPair("B","b1"));
set.add(new MultiKeyPair("A","a3"));


MultiKeyList o = new MultiKeyList("A");
set.contains(o);


for (MultiKeyPair pair : o.list) {
System.out.println(pair.value);
}
}

我用了这个:

java.util.List<java.util.Map.Entry<String,Integer>> pairList= new java.util.ArrayList<>();

class  DuplicateMap<K, V>
{
enum MapType
{
Hash,LinkedHash
}


int HashCode = 0;
Map<Key<K>,V> map = null;


DuplicateMap()
{
map = new HashMap<Key<K>,V>();
}


DuplicateMap( MapType maptype )
{
if ( maptype == MapType.Hash ) {
map = new HashMap<Key<K>,V>();
}
else if ( maptype == MapType.LinkedHash ) {
map = new LinkedHashMap<Key<K>,V>();
}
else
map = new HashMap<Key<K>,V>();
}


V put( K key, V value  )
{


return map.put( new Key<K>( key , HashCode++ ), value );
}


void putAll( Map<K, V> map1 )
{
Map<Key<K>,V> map2 = new LinkedHashMap<Key<K>,V>();


for ( Entry<K, V> entry : map1.entrySet() ) {
map2.put( new Key<K>( entry.getKey() , HashCode++ ), entry.getValue());
}
map.putAll(map2);
}


Set<Entry<K, V>> entrySet()
{
Set<Entry<K, V>> entry = new LinkedHashSet<Map.Entry<K,V>>();
for ( final Entry<Key<K>, V> entry1 : map.entrySet() ) {
entry.add( new Entry<K, V>(){
private K Key = entry1.getKey().Key();
private V Value = entry1.getValue();


@Override
public K getKey() {
return Key;
}


@Override
public V getValue() {
return Value;
}


@Override
public V setValue(V value) {
return null;
}});
}


return entry;
}


@Override
public String toString() {
StringBuilder builder = new  StringBuilder();
builder.append("{");
boolean FirstIteration = true;
for ( Entry<K, V> entry : entrySet() ) {
builder.append( ( (FirstIteration)? "" : "," ) + ((entry.getKey()==null) ? null :entry.getKey().toString() ) + "=" + ((entry.getValue()==null) ? null :entry.getValue().toString() )  );
FirstIteration = false;
}
builder.append("}");
return builder.toString();
}


class Key<K1>
{
K1 Key;
int HashCode;


public Key(K1 key, int hashCode) {
super();
Key = key;
HashCode = hashCode;
}


public K1 Key() {
return Key;
}


@Override
public String toString() {
return  Key.toString() ;
}


@Override
public int hashCode() {


return HashCode;
}
}

这个问题可以通过一个地图条目 List<Map.Entry<K,V>>的列表来解决。我们既不需要使用外部库,也不需要使用 Map 的新实现。可以像下面这样创建映射条目: Map.Entry<String, Integer> entry = new AbstractMap.SimpleEntry<String, Integer>("key", 1);

 1, Map<String, List<String>> map = new HashMap<>();

这种冗长的解决方案有多个缺点,并且容易出错 意味着我们需要为每个值实例化一个 Collection,检查 在添加或删除一个值之前,如果没有添加或删除该值,请手动删除该值 价值观是剩下的,等等。

2, org.apache.commons.collections4.MultiMap interface
3, com.google.common.collect.Multimap interface

Java-map-copy-key

那么这样的 MultiMap 呢?

public class MultiMap<K, V> extends HashMap<K, Set<V>> {
private static final long serialVersionUID = 1L;
private Map<K, Set<V>> innerMap = new HashMap<>();


public Set<V> put(K key, V value) {
Set<V> valuesOld = this.innerMap.get(key);
HashSet<V> valuesNewTotal = new HashSet<>();
if (valuesOld != null) {
valuesNewTotal.addAll(valuesOld);
}
valuesNewTotal.add(value);
this.innerMap.put(key, valuesNewTotal);
return valuesOld;
}


public void putAll(K key, Set<V> values) {
for (V value : values) {
put(key, value);
}
}


@Override
public Set<V> put(K key, Set<V> value) {
Set<V> valuesOld = this.innerMap.get(key);
putAll(key, value);
return valuesOld;
}


@Override
public void putAll(Map<? extends K, ? extends Set<V>> mapOfValues) {
for (Map.Entry<? extends K, ? extends Set<V>> valueEntry : mapOfValues.entrySet()) {
K key = valueEntry.getKey();
Set<V> value = valueEntry.getValue();
putAll(key, value);
}
}


@Override
public Set<V> putIfAbsent(K key, Set<V> value) {
Set<V> valueOld = this.innerMap.get(key);
if (valueOld == null) {
putAll(key, value);
}
return valueOld;
}


@Override
public Set<V> get(Object key) {
return this.innerMap.get(key);
}


@Override
etc. etc. override all public methods size(), clear() .....


}

不需要花哨的解说词。 地图是由一个唯一的键定义的,所以不要弯曲他们,使用一个列表。流是强大的。

import java.util.AbstractMap.SimpleImmutableEntry;


List<SimpleImmutableEntry<String, String>> nameToLocationMap = Arrays.asList(
new SimpleImmutableEntry<>("A", "A1"),
new SimpleImmutableEntry<>("A", "A2"),
new SimpleImmutableEntry<>("B", "B1"),
new SimpleImmutableEntry<>("B", "B1"),
);

就是这样。 用法例子:

List<String> allBsLocations = nameToLocationMap.stream()
.filter(x -> x.getKey().equals("B"))
.map(x -> x.getValue())
.collect(Collectors.toList());


nameToLocationMap.stream().forEach(x ->
do stuff with: x.getKey()...x.getValue()...

您可以将 TreeMap 与自定义 Compaator 一起使用,以便将每个键视为不等于其他键。它还将保留映射中的插入顺序,就像 LinkedHashMap 一样。因此,最终结果将类似于允许重复键的 LinkedHashMap!

这是一个非常简单的实现,不需要任何第三方依赖或 MultiMaps 的复杂性。

import java.util.Map;
import java.util.TreeMap;


...
...


//Define a TreeMap with a custom Comparator
Map<Integer, String> map = new TreeMap<>((a, b) -> 1); // See notes 1 and 2


//Populate the map
map.put(1, "One");
map.put(3, "Three");
map.put(1, "One One");
map.put(7, "Seven");
map.put(2, "Two");
map.put(1, "One One One");
    

//Display the map entries:
map.entrySet().forEach(System.out::println);


//See note number 3 for the following:
Map<Integer, String> sortedTreeMap = map.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(x, y) -> x, () -> new TreeMap<>((a, b) -> 1)
));
//Display the entries of this sorted TreeMap:
sortedTreeMap.entrySet().forEach(System.out::println);


    

...

备注:

  1. 您还可以在这里的比较器定义中使用任何正整数来代替1。
  2. 如果您使用任何负整数代替,那么它将反转您的映射中的插入顺序。
  3. 如果还希望根据键(TreeMap 的默认行为)对此映射进行排序,则可以在当前映射上执行此操作。