HashMap(JDK1.7以前:数组+链表;JDK1.8:加了红黑树)
-
HashMap时存储key-value的集合。每一个键值对都是一个Entry数组。这些Entry数组默认为空。
-
HashMap由数组+链表组成,数组时HashMap的主体,而链表是解决hash冲突而存在的。
-
当链表的长度过长时,就会降低查询效率。所以当链表长度大于8时就会转换成红黑树
链表长度大于8时转换成红黑树遵循松柏分布。用松柏分布的公式计算出链表中的元素和概率的对照表,得出当链表长度为8时的值已经非常小了。
因为红黑树的平均查找长度为log(n),当长度为8时红黑树的查找长度为3;而链表的查找长度为4,所以才有了转换成红黑树的必要。
但是如果链表长度小于等于6时,虽然红黑树的效率也很快,但是要考虑转换成红黑树所消耗的时间
-
HashMap的扩容机制
HashMap的初始大小为16,达到扩容阈值后会进行二倍扩容。当数组长度达到负载因子(0.75)的时候就会进行二倍扩容。扩容的时候要重新计算原数组的位置并放进新的数组
-
设置负载因子为0.75的原因:
如果设置的太大了,虽然保证了空间的充足,减少了哈希碰撞,查询效率也很高,但是会消耗大量的资源,越到后期消耗的资源越多
如果设置了太小了,如果等到数组要被填满了在扩容,虽然空间的利用率增大了,但是会产生大量的hash碰撞,也会产生大量的链表。
所以折中取0.75
-
-
put方法的原理:
-
首先把key和value封装到Node对象中
-
然后底层会调用hashCode()方法
-
第三步会调用哈希函数,把哈希值转换成数组下标,如果这个下标没有任何元素就直接添加,要是这个下标有链表。就会用这个key和链表上的每一个节点进行equal。如果equal的结果时false,就把这个新的节点添加到链表的末尾;如果equal的结果时true,这个节点的value就会被覆盖(通常hasMap的链表出现次数越少,性能越好)
哈希函数:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
意思就是,获取key的hash值,然后和其他的低16位进行异或运算,从而得到散列值。当关键字hash得到的地址已经存储过数据,这是就会出现哈希冲突,而hashMap采用了链地址法(数组+链表)
-
-
get方法的原理
-
先调用hashCode方法算出key的哈希值,并通过哈希算法得到数组的下标
-
转换成下标后,就可以快速定位到数组的位置
-
如果这个位置上上为空,就返回null
-
如果这个位置上有单向链表,就会拿着key在单向链表上进行equals。如果equals的结果为false,就get到null;如果equals的结果为true,就把这个value返回
-
-
HashTable
HashTable和HashMap的区别:
-
HashMap是非同步的,没有并发读写的保护措施,所以是线程不安全的,在多线程并发条件下会倒置数据不一致的情况;HashTable是线程同步的,所有的读写操作都进行了锁的保护,是线程安全的,但是非常影响读写的效率
-
HashMap中是允许保存null的;但是HashTable不允许空值的存在
HashTable原理
和HashMap的非常相似,不同点在于所有操作都是通过synchronized保护的,只有获得了对应的锁,才能进行读写的操作
-
HashTable的put方法:
-
先获取synchronized锁,put的值是不允许为null的。如果为null直接抛出异常
-
计算key的哈希值,并通过哈希值算出数组的下标
-
遍历对应下标的链表,并遍历链表。如果存在和key的节点就更新value;否则就把节点插入到链表的头部
-
-
HashTable的get方法:
-
先获取synchronized锁
-
计算key的哈希值,然后使用哈希函数得到数组的下标
-
在对应下标的链表下面寻找具有相同hash值的key节点,返回该节点的value;没找到就返回null
-