HashMap:以随机顺序迭代键值对
Posted
技术标签:
【中文标题】HashMap:以随机顺序迭代键值对【英文标题】:HashMap: iterating the key-value pairs in random order 【发布时间】:2012-09-30 16:40:54 【问题描述】:我有一个 HashMap,我想在每次获得迭代器时以不同的随机顺序迭代它们的键值对。从概念上讲,我想在调用迭代器之前“打乱”地图(或者如果你愿意,“打乱”迭代器)。
我有两个选择:
1) 使用 LinkedHashMap 的方法并在内部保留条目列表,将其就地打乱并在调用迭代器时返回该视图。 2) 获取map.entrySet(),构造一个ArrayList并在其上使用shuffle()。
虽然这两种方法看起来与我非常相似,但我期待非常大的 HashMap,所以我真的很关心细节和内部结构,因为我真的不能浪费内存或计算。
【问题讨论】:
你不知道实现细节,但你可以随时查看java源代码......如果你熟悉计算时间复杂度,你应该能够自己推断一些东西,至少对于计算部分:) 【参考方案1】:其实你根本不需要洗牌: 只需在键数组中绘制一个随机索引并通过覆盖最后一个键来删除键:
public class RandomMapIterator<K,V> implements Iterator<V>
private final Map<K,V> map;
private final K[] keys;
private int keysCount;
@SuppressWarnings("unchecked")
public RandomMapIterator(Map<K,V> map)
this.map = map;
this.keys = (K[]) map.keySet().toArray();
this.keysCount = keys.length;
@Override
public boolean hasNext()
return keysCount!=0;
@Override
public V next()
int index = nextIndex();
K key = keys[index];
keys[index] = keys[--keysCount];
return map.get(key);
protected int nextIndex()
return (int)(Math.random() * keysCount);
@Override
public void remove()
throw new UnsupportedOperationException();
【讨论】:
arrayList 上的 remove() 并不是一个简单的操作,因为它需要转移数据。此外,这需要通过 get() 随机访问数据结构,这是 O(1),但仍然比内部迭代数据结构更昂贵。 @marcorossi 感谢同意remove()
,但我的主要观点仍然是:随机抽奖可以达到与洗牌相同的目的,而成本只是其中的一小部分。 ArrayList
不是结构的最佳选择,因为无论如何我们都不需要维护密钥顺序。我用一个简单的数组修改了我的解决方案。决定归结为您是更愿意预先考虑 O(N) 成本还是每个 next() 的 O(1) 成本。【参考方案2】:
重新洗牌一个大集合总是会很昂贵。每个条目至少需要一个参考。例如对于 100 万个条目,您将需要大约 4 MB。
注意;洗牌操作是O(N)
我会用
Map<K,V> map =
List<Map.Entry<K,V>> list = new ArrayList<Map.Entry<K,V>>(map.entrySet());
// each time you want a different order.
Collections.shuffle(list);
for(Map.Entry<K, V> entry: list) /* ... */
【讨论】:
如何洗牌 O(n lg n)? Fisher-Yates 洗牌只需要线性时间,Collections.shuffle
也是如此。
正确,一个蹩脚的排序shuffle是O(N * log N)
,而Java使用的shuffle确实是O(N)
这基本上是我提出的(2)方法。您依赖于每个条目的额外开销是 4 字节的事实吗?为什么会这样?
最新版本的 JVM 支持 32 位引用。对于大型集合,列表中的大部分空间将是对Map.Entry
s 的引用。如果您重复重新洗牌相同的列表,会有轻微的优化。
在 Java 1.7 中,您可以在创建 ArrayList
时消除显式类型参数 Map.Entry<K,V>
。所以你可以输入:= new ArrayList<>(map.entrySet());
【参考方案3】:
尝试使用并发哈希映射,并在迭代周期之前随机获取密钥
Map<String, String> map = Maps.newConcurrentMap();
map.put("1", "1");
map.put("2", "2");
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext())
map.remove("2");// add random key values
map.put("2", "2");
String next = iterator.next();
System.out.println("next" + next);
随机删除/放置值可以“洗牌”您的地图
【讨论】:
put 可以随机播放您的条目,但不太可能。删除/放置不会做任何事情。以上是关于HashMap:以随机顺序迭代键值对的主要内容,如果未能解决你的问题,请参考以下文章