据我所知,从Map的keySet()方法返回的Set不保证任何特定的顺序。
我的问题是,它是否保证多次迭代的相同顺序。例如
Map<K,V> map = getMap();
for( K k : map.keySet() )
{
}
...
for( K k : map.keySet() )
{
}
在上面的代码中,假设地图不修改,keySet上的迭代将是相同的顺序。使用Sun的jdk15,以顺序迭代,但在依赖此行为之前,我想知道所有JDK是否也会这样做。
修改
我从答案中看到,我不能依赖它。太糟糕了。我希望不必为了保证我的订购而建立一些新的收藏品。我的代码需要迭代,执行一些逻辑,然后使用相同的顺序再次迭代。我将从keySet创建一个新的ArrayList,这将保证订单。
答案 0 :(得分:46)
如果您想要一个迭代顺序不变的HashMap,您可以使用LinkedHashMap。
此外,如果您遍历集合,则应始终使用它。迭代HashMap的entrySet或keySet要慢于LinkedHashMap的。
答案 1 :(得分:44)
如果API文档中没有说明保证,那么您不应该依赖它。甚至从同一供应商的JDK,行为甚至可能从JDK的一个版本发生变化。
您可以轻松获得该设置,然后自己对其进行排序,对吧?
答案 2 :(得分:9)
Map只是一个接口(而不是一个类),这意味着实现它的底层类(并且有许多)可能表现不同,并且API中keySet()的契约并不表示一致的迭代是必须的。
如果您正在查看实现Map(HashMap,LinkedHashMap,TreeMap等)的特定类,那么您可以看到它如何实现keySet()函数以通过检查源来确定行为,你' d必须真正仔细查看算法,以查看您要查找的属性是否被保留(即,当映射在迭代之间没有任何插入/删除时,一致的迭代顺序)。例如,HashMap的源代码在这里(打开JDK 6):http://www.docjar.com/html/api/java/util/HashMap.java.html
从一个JDK到另一个JDK,它可能有很大差异,所以我绝对不会依赖它。
话虽如此,如果您确实需要一致的迭代顺序,您可能想尝试使用LinkedHashMap。
答案 3 :(得分:6)
Map for Map不保证任何排序,即使在同一对象上多次调用该方法之间也是如此。
在实践中,如果迭代顺序为多个后续调用而改变(假设地图本身之间没有变化),我会非常惊讶 - 但你不应该(并且根据API不能)依赖于此。
编辑 - 如果你想依赖迭代顺序是一致的,那么你需要SortedMap来提供这些保证。
答案 4 :(得分:5)
为了好玩,我决定写一些代码,你可以用它来保证每次随机顺序。这很有用,因此您可以捕获依赖于订单的情况,但您不应该这样。如果你想依赖顺序,而不是像其他人所说的那样,你应该使用SortedMap。如果您只是使用Map并碰巧依赖于订单,那么使用以下RandomIterator将捕获它。我只会在测试代码时使用它,因为它会使用更多的内存然后不这样做。
你也可以包装Map(或Set)让它们返回RandomeIterator,然后让你使用for-each循环。
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class Main
{
private Main()
{
}
public static void main(final String[] args)
{
final Map<String, String> items;
items = new HashMap<String, String>();
items.put("A", "1");
items.put("B", "2");
items.put("C", "3");
items.put("D", "4");
items.put("E", "5");
items.put("F", "6");
items.put("G", "7");
display(items.keySet().iterator());
System.out.println("---");
display(items.keySet().iterator());
System.out.println("---");
display(new RandomIterator<String>(items.keySet().iterator()));
System.out.println("---");
display(new RandomIterator<String>(items.keySet().iterator()));
System.out.println("---");
}
private static <T> void display(final Iterator<T> iterator)
{
while(iterator.hasNext())
{
final T item;
item = iterator.next();
System.out.println(item);
}
}
}
class RandomIterator<T>
implements Iterator<T>
{
private final Iterator<T> iterator;
public RandomIterator(final Iterator<T> i)
{
final List<T> items;
items = new ArrayList<T>();
while(i.hasNext())
{
final T item;
item = i.next();
items.add(item);
}
Collections.shuffle(items);
iterator = items.iterator();
}
public boolean hasNext()
{
return (iterator.hasNext());
}
public T next()
{
return (iterator.next());
}
public void remove()
{
iterator.remove();
}
}
答案 5 :(得分:3)
Hashmap不保证地图的顺序会随着时间的推移保持不变。
答案 6 :(得分:2)
不一定是这样。 map的keySet函数返回一个Set,而set的iterator方法在其文档中说明了这一点:
“返回此集合中元素的迭代器。元素以无特定顺序返回(除非此集合是某个提供保证的类的实例)。”
所以,除非你使用其中一个有保证的类,否则没有。
答案 7 :(得分:2)
Map是一个接口,它没有在文档中定义顺序应该是相同的。这意味着你不能依赖订单。但是如果你控制getMap()返回的Map实现,那么你可以使用LinkedHashMap或TreeMap,并在你遍历它们时获得相同的键/值顺序。
答案 8 :(得分:2)
tl; dr 是。
我相信.keySet()
和.values()
的迭代顺序是一致的(Java
8)。
证明1 :我们使用随机键和随机值加载HashMap
。我们使用HashMap
对此.keySet()
进行迭代,并将键及其对应的值加载到LinkedHashMap
中(它将保留键和值的插入顺序)。然后,我们比较两个地图的.keySet()
和两个地图的.values()
。 总是一样,永远不会失败。
public class Sample3 {
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static SecureRandom rnd = new SecureRandom();
// from here: https://stackoverflow.com/a/157202/8430155
static String randomString(int len){
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
sb.append(AB.charAt(rnd.nextInt(AB.length())));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
for (int j = 0; j < 10; j++) {
Map<String, String> map = new HashMap<>();
Map<String, String> linkedMap = new LinkedHashMap<>();
for (int i = 0; i < 1000; i++) {
String key = randomString(8);
String value = randomString(8);
map.put(key, value);
}
for (String k : map.keySet()) {
linkedMap.put(k, map.get(k));
}
if (!(map.keySet().toString().equals(linkedMap.keySet().toString()) &&
map.values().toString().equals(linkedMap.values().toString()))) {
// never fails
System.out.println("Failed");
break;
}
}
}
}
证明2 :从here开始,table
是Node<K,V>
类的数组。我们知道,迭代数组每次都会得到相同的结果。
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
负责.values()
的班级:
final class Values extends AbstractCollection<V> {
// more code here
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
负责.keySet()
的班级:
final class KeySet extends AbstractSet<K> {
// more code here
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
仔细研究两个内部类。它们基本相同,除了:
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key); <- from KeySet class
// action.accept(e.value); <- the only change from Values class
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
它们在同一数组table
上进行迭代,以支持.keySet()
类中的KeySet
和.values()
类中的Values
。
证明3:this answer还明确指出-是的,是的,keySet(),values()和entrySet()返回内部链接列表使用的顺序的值。 < / p>
因此,.keySet()
和.values()
是一致的。
答案 9 :(得分:1)
从逻辑上讲,如果合同上写着“没有特别的订单得到保证”,并且由于“一次出来的订单”是特定订单,那么答案是否定的,你不能取决于它以同样的方式出现两次。
答案 10 :(得分:1)
我同意LinkedHashMap的观点。当我试图通过键对HashMap进行排序时,只是在我遇到问题的同时提出我的发现和经验。
我创建HashMap的代码:
HashMap<Integer, String> map;
@Before
public void initData() {
map = new HashMap<>();
map.put(55, "John");
map.put(22, "Apple");
map.put(66, "Earl");
map.put(77, "Pearl");
map.put(12, "George");
map.put(6, "Rocky");
}
我有一个showMap函数,用于打印map:
的条目public void showMap (Map<Integer, String> map1) {
for (Map.Entry<Integer, String> entry: map1.entrySet()) {
System.out.println("[Key: "+entry.getKey()+ " , "+"Value: "+entry.getValue() +"] ");
}
}
现在,当我在排序之前打印地图时,它按以下顺序打印:
Map before sorting :
[Key: 66 , Value: Earl]
[Key: 22 , Value: Apple]
[Key: 6 , Value: Rocky]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]
这与放置地图键的顺序基本不同。
现在当我用地图键排序时:
List<Map.Entry<Integer, String>> entries = new ArrayList<>(map.entrySet());
Collections.sort(entries, new Comparator<Entry<Integer, String>>() {
@Override
public int compare(Entry<Integer, String> o1, Entry<Integer, String> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
HashMap<Integer, String> sortedMap = new LinkedHashMap<>();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println("Putting key:"+entry.getKey());
sortedMap.put(entry.getKey(), entry.getValue());
}
System.out.println("Map after sorting:");
showMap(sortedMap);
输出是:
Sorting by keys :
Putting key:6
Putting key:12
Putting key:22
Putting key:55
Putting key:66
Putting key:77
Map after sorting:
[Key: 66 , Value: Earl]
[Key: 6 , Value: Rocky]
[Key: 22 , Value: Apple]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]
您可以按键的顺序查看差异。键的排序顺序很好,但复制的地图的键的顺序再次与早期地图的顺序相同。我不知道这是否有效,但对于具有相同键的两个hashmap,键的顺序是相同的。这意味着对于两个具有相同键的映射不能保证密钥顺序的说法,因为如果该JVM版本的HashMap实现,密钥插入算法的固有性质。
现在,当我使用LinkedHashMap将已排序的条目复制到HashMap时,我得到了期望的结果(这很自然,但这不是重点。点是关于HashMap键的顺序)
HashMap<Integer, String> sortedMap = new LinkedHashMap<>();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println("Putting key:"+entry.getKey());
sortedMap.put(entry.getKey(), entry.getValue());
}
System.out.println("Map after sorting:");
showMap(sortedMap);
输出:
Sorting by keys :
Putting key:6
Putting key:12
Putting key:22
Putting key:55
Putting key:66
Putting key:77
Map after sorting:
[Key: 6 , Value: Rocky]
[Key: 12 , Value: George]
[Key: 22 , Value: Apple]
[Key: 55 , Value: John]
[Key: 66 , Value: Earl]
[Key: 77 , Value: Pearl]
答案 11 :(得分:0)
您还可以存储keySet()方法返回的Set实例,并且只要您需要相同的订单,就可以使用此实例。