HashMap Java 8 重点内容详解

从原理和细节上搞定HashMap

声明:网上讲HashMap的帖子很多,各自有各自着重介绍的地方,个人把自己比较感兴趣的内容和自己的一点点认识写下了。
这里写图片描述
hashmap是由数组和链表组成的,要插入的元素首先根据哈希函数得到hash值,然后根据规则(取模),得到自己要插入的桶(所谓的桶就是图中的0-15的数组元素)的号。然后排在桶中元素的后面。而要取的时候也一样,先拿到桶号,在沿着这个指针逐个往下找。比如上述哈希表中,我们现在假设一个要插入的元素经过Hash函数后的值是44,44%16=12;所以这个元素要插入到桶号为12的桶中,接下来它一直往下找,最后排在了140后面。
话不多说,基本的原理我想大多数人应该也知道。不清楚的可以去有些博客上看看,应该很快就明白。

然后我们来看看JAVA1.8中关于Hashmap的源码:
这里有些不太常用的字段和函数就不一一列举了。
HashMap主要属性:

public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable {

private static final long serialVersionUID = 362498820763181265L;

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认桶的数量

static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量

static final float DEFAULT_LOAD_FACTOR = 0.75f;//填充比

//当add一个元素到某个位桶,其链表长度达到8时将链表转换为红黑树
static final int TREEIFY_THRESHOLD = 8;


static final int UNTREEIFY_THRESHOLD = 6;


static final int MIN_TREEIFY_CAPACITY = 64;

transient Node<k,v>[] table;//存储元素的数组


transient Set<map.entry<k,v>> entrySet;

transient int size;//存放元素的个数

transient int modCount;//被修改的次数fast-fail机制

int threshold;//临界值 当实际大小(容量*填充比)超过临界值时,会进行扩容

final float loadFactor;//填充比

而其中的负载因子loadFactor的理解为:HashMap中的数据量/HashMap的总容量(initialCapacity),当loadFactor达到指定值或者0.75时候,HashMap的总容量自动扩展一倍。

其中最常用的两个函数实现大致如下:
// 存储时:

int hash = key.hashCode(); 
int index = hash % Entry[].length;
Entry[index] = value;

// 取值时:

int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

接下来我觉得hash函数这点很重要。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这里hashCode()通过对象得到一个int的编码,不同的对象的函数不一样,如String的hashCode()
就是将String转化成char数组,然后把这个数组看做是一个32进制的数。

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

至于之后的h = key.hashCode()) ^ (h >>> 16)
开始有些不懂,后面去知乎上看了看。
这段代码,Java 7是这么写的

   static int hash(int h) {
     h ^= (h >>> 20) ^ (h >>> 12);
     return h ^ (h >>> 7) ^ (h >>> 4);
   }

Java 8只做一次16位右位移异或混合,而不是四次,但原理是不变的。两段代码的目的都是想让哈希函数映射得比较均匀松散。hash的过程
右唯一16位,正好32位的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相的保留下来。

后面还有个人认为比较重要的内容就是resize(),以及Java 8中的红黑树。
内容会在后面补充。
treefiybin这里写图片描述
红黑树替代普通的链表这里写图片描述

最后来看看java8 很大的一个改进resize 扩容
举个例子
上边图中第0个下标有496和896, 假设它俩的hashcode(int型,占4个字节)是
resize前:
496的hashcode: 00000000 00000000 00000000 00000000
896的hashcode: 01010000 01100000 10000000 00100000
oldCap是16: 00000000 00000000 00000000 00010000
496和896对应的e.hash & (oldCap)的值为0, 即下标都是第0个。

resize后:
496的hashcode: 00000000 00000000 00000000 00*0*00000
896的hashcode: 01010000 01100000 10000000 00*1*00000
oldCap是32: 00000000 00000000 00000000 00**1**00000
代码中 if ((e.hash & oldCap) == 0)
496和896对应的e.hash & oldCap的值为0和1, 即下标是第0个和第16个。
它将原来的链表数据散列到2个下标位置, 概率分别是0.5。
因为hashcode的第n位是0/1的概率相同, 理论上链表的数据会均匀分布到当前下标或高位数组对应下标。
回顾JDK1.7的HashMap,在扩容时会rehash即每个entry的位置都要再计算一遍, 性能不好, 所以JDK1.8做了这个优化。
再回到文章最开始的问题, HashMap为什么用&得到下标,而不是%? 如果使用了取模%, 那么在容量变为2倍时, 需要rehash确定每个链表元素的位置。

本文好多地方都是在各位大神的博客上摘抄和学习到的,只是自己整理归纳方便以后复习。
具体的引用就不写了,忘各位博主见谅!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值