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.Entrys 的引用。如果您重复重新洗牌相同的列表,会有轻微的优化。 在 Java 1.7 中,您可以在创建 ArrayList 时消除显式类型参数 Map.Entry&lt;K,V&gt;。所以你可以输入:= new ArrayList&lt;&gt;(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:以随机顺序迭代键值对的主要内容,如果未能解决你的问题,请参考以下文章

集合——Map

使用 shell 脚本迭代 json 以存储键值对

HashMap

java问题,我想在java中存储键值对,以便使用,但是键值对的键和值都有重复元素,使用hashmap会产生覆盖。

HashMap是啥东西

Java的HashMap键值对存储结构解析