【问题标题】:Iteration order of java.util.HashMap.values()java.util.HashMap.values()的迭代顺序
【发布时间】:2016-01-18 08:42:18
【问题描述】:

是否保证java.util.Map 的标准实现中的键和值以相同的顺序返回?例如,如果map包含映射x1 -> y1x2 -> y2,那么如果keySet()迭代产生x1, x2,是否保证values()迭代产生y1, y2而不是y2, y1?我没有看到任何地方保证这是真的,但它似乎有效。谁能给出确认或否定这个前提并举出反例?

public class MapsTest {
    @Test
    public void hashMapKeysAndValuesAreInSameOrder() {
        assertKeysAndValuesAreInSameOrder(new HashMap<>());
    }

    @Test
    public void treeMapKeysAndValuesAreInSameOrder() {
        assertKeysAndValuesAreInSameOrder(new TreeMap<>());
    }

    private void assertKeysAndValuesAreInSameOrder(Map<Integer, Integer> map) {
        Random random = new Random();
        IntStream.range(0, 100000).map(i -> random.nextInt()).forEach(i -> map.put(i, i));
        assertEquals(new ArrayList<>(map.keySet()), new ArrayList<>(map.values()));
    }
}

【问题讨论】:

  • 我认为你不能依赖它,因为values() 的文档没有提到这一点。
  • 我猜这个功能是公共合同的一部分,尽管它没有记录在案。顺便说一句,我只记得 Linus Torvald 的帖子“我们从不破坏用户空间”。

标签: java


【解决方案1】:

来自HashMap的文档,

这个类不保证地图的顺序;特别是,它不保证订单会随着时间的推移保持不变。

如果你查看它的code,你会发现一旦你调用put(),最终hash被计算出来,entry object被创建并添加到bucket array中。我过度简化了代码的目的,只是为了解释幕后发生的事情。

如果您想要保证订单,请根据您的要求使用LinkedHashMapTreeMap

同时检查,

Difference between HashMap, LinkedHashMap and TreeMap

【讨论】:

    【解决方案2】:

    有趣的问题。我试了一下。并且键的相对顺序似乎与值保持同步。

    import java.util.*;
    public class MapClass {
    public static Map<String, Integer> map = new HashMap<>();
    
      public static void main (String[] args){
        map.put("first", 1);
        map.put("second", 2);
        map.put("third", 3);
        map.put("fourth", 4);
        map.put("fifth", 5);
    
        System.out.println(map.keySet());
        System.out.println(map.values());
    
        map.put("sixth", 6);
        System.out.println(map.keySet());
        System.out.println(map.values());
    
        map.remove("first");
        System.out.println(map.keySet());
        System.out.println(map.values());
      }
    }
    

    我明白了:

    [third, fifth, fourth, first, second]
    [3, 5, 4, 1, 2]
    [sixth, third, fifth, fourth, first, second]
    [6, 3, 5, 4, 1, 2]
    [sixth, third, fifth, fourth, second]
    [6, 3, 5, 4, 2]
    

    这个简单的例子似乎表明,虽然HashMap 不会保留插入顺序,但它似乎确实保留了键和值的相对顺序。

    keyset() 和 values() 虽然不保证任何顺序,但似乎in practice 它将为多次调用返回相同的顺序。正如那里的评论所说,即使您可以依赖它,您是否应该也值得怀疑。

    【讨论】:

      【解决方案3】:

      来自HashMap docs

      这个类不保证地图的顺序;特别是,它不保证订单会随着时间的推移保持不变。

      【讨论】:

      • 我看到了这句话,但我认为它没有说明keys()和values()的顺序关系。
      • 可以,但不能。我想知道是否有人可以给我一个例子。
      • 我认为 ConcurrentModificationException 会被抛出。而且这些映射不是线程安全的,所以并发对我来说不是问题。
      【解决方案4】:

      看看http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/HashMap.java#HashMap.values%28%29,我猜它可以工作,因为 KeyIterator 和 ValueIterator 都扩展了 HashIterator。

      但前提是您同时不更改 HashMap,例如在调用 keySet() 和 values() 之间添加和删除项目,因为这会改变存储桶的大小,并且 HashIterator 的行为会有所不同。

      【讨论】:

      • 测试仍然通过...forEach(i -&gt; { map.put(i, i); map.values(); }
      • 如果你做 System.out.println(map.keySet());,然后添加一个 100000 个项目,再次删除所有 100000 个,然后打印 map.values(),你可能会有不同结果...
      • TreeMap 相同的故事?所有迭代器都扩展 PrivateEntryIterator grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…
      • TreeMap 是一个 SortedMap。因此,需要在所有迭代器上保持相同的顺序。 docs.oracle.com/javase/7/docs/api/java/util/SortedMap.html "地图是根据其键的自然顺序排序的,或者由通常在排序地图创建时提供的比较器排序。此顺序反映在遍历排序地图的集合视图时(由 entrySet、keySet 和 values 返回方法)。提供了几个额外的操作来利用排序。(此接口是 SortedSet 的映射类似物。)"
      • 这篇文章是关于keys()和values()的相对顺序,而不是keys()的绝对顺序。
      猜你喜欢
      • 2012-12-28
      • 2011-02-11
      • 2012-10-31
      • 1970-01-01
      • 1970-01-01
      • 2020-11-23
      • 1970-01-01
      • 2019-01-15
      • 2021-12-11
      相关资源
      最近更新 更多