1 首先哈希

hash,散列,直译为哈希。哈希表,即为散列存储结构,

采用散列技术

2结构

数组 + 链表,图示:

将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)

使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,如果hashcode相同,或者hashcode取模后的结果相同(hash collision),那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表。在hashcode特别差的情况下,比方说所有key的hashcode都相同,这个链表可能会很长,那么put/get操作都可能需要遍历这个链表

hash底层怎么存储的 redis hash存储结构_取模

(方块表示Entry对象,横排表示数组table[],纵排表示哈希桶bucket【实际上是一个由Entry组成的链表,新加入的Entry放在链头,最先加入的放在链尾】,)

3 添加数据

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    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;
}

if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {


这行代码是说,元素hash 值相同并不代表一定相等,但是hash值不同肯定代表不相等,如果相同还需要再次判断key 是否相同

分析源码可知,put()时,HashMap会先遍历table数组,用hash值和equals()判断数组中是否存在完全相同的key对象, 如果这个key对象在table数组中已经存在,就用新的value代替老的value。如果不存在,就创建一个新的Entry对象添加到table[ i ]处。

如果该table[ i ]已经存在其他元素,那么新Entry对象将会储存在bucket链表的表头,通过next指向原有的Entry对象,形成链表结构(hash碰撞解决方案)。

4 为啥大小为2的n次幂

不同的key通过indexFor()方法算得的数组位置相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,get()的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。而且从源码中也能发现在addEntry 方法中

resize(2 * table.length); 每次扩容就是2的倍数,而为啥取2的倍数就是因为减少hash碰撞能让链表分布的均匀