美文网首页
可怕的 Map 遍历乱序 | 有序遍历请使用List。

可怕的 Map 遍历乱序 | 有序遍历请使用List。

作者: haohui谌 | 来源:发表于2017-01-01 01:16 被阅读0次

    在工作中碰到的一个case 是使用HashSet来记录一些View的索引。在某些情况下,需要读取 HashSet 中的值,进行操作。万恶的bug出现在从读取过程中。伪代码如下:

    HashSet viewsId = new HashSet<>();

    viewsId.add("imageview-1");

    viewsId.add("textView-1");

    viewsId.add("imageview-2");

    viewsId.add("textView-2");

    for (String viewId : viewsId){

    //get result

    }

    期望的result应该是:imageview-1、textView-1、imageview-2、textView-2

    实际的顺序是:

    问题很明显,Set中的位置顺序并不是按照插入顺序的。修改为List 很容易解决这个问题。

    Why?

    HashSet 继承自 AbstractSet ,而Set 可以理解成阉割版的Map,这个可以从HashSet源码中发现(每个版本实现可能不一致):

    private transient HashMap map;

    /**

    * Constructs a new, empty set; the backing HashMap instance has

    * default initial capacity (16) and load factor (0.75).

    */

    public HashSet() {

    map = new HashMap<>();

    }

    而add方法直接调用了HashMap的put。

    public V put(K key, V value) {

    if (table == EMPTY_TABLE) {

    inflateTable(threshold);

    }

    if (key == null)

    return putForNullKey(value);

    int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);

    int i = indexFor(hash, table.length);

    .....

    modCount++;

    addEntry(hash, key, value, i);

    return null;

    }

    可以从上面代码中发现,插入前需要做一次hash来减少冲突,这里使用的hash方法是 著名的Wang/Jenkins hash算法。具体细节可以参考:

    https://en.wikipedia.org/wiki/Jenkins_hash_function

    接着使用预哈希完毕之后的值进行一次indexFor()操作。

    /**

    * Returns index for hash code h.

    */

    static int indexFor(int h, int length) {

    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";

    return h & (length-1);

    }

    最后进行插入操作:

    /**

    * Adds a new entry with the specified key, value and hash code to

    * the specified bucket.  It is the responsibility of this

    * method to resize the table if appropriate.

    *

    * Subclass overrides this to alter the behavior of put method.

    */

    void addEntry(int hash, K key, V value, int bucketIndex) {

    if ((size >= threshold) && (null != table[bucketIndex])) {

    resize(2 * table.length);

    hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;

    bucketIndex = indexFor(hash, table.length);

    }

    createEntry(hash, key, value, bucketIndex);

    }

    在addEntry时,如果当前table长度大于阈值threshold则会将table增大一倍,而默认的threshold是取值static final int DEFAULT_INITIAL_CAPACITY = 4;之后会再根据当前table的大小做一次indexFor()运算。将这个值作为插入的index。

    总结

    1.HashMap 中对于数据的存储位置是由hashcode来决定。因此遍历Map时,请注意使用的场景。

    相关文章

      网友评论

          本文标题:可怕的 Map 遍历乱序 | 有序遍历请使用List。

          本文链接:https://www.haomeiwen.com/subject/gxwbvttx.html