[hashmap的底层实现TOC]
hashmap的底层是数组加单向链表存放数据的,它的默认长度是16,加载因子是0.75,扩容阈值是默认长度乘以加载因子;
比如我们通过无参构造方法实例化出hashmap时,会默认给hashmap加载因子设置成0.75,这时hashmap并没有开辟空间,只有在调用put方法放值是才开始开辟空间。
put方法开辟空间后
第一步,判断传入的键是否是null,如果是null,直接放入到node中,并返回。
第二步,如果存放的键不是null, 接着往下走,下面判断条件是当前要存放的值是否在hashmap中存在,如果存在直接执行覆盖操作。
第三步,如果要存放的值不存在,就循环单向链表,比对单向链表中的每个值是否和传入的值一致,如果一致,直接跳出循环,拿新的值翻盖掉老值。如果单向链表的值都没有和新传入的值一致,那就在单向链表的最后面插入新值,在插入新值后进行判断,判断单向链表的长度是否超过8个,如果超过8个,单向链表就会变成红黑树,这样的目的是为了提升查询效率。
第四步,判断size的长度是否大于阈值(阈值=数组长度*加载因子),如果大于阈值就进行扩容。
在put方法最后有一个++modCount的属性,这个属性在put、get、remove方法中都有使用,这个属性是集合的一个fail fast机制,是为了防止集合多线程情况下,一个线程正在操作迭代器的foreach查询时,如果有其它线程执行增删改的操作,就会造成了查询数据不一致,这时查询就会抛异常,以保证数据的安全性。
modCount类似于一个乐观锁的机制,它只能保证数据在一定情况下安全,但不能保证绝对安全,所以hashmap还是一个线程非安全的。
为什么要设置0.75的扩容因子?
设置0.75的扩容因子是解决“哈希冲突”和“空间利用率”矛盾的一个折衷方案。跟数据结构要么查询快要么插入快一个道理,hashmap就是一个插入慢、查询快的数据结构。
加载因子是表示Hash表中元素的填满的程度。加载因子越大,填满的元素越多,空间利用率越高,但hash冲突的几率就会加大,冲突的记录大会造成查找的成本高。
加载因子越小,填满的元素越少,冲突的机会减小,但空间浪费多了,但查找的成本小了很多。
所以,0.75是在 "冲突的几率"与"空间利用率"之间寻找一种平衡与折衷。