欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识

V e = table[i]; e != null; e = e.next) { Object k; if (e.has

点击: 次  来源:宝鼎软件 时间:2017-07-30

原文出处: 五月的仓颉

初识HashMap

之前的List,讲了ArrayList、LinkedList,最后讲到了CopyOnWriteArrayList,就前两者而言,反应的是两种思想:

(1)ArrayList以数组形式实现,顺序插入、查找快,插入、删除较慢

(2)LinkedList以链表形式实现,顺序插入、查找较慢,插入、删除利便

那么是否有一种数据布局可以或许团结上面两种的利益呢?有,谜底就是HashMap。

HashMap是一种非经常见、利便和有用的荟萃,是一种键值对(K-V)形式的存储布局,下面将照旧用图示的方法解读HashMap的实现道理。

四个存眷点在HashMap上的谜底

V e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash ((k = e.key) == ke CAD加密 y || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash

添加数据

首先看一下HashMap的一个存储单位Entry:

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    int hash;
    ...
}

之前一篇写LinkedList的文章,内里写到LinkedList是一个双向链表,从HashMap的Entry看得出,Entry构成的是一个单向链表,因为内里只有Entry的后继Entry,而没有Entry的前驱Entry。用图暗示应该是这么一个数据布局:

V e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash ((k = e.key) == ke CAD加密 y || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash

接下来,假设我有这么一段代码:

public static void main(String[] args)
{
     Map<String, String> map = new HashMap<String, String>();
     map.put("111", "111");
     map.put("222", "222");
}

看一下做了什么。首先从第3行开始,new了一个HashMap出来:

public HashMap() {
     this.loadFactor = DEFAULT_LOAD_FACTOR;
     threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
     table = new Entry[DEFAULT_INITIAL_CAPACITY];
     init();
}

留意一下第5行的init()是个空要领,它是HashMap的子类好比LinkedHashMap结构的时候利用的。DEFAULT_INITIAL_CAPACITY为16,也就是说,HashMap在new的时候结构出了一个巨细为16的Entry数组,Entry内所有数据都取默认值,如图示为:

V e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash ((k = e.key) == ke CAD加密 y || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash

看到new出了一个巨细为16的Entry数组来。接着第4行,put了一个Key和Value同为111的字符串,看一下put的时候底层做了什么:

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
       Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
     return h & (length-1);
 }

看一下put要领的几个步调:

1、第2行~第3行就是HashMap答允Key值为空的原因,空的Key会默认放在第0位的数组位置上

2、第4行拿到Key值的HashCode,由于HashCode是Object的要领,因此每个工具都有一个HashCode,对这个HashCode做一次hash计较。凭据JDK源码注释的说法,这次hash的浸染是按照给定的HashCode对它做一次打乱的操纵,软件开发,防备一些糟糕的Hash算法发生的糟糕的Hash值,图纸加密,至于为什么要防备糟糕的Hash值,HashMap添加元素的最后会讲到

3、第5行按照从头计较的HashCode,对Entry数组的巨细取模获得一个Entry数组的位置。看到这里利用了&,移位加速一点代码运行效率。别的,这个取模操纵的正确性依赖于length必需是2的N次幂,这个熟悉二进制的伴侣必然领略,因此留意HashMap结构函数中,假如你指定HashMap初始数组的巨细initialCapacity,假如initialCapacity不是2的N次幂,HashMap会算出大于initialCapacity的最小2的N次幂的值,作为Entry数组的初始化巨细。这里为了讲授利便,我们假定字符串111和字符串222算出来的i都是1