Java HashMap:如何从值获取键?

528

如果我有一个值为 "foo" 的变量,并且有一个 HashMap<String> ftw ,其中 ftw.containsValue("foo") 返回 true,那么如何获取相应的键? 我必须遍历整个 HashMap 吗?最好的方法是什么?


87
请注意,没有一个唯一对应的键 - 很可能有多个键映射到相同的值。 - CPerkins
得分为527,已有10年经验,但仍然缺少HashMap<String> ftw中的关键类型。 - user16320675
40个回答

662

如果你的数据结构中存在键和值之间的 多对一 映射,你应该迭代所有项并选择所有适当的键:

public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
    Set<T> keys = new HashSet<T>();
    for (Entry<T, E> entry : map.entrySet()) {
        if (Objects.equals(value, entry.getValue())) {
            keys.add(entry.getKey());
        }
    }
    return keys;
}

如果是一对一关系,您可以返回第一个匹配的键:

public static <T, E> T getKeyByValue(Map<T, E> map, E value) {
    for (Entry<T, E> entry : map.entrySet()) {
        if (Objects.equals(value, entry.getValue())) {
            return entry.getKey();
        }
    }
    return null;
}

在Java 8中:

public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
    return map.entrySet()
              .stream()
              .filter(entry -> Objects.equals(entry.getValue(), value))
              .map(Map.Entry::getKey)
              .collect(Collectors.toSet());
}

此外,对于使用Guava的用户来说,BiMap可能会很有用。例如:

BiMap<Token, Character> tokenToChar = 
    ImmutableBiMap.of(Token.LEFT_BRACKET, '[', Token.LEFT_PARENTHESIS, '(');
Token token = tokenToChar.inverse().get('(');
Character c = tokenToChar.get(token);

4
你能谈一下性能吗?哪个更优化?这个还是BidiMap? - tasomaniac
我也考虑过同样的解决方案,当然我已经点赞了,但是对于真正大型集合的效率我还有疑虑。 - arjacsoh
3
HashMap 的时间复杂度为 o(1)。如果您正在遍历值,则会降低性能。如果您想要更好的性能并且具有一对一的关系,则可以使用另一个映射,其中“值是键”。 - veer7
4
е»әи®®е°Ҷ.filter(entry -> entry.getValue().equals(value)) жӣҝжҚўдёә .filter(entry ->Objects.equals(entry.getValue(), value))пјҢеӣ дёәжІЎжңүе…ідәҺnullеҖјзҡ„еЈ°жҳҺгҖӮжӯӨеӨ–пјҢдҪ еҸҜд»Ҙе°Ҷ.map(entry -> entry.getKey()) жӣҝжҚўдёә .map(Map.Entry::getKey)гҖӮ - Holger
我很难理解在 Set<T> getKeysByValue() 方法前的 <T, E> 表示法...这有什么意义...有没有不使用它的不同方法?谢谢。 - ponderingdev
调用需要API Level 24:java.util.Collection#stream - Aliton Oliveira

241
如果您选择使用Commons Collections库而不是标准的Java Collections框架,则可以轻松实现此目的。
集合库中的BidiMap接口是一个双向映射,允许您将键映射到值(像普通映射一样),还可以将值映射到键,从而允许您在两个方向上执行查找。通过getKey()方法支持获取值的键。
但是需要注意的是,双向映射不能将多个值映射到键,因此除非您的数据集具有键和值之间的1:1映射,否则无法使用双向映射。
如果您想依赖Java集合API,您必须确保在将值插入到Map中时,键和值之间存在1:1的关系。这比说起来容易做起来难。一旦您可以确保这一点,使用entrySet()方法获取Map中条目(映射)的集合。一旦您获得了类型为Map.Entry的集合,遍历这些条目,将存储的值与期望值进行比较,并获取相应的键

支持使用泛型的可以在Google Guava和重构后的Commons-Collections库中找到(后者不是Apache项目)。感谢Esko指出了Apache Commons Collections中缺少泛型支持的问题。使用带有泛型的集合可以使代码更易于维护。


version 4.0 版本起,官方的 Apache Commons Collections™ 库支持 泛型

请参阅 "org.apache.commons.collections4.bidimap" 包的 summary 页面,了解现在支持 Java 泛型BidiMapOrderedBidiMapSortedBidiMap 接口的可用实现列表。


26
如果你喜欢泛型和所有现代化的东西,Google Collections中有一个叫做BiMap的类,你可以通过调用biMap.inverse().get(value)来获得与指定值匹配的键。 - Esko
1
是的,Apache Commons Collections不支持泛型。然而,正如你所指出的,有Google Collections(我还没有使用-尚未发布1.0版本),还有重构后支持泛型的Commons-Collections。你可以在Sourceforge项目中找到它:http://sourceforge.net/projects/collections/ - Vineet Reynolds
2
Google Collections不是Commons-Collections的重构版本。 - whiskeysierra
14
我认为目前没有人这样说。 - huff
4
Apache Collections现在支持泛型。https://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/bidimap/package-summary.html - kervin
显示剩余4条评论

89
public class NewClass1 {

    public static void main(String[] args) {
       Map<Integer, String> testMap = new HashMap<Integer, String>();
        testMap.put(10, "a");
        testMap.put(20, "b");
        testMap.put(30, "c");
        testMap.put(40, "d");
        for (Entry<Integer, String> entry : testMap.entrySet()) {
            if (entry.getValue().equals("c")) {
                System.out.println(entry.getKey());
            }
        }
    }
}

一些额外的信息...可能对您有用

如果您的哈希映射表非常大,则上面的方法可能并不好。如果您的哈希映射表包含唯一键到唯一值的映射,则可以维护另一个哈希映射表,该表包含从值到键的映射。

也就是说,您需要维护两个哈希映射表。

1. Key to value

2. Value to key 

在这种情况下,你可以使用第二个哈希表来获取键。


27

您可以将键值对及其相反的形式插入到您的映射结构中。

map.put("theKey", "theValue");
map.put("theValue", "theKey");

使用map.get("theValue")将会返回"theKey"。

这是我制作常量映射的一种快速且简单的方法,但仅适用于少量数据集:

  • 仅包含一对一关系
  • 值的集合与键的集合不相交(1->2, 2->3会破坏它)

4
这并不完全正确。这不仅需要一对一的映射,还要求值集合与键集合不相交。你不能将其应用于双射映射 {1 -> 2, 2 -> 3} :2既是一个值又是一个键。 - Luis A. Florit

23

我认为你有以下几个选择:

  • 使用专门的映射实现,例如来自Google Collections的 BiMap。 需要注意的是,Google Collections的BiMap要求值和键都唯一,但在双向查询时性能较高。
  • 手动维护两个映射 - 一个用于key -> value,另一个用于value -> key。
  • 通过迭代entrySet()查找与值匹配的键。这是最慢的方法,因为它需要遍历整个集合,而其他两种方法不需要这样做。

20

使用 Java 8:

ftw.forEach((key, value) -> {
    if (value.equals("foo")) {
        System.out.print(key);
    }
});

@Anton,除非value已被内部化,否则为真。 - frododot

19

使用您自己的实现来装饰地图

class MyMap<K,V> extends HashMap<K, V>{

    Map<V,K> reverseMap = new HashMap<V,K>();

    @Override
    public V put(K key, V value) {
        // TODO Auto-generated method stub
        reverseMap.put(value, key);
        return super.put(key, value);
    }

    public K getKey(V value){
        return reverseMap.get(value);
    }
}

我认为这是一个有趣的方法,虽然由于关系必须是1:1,我会完全摆脱HashMap并实现Map<K,V>接口,以避免重复的键和值。 - Fran Marzoa

12

我认为这是最好的解决方案,原始地址: Java2s

    import java.util.HashMap;
    import java.util.Map;

        public class Main {

          public static void main(String[] argv) {
            Map<String, String> map = new HashMap<String, String>();
            map.put("1","one");
            map.put("2","two");
            map.put("3","three");
            map.put("4","four");

            System.out.println(getKeyFromValue(map,"three"));
          }


// hm is the map you are trying to get value from it
          public static Object getKeyFromValue(Map hm, Object value) {
            for (Object o : hm.keySet()) {
              if (hm.get(o).equals(value)) {
                return o;
              }
            }
            return null;
          }
        }
一个简单的用法: 如果你将所有数据放在HashMap中,而且你有一个项目 =“汽车”,那么你可以在HashMap中查找其键。这是一个很好的解决方案。
getKeyFromValue(hashMap, item);
System.out.println("getKeyFromValue(hashMap, item): "+getKeyFromValue(hashMap, item));

12

如果您在自己的代码中构建了Map,请尝试将键和值放在一起:

public class KeyValue {
    public Object key;
    public Object value;
    public KeyValue(Object key, Object value) { ... }
}

map.put(key, new KeyValue(key, value));

当你有一个值时,你也拥有了键。


3
聪明,但是如果有两个或更多的KeyValue对象包含相同的值怎么办?应该选择哪个键? - Vineet Reynolds
2
@Vineet,我不明白你的方法如何解决OP的问题。你说的“当你有一个值时,你也有了键”,是什么意思? - Qiang Li

12

由于多个键可以映射到同一个值,所以没有明确的答案。如果您想在自己的代码中强制唯一性,则最好的解决方案是创建一个类,使用两个Hashmap跟踪双向映射。


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