标签:table 修改 默认值 数组元素 hashmap 存储结构 ash 定义 允许
Map
接口定义了映射关系,有四个常用实现类:
HashMap
Hashtable
LinkedHashMap
TreeMap
HashMap
:
key
的hashCode值存储数据.synchronizedMap
或ConcurrentHashMap
.Hashtable
:
Dictionary
类.ConcurrentHashMap
,不需要线程安全>HashMap
.LinkedHashMap
:
HashMap
的子类.TreeMap
:
SortedMap
接口.Node<K,V>
保存,一个键值对对应一个结点.结点Node<K,V>
的属性:
final int hash; // 对应的哈希值
final K key; // 键
V value; // 值
Node<K,V> next; // 指向下一个结点的指针
Node[] table
(哈希桶数组)来存放这些键值对.int threshold
final float loadFactor
int modCount
int size
哈希桶数组的初始化长度为16.
负载因子loadFactor
的默认值为0.75.
哈希桶数组所能容纳的最大键值对个数为threshold
,threshold=哈希桶数组长度*负载因子
.
当存储的键值对个数超过threshold
,则需要扩容(扩大为原来的2倍).
对时间效率要求高==>降低负载因子.
内存空间紧张==>增加负载因子(可大于1).
size
:HashMap中实际存在的键值对个数(哈希桶数组及其对应的链表).
modCount
:记录内部结构发生变化的次数(增加,删除),修改某个key对应的value不属于结构变化.
哈希桶数组的长度为2的幂次,目的:为了取模和扩容时优化.减少冲突==>定位时加入高位参与运算.
当链表长度太长(超过8),链表转换为红黑树.
put
方法细节h=key.hashCode()
)h^(h>>>16)
)h&(length-1)
,等价于h%length
,但&运算效率高,length为哈希桶数组的长度)table
是否为空或null,若是,则resize()
进行扩容。table[i]
的首个元素的key是否与待插入的key一致,若相同,则直接覆盖,否则向下执行。table[i]
是否为treeNode(即判断待插入的位置是使用红黑树还是链表保存键值对),若是红黑树,则在树中插入键值对。size
是否超过最大容量threshold
,若超过,则进行扩容。注:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length; // 获取旧桶的大小
int oldThr = threshold; // 获得旧的threshold
int newCap, newThr = 0;
if (oldCap > 0) { // 若旧桶的大小大于0
if (oldCap >= MAXIMUM_CAPACITY) { // 若旧的threshold大于2^29,则直接设为最大值,并返回
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY) // 否则,若旧的threshold大于16,而加倍后仍然小于2^29
newThr = oldThr << 1; // double threshold // 则加倍
}
else if (oldThr > 0) // 若旧的threshold大于0,则threshold不进行改变
newCap = oldThr;
else { // 以上条件都不满足,则桶的大小设为16,threshold设为12
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) { // 计算新的桶大小对应的新的threshold
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; // 设置threshold,使之生效
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab; // 创建新桶
if (oldTab != null) { // 若旧的桶非空,则需要将其中的键值对迁移到新桶中
for (int j = 0; j < oldCap; ++j) { // 对于哈希桶数组中的每个元素进行检查
Node<K,V> e;
if ((e = oldTab[j]) != null) { // 若数组当前位置有键值对,则需要处理,否则直接到一个数组元素
oldTab[j] = null; //旧桶设为null,方便GC
if (e.next == null) // 若当前位置上只有一个元素,则直接将其迁移到新数组的新位置(经过hash计算)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode) // 若当前位置是以红黑树方式存储键值对,则对红黑树分裂
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // 若是链表,则将旧的链表分裂成两个新的链表
Node<K,V> loHead = null, loTail = null; // 此链表的键值存放在原来的位置
Node<K,V> hiHead = null, hiTail = null; // 此链表的键值对放在新位置(旧的位置+旧的桶大小)
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) { // 存放在旧位置上的键值对
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else { // 存放在新位置上的键值对
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) { // 分裂的链表1放在旧的位置上
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) { // 分裂的链表2放在新的位置上
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
JDK 1.8在扩容机制上的关键点:
两种遍历HashMap的方式:
// 第一种方式:将key 和 value 同时取出
Iterator<Map.Entry<String,Integer>> entryIterator = map.entrySet().iterator();
while(entryIterator.hasNext()){
Map.Entry<String,Integer> next = entryIterator.next();
}
// 第二种方式:将key取出,若要value,还需通过key遍历一遍
Iterator<String> iterator = map.keySet().iterator();
while(iterator.hasNext()){
String key = iterator.next();
}
resize()
在并发执行时容易在桶上形成环形链表.resize()
可能导致环形链表从而产生死锁(执行resize()
方法的线程无法退出)参考:
标签:table 修改 默认值 数组元素 hashmap 存储结构 ash 定义 允许
原文地址:https://www.cnblogs.com/truestoriesavici01/p/13216222.html