Java HashMap:以随机顺序迭代键值对

Java HashMap:以随机顺序迭代键值对,java,data-structures,random,iterator,hashmap,Java,Data Structures,Random,Iterator,Hashmap,我有一个HashMap,每次我得到迭代器时,我想以不同的随机顺序迭代它们的键值对。从概念上讲,我希望在调用迭代器之前对映射进行“洗牌”(或者,如果需要,可以对迭代器进行“洗牌”) 我有两个选择: 1) 使用LinkedHashMap的方法,在内部保留一个条目列表,将其无序排列,并在调用迭代器时返回该视图。 2) 以map.entrySet()为例,构造一个ArrayList并对其使用shuffle() 虽然这两种方法看起来非常相似,但我希望得到非常大的hashmap,因此我非常关注细节和内部结构

我有一个HashMap,每次我得到迭代器时,我想以不同的随机顺序迭代它们的键值对。从概念上讲,我希望在调用迭代器之前对映射进行“洗牌”(或者,如果需要,可以对迭代器进行“洗牌”)

我有两个选择:

1) 使用LinkedHashMap的方法,在内部保留一个条目列表,将其无序排列,并在调用迭代器时返回该视图。
2) 以map.entrySet()为例,构造一个ArrayList并对其使用shuffle()


虽然这两种方法看起来非常相似,但我希望得到非常大的hashmap,因此我非常关注细节和内部结构,因为我不会浪费内存或计算

尝试使用concurent哈希映射并在迭代周期之前随机获取密钥

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);
        }
Map Map=Maps.newConcurrentMap();
地图放置(“1”、“1”);
地图放置(“2”、“2”);
迭代器迭代器=map.keySet().Iterator();
while(iterator.hasNext()){
map.remove(“2”);//添加随机键值
地图放置(“2”、“2”);
String next=迭代器.next();
System.out.println(“下一步”+下一步);
}

随机移除/放置值可以“洗牌”您的地图

重新洗牌一个大集合总是很昂贵的。每个条目至少需要一个引用。e、 g.对于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) { /* ... */ }
Map=
List List=newarraylist(map.entrySet());
//每次你想要不同的订单。
集合。洗牌(列表);
对于(Map.Entry:list){/*…*/}

实际上你根本不需要洗牌:
只需在一组键中绘制一个随机索引,然后用最后一个键覆盖以删除该键:

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();
}
public类RandomMapIterator实现迭代器{
私人最终地图;
私有最终K[]密钥;
私有int密钥计数;
@抑制警告(“未选中”)
公共随机映射器(映射){
this.map=map;
this.keys=(K[])map.keySet().toArray();
this.keysCount=keys.length;
}
@凌驾
公共布尔hasNext(){
返回键计数!=0;
}
@凌驾
公共V next(){
int index=nextIndex();
K键=键[索引];
键[索引]=键[--键计数];
返回map.get(key);
}
受保护的int-nextIndex(){
return(int)(Math.random()*keysCount);
}
@凌驾
公共空间删除(){
抛出新的UnsupportedOperationException();
}

}

您不知道实现的细节,但您可以随时查看java源代码。。。如果您熟悉计算时间复杂度,您应该能够自己推断一些东西,至少在计算部分:)put可以洗牌您的条目,但这是不太可能的。移除/放置不会做任何事情。洗牌O(n lg n)如何?Fisher-Yates洗牌只需要线性时间,也是如此。正确的,跛脚排序洗牌是
O(N*logn)
,Java使用的洗牌确实是
O(N)
这基本上是我(2)提出的方法。您依赖于这样一个事实:额外的开销是每个条目4字节?为什么?最新版本的JVM支持32位引用。对于大型集合,列表中的大部分空间将是对
Map.Entry
s的引用。如果重复地重新洗牌相同的列表,则会有轻微的优化。在Java 1.7中,在创建
ArrayList
时,可以消除显式类型参数
Map.Entry
。因此您可以键入:
=newarraylist(map.entrySet())remove()并不完全是一个cheep操作,因为它需要移动数据。此外,这需要通过get()随机访问数据结构,这是O(1),但仍然比在数据结构内部迭代更昂贵。@marcorossi感谢您同意
remove()
,但我的主要观点仍然是:随机抽取与随机抽取的目的相同,只需花费一小部分成本
ArrayList
不是结构的最佳选择,因为我们不需要维护密钥顺序。我用一个简单的数组修改了我的解决方案。最终的决定取决于你是愿意提前支付O(N)成本,还是在下一次支付O(1)成本()。