LinkedHashMap的keySet()创建的ArrayList是否保留插入顺序?

5
我获取的数据类似于以下内容:
{"ABC的员工1", "ABCX"}, {"ABC的员工2", "ABCY"}, {"ABC的员工3", "ABCZ"}
这些数据是通过RefCursor从数据库中获取的。
我有一个需求,需要保留从数据库中读取数据的顺序。由于我的数据类似于“键-值”对,我考虑使用一个有序的Map实现。因此选择了LinkedHashMap
//defined as a static map inside a common utlity class
public class Common{
public static final LinkedHashMap<String, String> empMap = new   LinkedHashMap<String, String>();
}

//rs is the resultset
if (rs != null) {
            while (rs.next()) {
                key = rs.getString("name_text");
                value = rs.getString("id");
                Common.empMap.put(key, value);
            }
}

我需要将从数据库中检索到的密钥按照相同的顺序传递给客户端(游标)。

List<String> empList = new ArrayList<String>(Common.empMap.keySet());

keySet() - 文档中表示“返回此映射中包含的键的Set视图。该集合由映射支持,因此对映射的更改会反映在集合中,反之亦然”。

我的期望是,由于ArrayList也是一个有序集合,我应该以相同的方式获取/插入到Map中的键。

当我做了一个示例测试程序时,结果与预期相符。

public class LinkedMap {    
    public static void main(String[] args) {
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();        
        map.put("Employee 1 of ABC", "ABCX");
        map.put("Employee 2 of ABC", "ABCY");
        map.put("Employee 3 of ABC", "ABCZ");   
        ArrayList<String> list = new ArrayList<String>(map.keySet());   
        System.out.println(list);   
    }
}

输出结果:[ABC公司的员工1,ABC公司的员工2,ABC公司的员工3]

然而我的问题是,这个输出结果是保证的还是随机的,并且可能会有所不同(?)

  • 更新于2019年9月14日

为了避免混淆,我将答案与问题分开。


我喜欢变量类型最具体的LinkedHashMap,而不是模糊的Map :) - ZhongYu
我认为,在我的用例中拥有接口或具体类引用并没有区别。但是我必须承认使用具体类而不是接口是一个错误。你有不同的看法吗? - spiderman
不,我认为对于本地/实例变量来说,使用最具体的类型是最好的。 - ZhongYu
6个回答

4

保证了。

虽然Set接口本身不保证任何顺序(好吧,LinkedHashSet保证了),但是Map实现本身保证插入顺序几乎保证您将以这个顺序获取键。由.keySet()返回的接口只是一个Set,因为,嗯,在Map中的键保证是唯一的。

如果不是这样的话,请考虑一下以下情况会发生什么:

// case 1
for (final Map.Entry<K, V> entry: map.entrySet()) {
    // entry.getKey(), entry.getValue()
}

// case 2
for (final K key: map.keySet()) {
    V value = map.get(key);
}

如果这两个代码有不同的行为,嗯...

谢谢 +1,“.keySet()返回的接口恰好是一个Set” - 我想到这个“Set”就让我感到困惑了。 - spiderman
那么,如果这两个代码片段以不同的顺序迭代呢?或者甚至使用 .values() 方法呢? - fge

2

是的,这是有保证的。

在Oracle JDK中,LinkedHashMap类重新实现了newKeyIterator()方法,该方法返回一个继承LinkedHashIterator的类的实例。这个类是有序的。


非常有效的一篇文章,+1。我深入研究了一下它 :) - spiderman

1
其他答案已经解释了LinkedHashMap#keySet().iterator()的迭代顺序,那么让我补充一下: ArrayList Javadoc关于ArrayList(Collection)的说明是:
构造一个包含指定集合元素的列表,元素顺序与集合的迭代器返回顺序相同。
所以可以保证ArrayList的项目将按照相同的顺序排列。

1
一个LinkedHashSet的迭代顺序保证是插入键的顺序(假设您不使用特殊构造函数来请求按最近访问排序)。您可以在其文档中找到这个信息。
我认为你可以争论迭代顺序保证仅适用于entry set,而不适用于其他视图,但文档不支持这一点(因为它们提到了theiteration order),并且在实践中集合视图确实共享公共迭代顺序。
对于它自己,构造函数ArrayList(Collection)记录下来以指定Collection的迭代器返回元素的顺序填充列表。

1
这个问题所解释的,并且在JavaDocs中指出,迭代类型操作被定义为按照插入顺序进行LinkedHashMap。这影响了keySetentrySetArrayList的构造函数将按照迭代器的顺序插入到List中。
这两个条件的结合意味着API保证此行为。

好的,参考资料加一。 - spiderman

0

谢谢大家,你们每个人都提出了有价值的观点。

总结所有答案,

保证顺序不变。

根据Javadocs

LinkedHashMap是Map接口的哈希表和链表实现,具有可预测的迭代顺序。这种实现与HashMap不同之处在于它维护一个双向链接列表,该列表通过其所有条目运行。此链接列表定义了迭代顺序,通常是将键插入映射中的顺序(插入顺序)

即:LinkedHashMap将按照放入映射中的条目的顺序进行迭代

因此,LinkedHashMap#keySet()将按照将键插入映射中的顺序给我相同的顺序,因为LinkedHashMap#keySet().iterator()按指定顺序迭代。

深入到iterator()的实现,我们可以看到,

LinkedHashMap 实现了 newKeyIterator() 方法,该方法返回一个继承 LinkedHashIterator 的类的实例,处理“排序”问题。

// These Overrides alter the behavior of superclass view iterator() methods
Iterator<K> newKeyIterator()   { return new KeyIterator();   } ...  

private class KeyIterator extends LinkedHashIterator<K> {...

private abstract class LinkedHashIterator<T> implements Iterator<T> { ...

对于这个代码:ArrayList<String> list = new ArrayList<String>(map.keySet()); 构造函数ArrayList(Collection)记录为按照指定集合的迭代器返回元素的顺序填充列表。


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