目录
类图
图解
Map
特点
键值对
的存储
元素需要重写hashCode()和equals()
api
增加:put(K key, V value);
删除:clear(); remove(Object key);
查看:get(Object key); size(); entrySet(); keySet(); values();
判断:containsKey(Object key);containsValue(Object value);
equals(Object o); isEmpty();
AbstractMap
特点
- 元素无序、唯一
- 底层结构:哈希表(数组 + 链表)
HashMap
特点
线程不安全,效率高
key可以存入null,并且唯一
属性
// 数组初始化长度 ==> 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 数组最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 负载因子/加载因子的默认值:0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 底层主数组
transient Entry<K,V>[] table;
// 元素的数量
transient int size;
// 数组扩容的边界值/门槛值,默认为 0
int threshold;
// 装填因子、负载因子、加载因子,影响扩容边界值
final float loadFactor;
内部类
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
负载因子为什么是0.75
数组扩容边界值 = 数组长度 * 负载因子
- 如果设置为1:空间利用率高;但哈希碰撞的几率也变高,而链表的查询效率较低
- 如果设置为0.5:哈希碰撞的几率低,产生链表的几率低,查询效率高;但扩容频繁,空间利用率低
- 所以在 0.5 ~ 1 之间,取中间值:0.75
JDK1.7
工作原理
- 元素:
key,value
- 调用key的hashCode(),得到
hash
值,hash值对数组table长度进行取余hash & (length - 1) 等效于 hash % length
(& 的效率比 % 要高),得到元素在数组中的位置(i) - 通过节点的next属性,遍历table[i]上链表的节点e
- 比较
e.key 和 key
是否相等- e.hash == hash:首先判断hash值,hash不相等,则key一定不相等
- e.key == key:判断key的地址值,地址值一样,则key一定相等
- key.equals(e.k):最后调用equals() 判断key 是否相等
- 若相等,value替换oldValue,并
return oldValue
- 判断是否需要
扩容
- 采用头插法,获取table[i]上的节点e,封装元素
new Entry(hash, k, v, e)
next指向e,table[i]重新指向新节点,成为新的头节点 return null
构造函数
// 无参构造
public HashMap() {
// this(16, 0.75)
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
// 如果初始化数组长度 大于 数组最大容量,则使用最大容量进行初始化
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
// 左移1位,等同于 * 2
// 确保数组的长度为 2的整数倍
capacity <<= 1;
// 确定使用的负载因子
this.loadFactor = loadFactor;
// threshold = capacity * loadFactor = 16 * 0.75 = 12;
// 确定数组扩容的边界值:12
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
// 创建主数组,长度为16
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
- 默认数组长度为16,负载因子为0.75
- 调整数组长度,变为2的整数倍
- 确定数组扩容边界值:
数组长度 * 负载因子
- 创建指定长度的Entry数组table
为什么数组长度必须是2的整数倍?
h & (length - 1) 等效于 h % length
的前提就是length == 2^n
- 减少哈希冲突
put()
添加元素
public V put(K key, V value) {
// 对key == null处理,允许key的值为空
if (key == null)
return putForNullKey(value);
// 获取key的哈希值
int hash = hash(key);
// 得到元素在数组中的位置
int i = indexFor(hash, table.length);
// 哈希碰撞
// 对table[i]上的链表元素进行循环,key相等则替换value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// e.hash == hash:首先判断hash值,hash不相等,则key一定不相等
// e.key == key:判断key的地址值,地址值一样,则key一定相等
// key.equals(k):最后调用equals() 判断key 是否相等
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
// 获取原value
V oldValue = e.value;
// 替换成新value
e.value = value;
e.recordAccess(this);
// 返回原value
return oldValue;
}
}
modCount++;
// 添加元素
addEntry(hash, key, value, i);
return null;
}
hash()
计算key的哈希值
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
// 二次散列,没有直接使用hashCode()的值,减少哈希冲突
h ^= k.hashCode();