HashMap的存储逻辑图:
上图中可以看出在HashMap的底层使用的是(数组+链表),当链表长度超过8时,将链表转换为红黑树(从JDK1.8版本开始)
1.链表的优势在于插入节点与删除节点
2.数组的优点是通过数组下标查找O(1)
HashMap底层所用到的就是将链表与数组相结合(Entry链表)
Java是一门面向对象的编程语言,万物皆对象
class Node<K,V>{ private K key; private V value; private Node next; }
3.那么数组的表示必然就是:
Node<K,V>[]
4.那么既然是数组,就不可能让其无限增大,所以要设置一个初始值大小
DefaultSize。。。
和上限大小
MaxSize。。。
5.那么数组存不下了怎么办,这里就要用到扩容,
假设初始大小是16,如果数组的大小不够用了,或者达到某个值了,需要扩容resize()
扩容是不是要有一个依据: 16 数组大小达到12的时候就要进行数组大小的扩容
Size > totalSize * 小于1的数 0.75
hashMap的扩容:当使用总容量大于总容量的3/4时,要进行数组扩容
6.链表的长度就可以无限增大了嘛,答案是错误的,链表的大小也是有一个限制的:
链表太长会严重影响效率
改变链表的结构
达到一个限制值之后,也需要一个改变
JDK1.8的时候,在链表的长度达到一个长度的时候就将其结构改成红黑树
链表长度大于8了就修改链表的结构,红黑树
7.对于一个Put进来的节点,要存储在数组或链表的哪里呢?
1.首先通过Hash函数来算出hash值, (h = key.hashCode())^(h>>>16) 将key值得高16位与低16位做异或运算,目的是使得算出的hash值尽量分散,充分的将int的32位都应用起来了
2.(n-1)&hash函数-->得到下标,然后再存储进数组或链表即可。
3.每一次put都会判断列表的长度
1).HashMap的原理,内部数据结构
底层使用哈希表(数组+列表),当列表过长时会将列表转为红黑树已实现O(logn)时间复杂度内的查找
2).讲一下HashMap中的put方法过程:
1.对key求Hash值,来求标
2.如果没有碰撞直接放入槽中
3.如果发生了碰撞,以链表的方式放到链表的下方
4.如果链表的长度超过阈值(8) 就转成红黑树jdk1.8
5.如果节点已经存在就替换旧值
6.如果槽满了(容量*加载因子)0.75 就resize扩容
3).HashMap中hash函数怎么实现的?还有哪些hash的实现方式
1.高16bit不变,与低16位做异或运算得到hash值
2.(n-1)&hash函数-->得到下标
还有哪些Hash实现方法
4).HashMap怎么解决冲突的,讲一下扩容过程,假如一个值原来在数组中,现在移动了新数组,位置肯定改变了,那是什么定位到这个值在新数组中的位置的
1.将新节点加到列表后
2.容量扩充为原来的二倍,然后对每个节点计算哈希值
3.这个值只可能在两个地方,一个是原下标的位置,另一种在下标为《原下标+元容量》的位置
5).抛开HashMap,hash冲突有哪些解决方法
1.开放地址法
2.链地址法
6).针对Hashmap中某个Entry链太长,查找的复杂的可能达到O(n),怎么优化
1.将链表转换为红黑树,jdk1.8已经实现了(链表长度超过8的时候)
红黑树和链表有一个他们各自数量的节点,当节点数目小于6时要转换成链表,大于8时要把链表转换成红黑树
7).hashMap线程安全问题:
1.在多线程同时操作hashMap的时候,put方法没有设置同步方法,所以不是线程安全的,HashMap在并发执行put操作室会引发死循环,因为多线程会导致HashMap的Entry链表形成环,一旦成环,Entry的Next节点永远不能为空,产生死循环
2.HashTable是线程安全的,但是在并发情况下效率低下,原因是所有访问hashTable的线程都必须竞争同一把锁
3.为线程加锁会影响线程执行效率,使用ConcurrentHashMap:线程安全切高效的HashMap
ConcurrentHashMap简介:
假如一个容器中有多把锁,没把锁英语锁容器中的一部分数据,那么在多线程访问的容器里的不同数据段,线程之间不会存在锁竞争,从而可以有效地提高并发访问的效率,这就是ConCurrentHashMap的锁分段技术
首先将数据分成一段一段的存储
然后给每一段配一把锁
当一个线程占用锁访问其中一个段数据的时候,其他短的数据也能被其他线程所访问
原文:https://www.cnblogs.com/PoorGuy/p/10397346.html