HashMap
在jdk1.7版本其底层结构是:数组+链表
在jdk1.8版本之后底层结构修改成为:数组+链表+红黑树
在扩容机制上:
jdk1.7:当满足扩容条件后-->其初始默认的容量为16,每次扩容都×2;(只有在添加第一个元素时,才会初始化一个数组:长度为16)
其原因是:HashMap在扩容时选择了位运算。当向集合中添加元素时,它将使用(n-1) & hash的计算方法来获得元素在集合中的位置。只有当相应位置的数据都为1时,运算结果也为1。当HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111111这样形式的。这样,在用添加元素的hash值进行位运算时,可以充分散列,使添加元素均匀分布在HashMap的各个位置,减少hash碰撞。*
jdk1.8:在扩容是也是创建一个新的数组,然后将原来数组中的元素进行复制;但是需要注意的是,若原来数组位置上是一个树;在将树上的元素复制到新的数组中时,会重新计算位置那么可能发生的情况就会存在很多
1.一棵树拆成了两个链表,或者很多的散列元素
2.一棵树 拆成了两棵树
3.一棵树还是一棵树
.......
生成红黑树的条件-->数组长度大于64,链表长度大于8;
HashMap的构造方法
存在有参构造,new Hash(初始的数组长度,加载因子)
存在无参构造---->调用了带参构造默认的给初始长度和加载因子赋值了
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16//默认的初始容量
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认的加载因子
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;//数组的最大容量
添加数据时,先根据key算hash;然后根据hash放到数组中;
1.但是直接以算出来的hash值作为索引的下标是不可取的;
原因是:这样会以hash算法算出来的下标会过大,这样就会导致数组过大;
采取的办法是,算出来的hash再进行一次位运算,得到索引的下标,然后放大数组中,若算出来数组索引的位置是没有元素的就直接存放,若已经有元素了就会形成一个链表,这个时候就需要进行链表的添加了;
在1.7版本中采用的是头插法(https://www.cnblogs.com/xuzhidong/p/16852982.html):
2.需要注意的是,若是直接采取头插法,那么放进去的元素就会找不到,因为,形成的链表是一个单向链表,且在数组下标位置存放的是链表的尾部元素,直接存放会导致新进的元素没有指针指向;
所以新添加的元素(的引用--地址值)会直接占据原来数组下标位置(也就是"移动")-----注意的是在数组中存放的并不是该元素,而是该元素的引用,元素的本身实际上是在堆中的;
结论头插法配合"移动"才能实现数据的添加;
//java伪代码的实现
table[index] = new Node("第一个添加的元素",null);
table[index] = new Node("第二个添加的元素",table[index]);//就在这一步完成了类似"移动"的代码
HashMap类中的threshold(阈值),这个属性是用来判断是否进行扩容的;
注意的是阈值的计算不是使用HashMap类中的size属性×加载因子,而是使用数组的长度×(loadFactor)加载因子;
size是指整个数组加上链表的中的元素,并非数组的长度;