第一节 HasMap

1 Hash链表结构

在看HashMap源码之前,先复习下Hash表结构:

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

  • 若关键字为 k ,则其值存放在 f(k) 的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表。

  • 对不同的关键字可能得到同一散列地址,即 k1 != k2,而 f(k1)=f(k2) ,这种现象称为冲突(英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数f(k)和处理冲突的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表或散列,所得的存储位置称散列地址。

  • 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为 均匀散列函数(Uniform Hash function) , 这就是使关键字经过散列函数得到一个“随机的地址”,从而减少冲突。

维基百科中关于Hash链表的描述清晰的定义了哈希结构的关键因素key、散列函数、冲突。其图示结构如下 image

2 HashMap结构简述

HashMap继承自Map,实现了Map的主结构Entry,包含key、value以及指向下一个Entry的引用

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    /***
     * 对key进行hashcode运算后得到的hash值,存储在Entry,避免重复计算
     */
    int hash;

    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
}

定义好主要结构后,HashMap中还定义了

3 HashMap详解

3.1 关于初始化

jdk1.7中为HashMap提供了4个构造函数,其中三个方法,一堆参数初始化后殊途同归的走向init()方法,当我满心欢喜以为马上就要揭开核心时发现,我操什么鬼,init里面什么都没有。注释都没有。只能心里默默的念叨,好吧看来初始化就是纯粹意义上的初始化。 不过三个构造函数一通看完并不是没有收获,比如

  • HashMap 默认容量是16

  • HashMap (loadFactor)默认负载因子是0.75

  • HashMap (threshold)初始阈值是容量

3.2 关于写操作

我们知道jdk1.7的Map规范中定义了,Map的写操作就两个,put(K key,V value)和putAll(Map<? extends K, ? extends V> map)

这里先看下put操作

  1. 先聊聊方法inflateTable:该方法先是通过Integer的highestOneBit方法对toSize进行取2的幂次处理,然后重新计算阈值、初始化一个大小为capacity的数组。

  2. 然后我们在看看如果key是null的情况:如果key=null 则将value放入table[0]的位置或者table[0]的冲突链上

  3. 关于hash 简单说来就是如果没有设置hashSeed,则对key的hashcode进一步进行计算以及二进制位的调整等来保证最终获取的存储位置尽量分布均匀

  4. 关于indexFor,进一步处理来获取实际的存储位置

    inflateTable中我们说到table的capacity是2的幂次,所以h于上length-1就是如下所示了

比如h=18,length=16

所以存储位置是table[2]或者其对于的冲突处理链上;

  1. 关于添加键值对addEntry

至此put方法完了。

最后说下总体put的总体流程 HashMapAddEntry_1.7_get

3.3 关于扩容

在进行put操作是有个判定,如果size>=当前阀值,并且对于的table[bucketIndex]位置不为空就调用resize方法进行扩容。

3.4 关于读操作

通过代码阅读,可以看到get方法比较简单。 HashMapAddEntry_1.7_get

3.5 关于移除操作

HashMapAddEntry_1.7_get

可以看到,hashmap的移除操作相对还是很简单的

4 jdk1.8新特性

读完了1.7的HashMap源码我以为终于掌握了一套葵花宝典,发现我想多了。1.8中HashMap对于Hash碰撞的解决方案做了比较大的改动,引入了红黑树来解决碰撞。闲话少叙,直接上源码吧。

4.1 结构变化

1.7中的hashmap底层结构就一个Entry,到了1.8时变成了两个,一个是Node,一个是TreeNode,需要注意下,TreeNode继承自LinkedHashMap.Entry。原因暂且不表

4.2 写操作变化

Last updated