Java Hashmap:如何从价值中获取关键?


450

如果我有value "foo",并且HashMap<String> ftw为其ftw.containsValue("foo")返回a true,那么如何获取对应的密钥?我是否必须遍历哈希图?最好的方法是什么?


72
请注意,没有单个对应的键-可能有多个键映射到相同的值。
CPerkins

1
@CPerkins,但是对于像<strFilename,Reader>之类的哈希图,以及许多其他1到1,它非常有用。
水瓶座力量

如果您有少量物品,请考虑创建常量, public static final String TIME = "time";以及properties.put(TIME, PbActivityJpa_.time);
Basheer AL-MOMANI

可能可以帮助您从地图上获取数据,并且还可以执行某种处理。frugalisminds.com/java/java-8-stream-map-examples
Sanjay-Dev

Answers:


215

如果您选择使用Commons Collections库而不是标准Java Collections API,则可以轻松实现此目的。

Collections库中的BidiMap接口是一个双向映射,使您可以将键映射到值(如法线映射),也可以将值映射到键,从而允许您在两个方向上执行查找。getKey()方法支持获取值的键。

需要注意的是,bidi映射不能将多个值映射到键,因此,除非您的数据集在键和值之间具有1:1映射,否则您不能使用bidimaps。

更新资料

如果要依赖Java Collections API,则必须在将值插入映射时确保键和值之间的1:1关系。说起来容易做起来难。

一旦可以确保使用,请使用entrySet()方法获取Map中的一组条目(映射)。获得类型为Map.Entry的集合后,请遍历条目,将存储的值与期望值进行比较,并获得对应的key

更新#2

可以在Google Guava和重构的Commons-Collections库(后者不是Apache项目)中找到对带有泛型的比迪地图的支持。感谢Esko指出Apache Commons Collections中缺少的通用支持。将集合与泛型一起使用可使代码更易于维护。


23
...并且,如果您喜欢泛型和所有现代的东西,则Google Collections提供了BiMap,您可以在其中通过调用biMap.inverse()。get(value)获得键匹配的指定值
Esko

1
是的,Apache Commons Collections不支持泛型。但是,正如您所指出的那样,有Google Collections(我还没有使用-尚无1.0版本),并且有重构的Commons-Collections支持Generics。您可以在Sourceforge项目中找到它@ sourceforge.net/projects/collections
Vineet Reynolds

2
Google收藏夹不是 Commons-Collections的重构版本。
whiskeysierra

12
@whiskeysierra:我认为目前没有人这么说。
一怒之下


603

如果您的数据结构在键和值之间具有多对一映射,则应遍历条目并选择所有合适的键:

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);

3
您能谈谈演出吗?什么会更优化?这还是BidiMap?
tasomaniac

我曾想过相同的解决方案,我当然赞成它,但是对于真正的大型馆藏,我怀疑它的效率。
arjacsoh

3
stackoverflow.com/questions/4553624/hashmap-get-put-complexity HashMap具有时间复杂度o(1)。如果您遍历这些值,那么它将破坏性能。如果您想要better performance和有one-one关系,可以使用another mapwherevalue is a key
veer7

3
我建议替换为.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>表示法时有困难...这有什么意义...不用它的不同方法呢?谢谢
weringingdev

76
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 

在这种情况下,您可以使用第二个哈希图来获取密钥。


19

我认为你的选择是

  • 使用为此目的而构建的地图实现,例如Google集合中的BiMap。请注意,Google集合BiMap要求值和键都是唯一的,但在双向性能上却表现出色
  • 手动维护两个映射-一个映射为key-> value,另一个映射为value-> key
  • 遍历entrySet()和查找与值匹配的键。这是最慢的方法,因为它需要遍历整个集合,而其他两种方法则不需要。

19

您可以将键,值对及其反向插入到地图结构中

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

然后使用map.get(“ theValue”)将返回“ theKey”。

我制作了常量映射是一种快速而肮脏的方法,它仅适用于少数几个数据集:

  • 仅包含1对1
  • 值集与键集不相交(1-> 2,2-> 3断开键)

4
这不是真的正确。这不仅需要1-1,而且值的集合与键的集合是不相交的。您不能将其应用于双射映射{1-> 2,2-> 3}:2既是值又是键。
路易斯·弗洛里特

15

用自己的实现装饰地图

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 '16

11

没有明确的答案,因为多个键可以映射到相同的值。如果要使用自己的代码强制唯一性,则最佳解决方案是创建一个使用两个Hashmap来跟踪两个方向上的映射的类。


11

要查找映射到该值的所有键,请使用遍历哈希图中的所有对map.entrySet()


4
这种解决方案非常费力,以至于在大型HashMap上不切实际。
Joehot200年

10

使用Java 8:

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

6
value=="foo" 这是行不通的。equals应该用来比较字符串。
安东·巴拉纽克

@Anton,除非value被拘留,否则为真。
frododot

9

如果您使用自己的代码构建地图,请尝试将键和值放到地图中:

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的问题。您的意思是“然后,当您拥有价值时,您也拥有钥匙”。
李强

9

我认为这是最好的解决方案,原始地址: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;
          }
        }

一种简单的用法:如果将所有数据都放入hasMap中,并且具有item =“ Automobile”,那么您将在hashMap中查找其键。那是很好的解决方案。

getKeyFromValue(hashMap, item);
System.out.println("getKeyFromValue(hashMap, item): "+getKeyFromValue(hashMap, item));

7

恐怕您只需要迭代地图即可。我能想到的最短的时间是:

Iterator<Map.Entry<String,String>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
    Map.Entry<String,String> entry = iter.next();
    if (entry.getValue().equals(value_you_look_for)) {
        String key_you_look_for = entry.getKey();
    }
}

6
for(int key: hm.keySet()) {
    if(hm.get(key).equals(value)) {
        System.out.println(key); 
    }
}

6

听起来最好的方法是让您使用来遍历条目,map.entrySet()因为map.containsValue()无论如何都可以这样做。


是的,这就是它的作用。但是,当然,一旦找到一个等于.equals的值(而不是OP可能需要执行的操作),它就会返回true。
CPerkins

1
好吧,迭代条目也可以在找到匹配值后立即以key返回。似乎没有多场比赛。
乔纳斯·K

6

对于面向API <19的Android开发,Vitalii Fedorenko一对一关系解决方案由于Objects.equals未实现而无法使用。这是一个简单的选择:

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

这个解决方案对我有用;我还开发了一个考古Android版本,以获取“ onMarkerClick”事件中保存在地图中的Google Map标记的密钥为例。迭代entrySet起作用;但是没有迭代键,并使用get()将它们与条目匹配,然后比较输出。
托比·威尔逊

3

您可以使用以下内容:

public class HashmapKeyExist {
    public static void main(String[] args) {
        HashMap<String, String> hmap = new HashMap<String, String>();
        hmap.put("1", "Bala");
        hmap.put("2", "Test");

        Boolean cantain = hmap.containsValue("Bala");
        if(hmap.containsKey("2") && hmap.containsValue("Test"))
        {
            System.out.println("Yes");
        }
        if(cantain == true)
        {
            System.out.println("Yes"); 
        }

        Set setkeys = hmap.keySet();
        Iterator it = setkeys.iterator();

        while(it.hasNext())
        {
            String key = (String) it.next();
            if (hmap.get(key).equals("Bala"))
            {
                System.out.println(key);
            }
        }
    }
}

您很想提供有用的东西,但是它不应该是“仅代码”的答案,并且代码本身也不应该充满代码的味道。
汤姆(Tom)

2

是的,除非您按照这些不同答案所建议的方式实施某些操作,否则您必须遍历哈希图。我只需要获取keySet(),然后遍历该集合,然后保留(第一个)键即可获得匹配的值,而不必摆弄entrySet。如果您需要与该值匹配的所有键,那么显然您必须做整个事情。

正如乔纳斯(Jonas)所建议的,这可能已经是containsValue方法的作用,因此您可以完全跳过该测试,并且每次都进行迭代(或者编译器已经消除了冗余,这是众所周知的)。

另外,相对于其他答案,如果您的反向映射看起来像

Map<Value, Set<Key>>

如果需要,可以处理非唯一的key-> value映射(将它们解开)。这可以很好地结合到人们使用两个地图建议的任何解决方案中。


2

您可以使用以下代码使用值来获取密钥。

ArrayList valuesList = new ArrayList();
Set keySet = initalMap.keySet();
ArrayList keyList = new ArrayList(keySet);

for(int i = 0 ; i < keyList.size() ; i++ ) {
    valuesList.add(initalMap.get(keyList.get(i)));
}

Collections.sort(valuesList);
Map finalMap = new TreeMap();
for(int i = 0 ; i < valuesList.size() ; i++ ) {
    String value = (String) valuesList.get(i);

    for( int j = 0 ; j < keyList.size() ; j++ ) {
        if(initalMap.get(keyList.get(j)).equals(value)) {
            finalMap.put(keyList.get(j),value);
        }   
    }
}
System.out.println("fianl map ---------------------->  " + finalMap);

2
public static class SmartHashMap <T1 extends Object, T2 extends Object> {
    public HashMap<T1, T2> keyValue;
    public HashMap<T2, T1> valueKey;

    public SmartHashMap(){
        this.keyValue = new HashMap<T1, T2>();
        this.valueKey = new HashMap<T2, T1>();
    }

    public void add(T1 key, T2 value){
        this.keyValue.put(key, value);
        this.valueKey.put(value, key);
    }

    public T2 getValue(T1 key){
        return this.keyValue.get(key);
    }

    public T1 getKey(T2 value){
        return this.valueKey.get(value);
    }

}

我认为可以通过添加解释来改善此答案。
乔纳森(Jonathan)

2
-1。我测试了它的String关键和价值。当我打电话时,map.add("1", "2"); map.add("1","3");我可以打电话map.getKey("2");和找回"1",即使这"1"是的关键"3"
jlordo

@Jonathan此类的想法是使用反向映射保留另一个HashMap,以便除了从键检索值之外,还可以从值检索键。T1和T2类有点让人困惑。也许从字面上将其命名为“键与值”?尽管我希望能够接收到一个以上的值或一个以上的键作为回报,但这取决于数据和所需内容。谨慎使用
Chicowitz 2014年

1
@theknightwhosaysni“ 1”不再是“ 2”的键(不再)。这也是您问题的答案,电话getValue("1")会返回3
jlordo 2014年

抱歉,jlordo,我对标准Hashmap行为感到误解:您是正确的,因为为键添加新值应该替换旧值
Chicowitz 2014年

2

在java8中

map.entrySet().stream().filter(entry -> entry.getValue().equals(value))
    .forEach(entry -> System.out.println(entry.getKey()));

2
public static String getKey(Map<String, Integer> mapref, String value) {
    String key = "";
    for (Map.Entry<String, Integer> map : mapref.entrySet()) {
        if (map.getValue().toString().equals(value)) {
            key = map.getKey();
        }
    }
    return key;
}

Map <String,Integer> map = new HashMap <String,Integer>(); map.put(“ A”,1); map.put(“ B”,2); map.put(“ C”,3); map.put(“ D”,4); // System.out.println(map); System.out.println(getKey(map,“ 4”));
Amazing India

1
如果多个键具有相同的值会怎样?
CàPHEđen

当u传递时,多个键具有相同的值,我们将得到最后一个键作为结果。示例:A 1,B 1,C 1,D 2输出:如果我们传递1值,则输出将为C
Amazing India

@AmazingIndia无法保证,这完全取决于特定的地图实现。例如,HashMap不能保证订单,因此您不知道将在此处返回什么输出。
Niels Doucet

1
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class ValueKeysMap<K, V> extends HashMap <K,V>{
    HashMap<V, Set<K>> ValueKeysMap = new HashMap<V, Set<K>>();

    @Override
    public boolean containsValue(Object value) {
        return ValueKeysMap.containsKey(value);
    }

    @Override
    public V put(K key, V value) {
        if (containsValue(value)) {
            Set<K> keys = ValueKeysMap.get(value);
            keys.add(key);
        } else {
            Set<K> keys = new HashSet<K>();
            keys.add(key);
            ValueKeysMap.put(value, keys);
        }
        return super.put(key, value);
    }

    @Override
    public V remove(Object key) {
        V value = super.remove(key);
        Set<K> keys = ValueKeysMap.get(value);
        keys.remove(key);
        if(keys.size() == 0) {
           ValueKeysMap.remove(value);
        }
        return value;
    }

    public Set<K> getKeys4ThisValue(V value){
        Set<K> keys = ValueKeysMap.get(value);
        return keys;
    }

    public boolean valueContainsThisKey(K key, V value){
        if (containsValue(value)) {
            Set<K> keys = ValueKeysMap.get(value);
            return keys.contains(key);
        }
        return false;
    }

    /*
     * Take care of argument constructor and other api's like putAll
     */
}

1
/**
 * This method gets the Key for the given Value
 * @param paramName
 * @return
 */
private String getKeyForValueFromMap(String paramName) {
    String keyForValue = null;
    if(paramName!=null)) {
        Set<Entry<String,String>> entrySet = myMap().entrySet();
        if(entrySet!=null && entrySet.size>0) {
            for(Entry<String,String> entry : entrySet) {
                if(entry!=null && paramName.equalsIgnoreCase(entry.getValue())) {
                    keyForValue = entry.getKey();
                }
            }
        }
    }
    return keyForValue;
}

1
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class M{
public static void main(String[] args) {

        HashMap<String, List<String>> resultHashMap = new HashMap<String, List<String>>();

        Set<String> newKeyList = resultHashMap.keySet();


        for (Iterator<String> iterator = originalHashMap.keySet().iterator(); iterator.hasNext();) {
            String hashKey = (String) iterator.next();

            if (!newKeyList.contains(originalHashMap.get(hashKey))) {
                List<String> loArrayList = new ArrayList<String>();
                loArrayList.add(hashKey);
                resultHashMap.put(originalHashMap.get(hashKey), loArrayList);
            } else {
                List<String> loArrayList = resultHashMap.get(originalHashMap
                        .get(hashKey));
                loArrayList.add(hashKey);
                resultHashMap.put(originalHashMap.get(hashKey), loArrayList);
            }
        }

        System.out.println("Original HashMap : " + originalHashMap);
        System.out.println("Result HashMap : " + resultHashMap);
    }
}

1

使用薄包装器:HMap

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

public class HMap<K, V> {

   private final Map<K, Map<K, V>> map;

   public HMap() {
      map = new HashMap<K, Map<K, V>>();
   }

   public HMap(final int initialCapacity) {
      map = new HashMap<K, Map<K, V>>(initialCapacity);
   }

   public boolean containsKey(final Object key) {
      return map.containsKey(key);
   }

   public V get(final Object key) {
      final Map<K, V> entry = map.get(key);
      if (entry != null)
         return entry.values().iterator().next();
      return null;
   }

   public K getKey(final Object key) {
      final Map<K, V> entry = map.get(key);
      if (entry != null)
         return entry.keySet().iterator().next();
      return null;
   }

   public V put(final K key, final V value) {
      final Map<K, V> entry = map
            .put(key, Collections.singletonMap(key, value));
      if (entry != null)
         return entry.values().iterator().next();
      return null;
   }
}

1

我的2美分。您可以获取数组中的键,然后在数组中循环。如果映射很大,这将影响此代码块的性能,在这种情况下,您首先要获取数组中的键,这可能会花费一些时间,然后循环。否则,对于较小的地图应该没问题。

String[] keys =  yourMap.keySet().toArray(new String[0]);

for(int i = 0 ; i < keys.length ; i++){
    //This is your key    
    String key = keys[i];

    //This is your value
    yourMap.get(key)            
}

为什么有人要使用这种方法?正如您已经说过的,与其他方法相比,性能会更差。
汤姆(Tom)

1

我认为keySet()可能很适合查找映射到该值的键,并且比entrySet()更好的编码风格。

例如:

假设您有一个HashMap 映射 ArrayList res,该值是您想要查找所有键映射的,然后将键存储到res

您可以在下面编写代码:

    for (int key : map.keySet()) {
        if (map.get(key) == value) {
            res.add(key);
        }
    }

而不是使用下面的entrySet():

    for (Map.Entry s : map.entrySet()) {
        if ((int)s.getValue() == value) {
            res.add((int)s.getKey());
        }
    }

希望能帮助到你 :)


map.get(key) == value在检查对象是否相等时,这不是一个好主意,因为您正在比较引用。对象相等性应始终使用其.equals()
frododot

1

尽管这并不能直接回答问题,但却是相关的。

这样,您无需继续创建/迭代。只需创建一次反向映射即可获得所需的内容。

/**
 * Both key and value types must define equals() and hashCode() for this to work.
 * This takes into account that all keys are unique but all values may not be.
 *
 * @param map
 * @param <K>
 * @param <V>
 * @return
 */
public static <K, V> Map<V, List<K>> reverseMap(Map<K,V> map) {
    if(map == null) return null;

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

    for(Map.Entry<K,V> entry : map.entrySet()) {
        appendValueToMapList(reverseMap, entry.getValue(), entry.getKey());
    }

    return reverseMap;
}


/**
 * Takes into account that the list may already have values.
 * 
 * @param map
 * @param key
 * @param value
 * @param <K>
 * @param <V>
 * @return
 */
public static <K, V> Map<K, List<V>> appendValueToMapList(Map<K, List<V>> map, K key, V value) {
    if(map == null || key == null || value == null) return map;

    List<V> list = map.get(key);

    if(list == null) {
        List<V> newList = new ArrayList<>();
        newList.add(value);
        map.put(key, newList);
    }
    else {
        list.add(value);
    }

    return map;
}

0

值得注意的是,由于这个问题,Apache Collections支持Generic BidiMaps。因此,在这一点上,一些投票最多的答案不再准确。

对于也支持重复值的序列化BidiMap(一对多方案),请考虑MapDB.org


0
  1. 如果要从值中获取密钥,最好使用bidimap(双向映射),则可以在O(1)时间内从值中获取密钥。

    但是,这样做的缺点是您只能使用唯一的键集和值集。

  2. Java中有一个名为Table的数据结构,它不过是类似map的map

    表格<A,B,C> ==映射<A,映射<B,C>>

    在这里,您可以map<B,C>通过查询获得T.row(a);,也可以map<A,C>通过查询获得T.column(b);

在特殊情况下,将C插入为常数。

因此,它类似于<a1,b1,1> <a2,b2,1>,...

因此,如果通过T.row(a1)查找--->返回->的映射,则获取此返回映射的键集。

如果您需要查找键值,则T.column(b2)->返回->获取返回映射的键集的映射。

与以前相比的优势:

  1. 可以使用多个值。
  2. 使用大型数据集时效率更高。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.