如何使用LinkedHashMap的键获取键/值在其中的位置

41

我有一个名为infoLinkedHashMap,它包含名字/年龄(字符串/整数)对。如果我输入键,如何获取该键/值的位置?例如,如果我的LinkedHashMap如下所示:{bob=12, jeremy=42, carly=21},并且我要搜索jeremy,它应该返回1,因为它在第一个位置。我希望我可以使用类似于info.getIndex("jeremy")这样的东西。


7个回答

32

HashMap 的实现通常是无序的,不适合进行 Iteration

LinkedHashMap 是可预测的有序的,适合进行 Iteration(插入顺序),但它不公开 List 接口,而且 LinkedList(它反映了键集的插入顺序)也不会跟踪索引位置,因此查找索引非常低效。 LinkedHashMap 也不公开内部 LinkedList 的引用。

实际的“链表”行为是特定于实现的。有些可能实际上使用 LinkedList 的实例,有些可能只是让 Entry 跟踪前一个和后一个 Entry 并将其用作实现。在查看源代码之前不要做任何假设。

包含键的 KeySet 也不能保证顺序,因为继承的 HashMap 在放置时使用哈希算法,所以你不能使用它。

唯一的方法是遍历使用镜像LinkedListIterator,并保持计数器记录位置,但这对于大数据集非常低效。

解决方案

听起来你想要的是原始插入顺序索引位置,你需要在KeySet中镜像键到类似ArrayList的东西中,与HashMap的更新同步,并用它来查找位置。创建一个HashMap的子类,比如IndexedHashMap,在内部添加这个ArrayList,并添加一个.getKeyIndex(<K> key),委托给内部的ArrayList .indexOf()可能是最好的方法。

这就是LinkedHashMap所做的,但是使用镜像LinkedList代替ArrayList来镜像KeySet


1
我认为LinkedHashMap可以保留顺序。有没有什么东西可以存储键/值但保持顺序? - M9A
6
它保留了顺序,但不追踪位置 - user177800
@HernánEche请仔细阅读问题和答案,他们也想跟踪位置,我在我的答案中详细说明了这一点。 - user177800
@HernánEche HashSet顺序 可能会在每次插入或删除时发生变化,这并不是有保证的,所以我是正确的,你不能使用它! - user177800

28
int pos = new ArrayList<String>(info.keySet()).indexOf("jeremy")

9
忽略上面的评论。LinkedHashMapkeySet() 方法返回键的顺序是有保证的。参考链接:https://dev59.com/YXA85IYBdhLWcg3wJf8Z. - ykaganovich

5

我在这个问题的一个重复问题中看到了一个建议,链接如下:

如何根据索引而不是键从LinkedHashMap获取值?

我喜欢评论中@schippi所描述的伪代码建议。我认为一些可用的Java代码可能对其他人有用。

import java.util.ArrayList;
import java.util.LinkedHashMap;

public class IndexedLinkedHashMap<K,V> extends LinkedHashMap<K,V> {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    ArrayList<K> al_Index = new ArrayList<K>();

    @Override
    public V put(K key,V val) {
        if (!super.containsKey(key)) al_Index.add(key);
        V returnValue = super.put(key,val);
        return returnValue;
    }

    public V getValueAtIndex(int i){
        return (V) super.get(al_Index.get(i));
    }

    public K getKeyAtIndex(int i) {
        return (K) al_Index.get(i);
    }

    public int getIndexOf(K key) {
        return al_Index.indexOf(key);
    }

}

1
考虑到LinkedHashMap保持插入顺序,您可以像这样使用keySet()和List.copyOf()方法(自Java 10开始):
List<String> keys = List.copyOf( yourLinkedHashMap.keySet() );

System.out.println( keys.indexOf("jeremy") ); // prints '1'

0
你可以使用来自Google Guava库com.google.common.collect.LinkedListMultimap。你不需要这个类的多重映射行为,你想要的是keys()方法保证以插入顺序返回它们,然后可以用来构造一个List,你可以使用indexOf()来找到所需的索引位置。

0
LinkedHashMap具有“可预测的迭代顺序”(javadoc)。 项目不知道它们的位置,所以您必须迭代集合才能获取它。 如果您正在维护一个大型映射,则可能需要使用不同的结构进行存储。
编辑:澄清迭代。

“遍历Set键不会对您有任何好处,因为它由Set支持,并且是无序的。 LinkedList仅用于Iterator当有疑问时,请使用源代码。” - user177800
确实。我是指条目集。这就是为什么我引用了迭代顺序的部分...暗示了迭代器的使用。我在措辞上选择不当。 - Mike D
Javadoc中的第一段说它是按插入顺序排列的,这正是他所要求的。无论如何,我认为我们在这里说的是同样的事情。编辑使你的答案更清晰了。 - Mike D
他们想要按插入顺序通过获取位置,如果不使用带有标准LinkedHashMapIterator,就无法实现这一点。无论是keyset还是entryset都不能单独提供他们所要求的内容。 - user177800

0
我会将关键字的位置提取到一个并发映射中,就像这样:
对于一个Map,someListOfComplexObject() 将作为entrySet(), getComplexStringKeyElem() 将作为getKey()
可能来自:
 final int[] index = {0};
 Stream<ComplexObject> t = someListOfComplexObject.stream();
 ConcurrentMap<String, List<Integer>> m = 
      t.collect(Collectors.groupingBy(
           e -> e.getComplexStringKeyElem(),
           Collectors.mapping(
                e -> index[0]++,
                Collectors.toList()
           ),
           ConcurrentSkipListMap::new));

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