基础入门
- 散列表数据结构:散列表是一个基于线性表和链表的优点和长度于一身的数据结构。其实就是一个数组,然后数组里面保存的数据是一个个的链表。
- 什么是哈希:Hash也称散列,基本原理就是将任意长度的输入转化为固定长度的输出。哈希算法是单向的,不可逆的。哈希算法的冲突概率很小。但是根据抽屉原理,一定会存在不同的输入被映射为相同的输出的情况。
HashMap原理
- HashMap的继承体系:先继承了Map的一个抽象类AbstractMap,然后实现了AbstractMap。
Node数据结构分析
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
- Node<K,V> next作用:hashMap的存放类似于散列表,并且因为hash值有碰撞,如果碰撞的话就会在数组的同一个位置用链表来进行存储。
底层存储结构的介绍
![]()
- 外部是一个Node数组,默认初始化长度为16,而且数组的长度一定是2的N次方.
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
- 当发生碰撞的时候,数组里面存放的会自动转变为链表。
- 当链表的长度超过8,并且数组长度达到64个的时候,整个链表会转变为红黑树。(java8新特性)
static final int TREEIFY_THRESHOLD = 8;
static final int MIN_TREEIFY_CAPACITY = 64;
put过程介绍
![]()
- 可以看出,其实HashMap本质上就是计算出Key的hash值,然后通过一次扰动函数使其更加散列,之后再通过取余的运算添加到相应数组的位置上。
- 使用hash值得原因可能是因为Key和Value都可以使用字符串得缘故。
JDK8为何引用红黑树
- 当数据非常多的时候,难免会出现哈希碰撞的问题,而出现哈希碰撞之后就会链化。
- 当链足够长的时候,其实查找效率就会变得非常的低下,因为本身数组查找的时间复杂度是O(1),而链表则是O(N)
- 为了优化链表的查找效率,引入了红黑树的数据结构。
HashMap的扩容原理
- 扩容就是增加数组的长度。
- 数组的长度增加,Hash碰撞的次数就会减少,相对来讲也会减少非常多的链表长度。可以起到降低时间复杂度的作用。
源码分析
/**
* 默认的初始化数组长度 2的4次方
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 默认的最大数组长度 2的30次方
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认的负载因子
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 树化阙值,如果链表长度达到8,就有可能转为红黑树
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 树降级称为链表的阈值
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 当数组的长度超过64之后,才有可能转化为树
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 哈希表,可以看出hash表是一个Node数组
*/
transient Node<K,V>[] table;
/**
* Holds cached entrySet(). Note that AbstractMap fields are used
* for keySet() and values().
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* hash表的大小
*/
transient int size;
/**
* 当前hash表的修改次数
* /
transient int modCount;
/**
* 扩容阙值,当你的hash表中的元素超过阙值的时候,触发扩容
*/
int threshold;
/**
* 负载因子
* threshold = capacity * loadFactor
*/
final float loadFactor;