如何在Java中创建一个双向映射?

64

我需要一个数据结构来存储字符串-整数值对,这些值之间是1:1的关系,并且能够从任意一方查找对应项。

我编写了一个包含哈希表和字符串数组的类,并将数据存储了两次,并使用内置函数进行查找。

我的问题是,有没有更好的方法实现这个需求?更好的意思是既高效又不用存储两次数据,最好也不需要写太多代码 :P。


请查看这个简单的答案 https://dev59.com/ikzSa4cB1Zd3GeqPrPmH#39329515 - Sibin John Mattappallil
可能是Java是否有带有反向查找的HashMap?的重复问题。 - blahdiblah
7个回答

58

看起来您可能正在寻找一个双向映射表

Guava中包含一个BiMap接口和一些实现。

根据BiMap的文档:

bimap(或“双向映射表”)是一种保留其值唯一性和键唯一性的映射表。此约束使得双向映射表能够支持“反向视图”,即另一个双向映射表,其中包含与此双向映射表相同的条目,但具有反转的键和值。

BiMap.inverse方法似乎返回一个Map,其值作为键,键作为值,这样可以使用Map调用get获得键。

此外,inverse方法返回的Map是基于底层数据的视图,因此无需复制原始数据。

BiMap.inverse方法的文档中可以了解到:

返回此bimap的反向视图,将此bimap的每个值映射到其关联键。这两个双向映射表都由相同的数据支持;对一个映射表所做的任何更改都将出现在另一个映射表中。


答案中的Javadoc链接已不再有效。请使用以下链接替代:https://google.github.io/guava/releases/snapshot-jre/api/docs/com/google/common/collect/BiMap.html - Jim Davis
提供一个使用该库的实际示例会很好。它只是一个带有大量实现的接口。 - mjs

35

你可以这样简单地实现。请注意,数据在此实现中未被复制,只有引用!我已添加了add和get的实现。remove和其他所需方法留作练习 :)

public class TwoWayHashmap<K extends Object, V extends Object> {

  private Map<K,V> forward = new Hashtable<K, V>();
  private Map<V,K> backward = new Hashtable<V, K>();

  public synchronized void add(K key, V value) {
    forward.put(key, value);
    backward.put(value, key);
  }

  public synchronized V getForward(K key) {
    return forward.get(key);
  }

  public synchronized K getBackward(V key) {
    return backward.get(key);
  }
}

当然,确保“值”唯一性是应用程序的责任。 示例用法:

TwoWayHashmap twmap = new TwoWayHashmap<String, String>();
twmap.add("aaa", "bbb");
twmap.add("xxx", "yyy");
System.out.println(twmap.getForward("xxx"));
System.out.println(twmap.getBackward("bbb"));

2
如果没有Guava的BiMap,这将是一种有趣的方法。然而,我建议您让这个类实现Map接口。 - Noel M
1
如果您需要带有Map接口的类,可以使用Map<K, V> inverse()替换getBackward方法,该方法将返回反向映射。 - Андрей Москвичёв
K继承Object几乎没有必要。 - mjs

14

Apache Commons 还包含BidiMap(双向映射)。

定义了一种允许在键和值之间进行双向查找的映射。

这个扩展 Map 表示一种映射,其中一个键可以轻松查找一个值,一个值也可以轻松查找一个键。该接口扩展了 Map 接口,因此可在任何需要 Map 的地方使用。该接口提供了一个反转的映射视图,使得完全可以访问 BidiMap 的两个方向。


5

好的,搞定了!我试着搜索“2 way map”,但没找到结果。看来“bidirectional map”才是正确的术语。 - sekmet64
答案中的Javadoc链接已不再有效。请使用以下链接替代:https://google.github.io/guava/releases/snapshot-jre/api/docs/com/google/common/collect/BiMap.html - Jim Davis

4
使用 Guava
    HashBiMap<String, String> map = HashBiMap.create();

    map.put("name", "Sohail");
    map.put("country", "Pakistan");

    Log.d("tag", "name is " + map.get("name"));


    BiMap<String, String>invmap= map.inverse();

    Log.d("tag", "Pakistan is a " + invmap.get("Pakistan"));

阅读完整教程在这里。


1

一定不需要写很多代码 → 使用lambda
从您的 Map<String,Integer> map 中,您可以通过以下方式获得反转后的映射:

Map<Integer,String> inverted = map.keySet().stream().collect(Collectors.toMap( s -> map.get( s ), s -> s ) );

-4
创建一个将Object映射到Object的哈希表 - 然后您可以使用相同的哈希表来存储String-> Integer和Integer-> String。
当您添加字符串/整数对时,只需将其双向添加到同一哈希表中即可。

3
这个实现方式并不太明智。你抛弃了所有泛型类型的安全性,还使得操作地图时的复杂度增加了,因为会双倍添加条目。这里也没有封装你所实现的双向映射机制。 - Tom
@Tom - 我匆忙写下了答案,并假设实现者足够明智,会将哈希映射包装在一个控制访问并执行所需类型转换的类中。我将在以后更清楚地表达这一点。 - Dave Kirby
1
你可能会遇到的另一个问题是存储A->B和B->C,这在问题上应该没问题,但在你的答案中不行。 - DerMike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接