前言:Map 作为key-value 数据结构存放数据,它的内部结构是怎么的,它的存放和获取又是怎样的;本文对jdk8 库中对HashMap的数据存放进行探究。
1 使用:
// 声明
Map<String,Object> map = new HashMap(10, (float) 0.8);
// 放入元素
map.put("key","value");
map.put("key","123");
// 获取元素
map.get("key");
// 移除元素
map.remove("key");
// 元素的数量
map.size();
// entry 遍历
Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = it.next();
System.out.println("key:" + entry.getKey() + " "
+ "Value:" + entry.getValue());
}
2 过程:
参数:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
// 默认初始容量 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;
// 当链表中的元素个数大于等于 8,并且数组的长度大于等于 64 时将链表转为红黑树
static final int TREEIFY_THRESHOLD = 8;
// 当链表中的元素个数大于等于 8,并且数组的长度大于等于 64 时将链表转为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;
// 当红黑树的长度小于 6 时转为链表
static final int UNTREEIFY_THRESHOLD = 6;
// 第一次使用时,才进行初始化操作
transient Node<K,V>[] table;
// 阈(yu)值,由负载因子和容量决定:CAPACITY * LOAD_FACTOR,默认为 16 * 0.75 = 12
// 当哈希桶数组内的节点数大于该值时,则扩容
int threshold;
/** tab 数组的长度
* The number of key-value mappings contained in this map.
*/
transient int size;
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
}
2.1 new HashMap:
Map<String, Object> map = new HashMap(20);
// 无参构造
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 指定初始容量
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 指定集合转化为 Map
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
// 指定初始容量和加载因子
public HashMap(int initialCapacity, float loadFactor) {
// 对初始化数组的长度和负载因子进行校验
if (initialCapacity < 0)// 初始化长度不能为0
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)// 长度超过int 32 位的最大长度
initialCapacity = MAXIMUM_CAPACITY;// 取最大长度
if (loadFactor <= 0 || Float.isNaN(loadFactor))// 负载因子数字判断
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;// 赋值负载因子
this.threshold = tableSizeFor(initialCapacity);// 扩容阈值 初始化下一次需要扩容时 的长度
}
/** 返回一个大于 cap 的最小的 2 的 n 次幂,比如 cap=100,则返回 128。 cap=16,则返回 16
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
2.2 put(K key, V value) :
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 得到key 的hash 数值
static final int hash(Object key) {
int h;
// k 的hashCode 与k 的hashCode的高16位进行异或
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 变量声明数组 tab = null; p= null; n;i
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 将table 赋值给tab ;将tab 的长度赋值给n
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;// tab 长度为空,说明没有进行初始化,需要进行初始化
// 获得对应tab 的位置坐标,并将改位置的值赋值给p
// 如果p 节点为null ,则表示改tab 位置没有被元素占用可以直接放入数组中
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);// 初始化node 节点 ,将改节点放入tab 数组中
else {// 如果发现tab 数组中改位置已经有元素占用
// 声明e = null ; k
Node<K,V> e; K k;
// 如果tab 位置中元素的hash 值和要放入元素的hash 值相同并且
// 此时tab位置中的元素和将要放入的元素key 是相同的,则将tab位置中的元素 赋值给e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果发现tab 位置中元素和要放入的元素key 不是同一个,
// 并且 tab 位置中元素 是树结构则进入树结构进行节点的存入
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {// 否则为链表结构则进入链表结构数据的存入
// 没有循环结束条件的 for
for (int binCount = 0; ; ++binCount) {
// 将tab 位置中元素的下一个节点 赋值给e,
// 如果发现下一个节点为null ,则可以将新的node 加点加入到链表中
if ((e = p.next) == null) {
// 初始化节点并放入到单向链表中
p.next = newNode(hash, key, value, null);
// 如果链表的长度大于等于7
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);// 进入链表转换为红黑树的逻辑
break;
}
// 如果 e 节点的key 和要存入node 节点的key 是相同的,
// 则跳出循环(后续将value 直接替换 到原有node 节点的value)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;// 等价于 p=p.next;
}
}
// 如果e 不为null 则在原有的结构中 找到了与本次要存入key 相同的node 节点,
// 则执行替换操作
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;// 替换完成返回本次要存入的value
}
}
++modCount;// modCount 自增
// 将元素的长度+1 ;如果增加元素后发现 长度已经达到扩容的条件
if (++size > threshold)
resize();// 进行扩容
afterNodeInsertion(evict);
return null;
}
// Create a regular (non-tree) node
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
resize() table 的初始化或者数组达到扩容需求:
此方法用来对hashMap进行 初始化(如果没有进行过初始化),或者数组长度达到阈值时进行扩容
final Node<K,V>[] resize() {
// table 数组赋值给 oldTab
Node<K,V>[] oldTab = table;
// 如果原有的table 是null 则返回0 ,否则返回 原有的table 长度;
// 第一次初始化时 oldTab为null ,oldCap =0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 将扩容的阈值赋值给oldThr
int oldThr = threshold;
// 声明newCap = 0;newThr =0
int newCap, newThr = 0;
if (oldCap > 0) {// table 已经被初始化即扩容时进入
// 如果 之前的table 数组长度已经大于int 最大值
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;// 扩容阈值赋值为int 的最大值
return oldTab;// 直接返回原有的 table
}
// 将原有的table 的长度赋值给 newCap ,左移一位(相当于*2)
// 即新的数组长度比老的长度扩大一倍
// table 长度大于等于 16
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 将原有的扩容阈值 *2 赋值给newThr
newThr = oldThr << 1; // double threshold
}
// 如果扩容的阈值大于0
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;// 将阈值赋值给newCap
else { // zero initial threshold signifies using defaults
// 如果原有的阈值为0 则进行初始化
newCap = DEFAULT_INITIAL_CAPACITY;// 数组新的长度 newCap = 16
// 新的阈值newThr = 0.75*16= 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 如果阈值为0,初始化阈值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;// ft
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 赋值阈值
threshold = newThr;
// 新的数组
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 将新的数组赋值给table
table = newTab;
// ,第一次初始化时oldTab 为null;
// 后续如果达到数组长度扩容时需要对之前table 的数据进行转移
if (oldTab != null) {
// 此时oldCap 已经进行一次左移操作(相当于*2)
for (int j = 0; j < oldCap; ++j) {
// 声明e =null
Node<K,V> e;
// 依次将算作的元素赋值给 e
if ((e = oldTab[j]) != null) {
// 如果e 不为null 则代表有数据,需要向新的数组转移
oldTab[j] = null;// 将oldtable 改位置的元素置空
// 如果e 节点的下一个节点为null ,则证明改数组位置只有一个节点
if (e.next == null)
// 在新的newTab 重新计算数组下标并赋值
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)// 如果该位置是树节点
// 树节点数据的转移
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order 该位置是链表结构
// 声明 loHead = null, loTail = null
Node<K,V> loHead = null, loTail = null;
// 声明 hiHead = null, hiTail = null
Node<K,V> hiHead = null, hiTail = null;
// 声明next = null
Node<K,V> next;
do {
// 链表的下个节点
next = e.next;
// 链表节点的hash 对扩容后的2倍oldCap 取与,
if ((e.hash & oldCap) == 0) {
if (loTail == null)// 初始loTail 为null
loHead = e;// 将e 节点赋值给loHead 节点
else
loTail.next = e;
loTail = e;// 将e 节点赋值给loTail
}
else {// 链表节点的hash 对 之前table的length 取模下标不为0
if (hiTail == null)
hiHead = e;// hiHead 执行e
else
hiTail.next = e;
hiTail = e;// hiTail 指向e
}
} while ((e = next) != null);// 链表依次遍历
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;// 赋值到新的数组中
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;// 赋值到新的数组中
}
}
}
}
}
// 返回新的数组
return newTab;
}
扩容时红黑树的处理:
split(this, newTab, j, oldCap):
/**
** map HashMap 自身, tab 新的数组,index 当前数组下标,bit 扩容后的数组长度
**/
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
// 将数组的e 节点 赋值给b
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
// 声明节点
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
// 将节点赋值给e ,声明next 。循环条件 e不为null;下次遍历,将next 赋值给e
// e.hash & bit) == 0 构建为条件;构建出2个双向链表
for (TreeNode<K,V> e = b, next; e != null; e = next) {
// 获取节点的下一个节点
next = (TreeNode<K,V>)e.next;
// 下一个节点 置null
e.next = null;
// 计算e。hash 的数组下标
if ((e.hash & bit) == 0) {
// loTail 赋值给e 的前置节点,第一次 e.prev = null
if ((e.prev = loTail) == null)
loHead = e;// loHead节点指向e
else
loTail.next = e;
loTail = e;// loTail 指向e
++lc;
}
else {// 计算e。hash 的数组下标 不为0
// hiTail赋值给e 的前置节点,第一次 e.prev = null
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;// hiTail 指向e
++hc;
}
}
// loHead 不为null 说明此双向链表是有元素的
if (loHead != null)
if (lc <= UNTREEIFY_THRESHOLD)// 元素的长度如果小于等于6
// 转换为单向链表,并赋值到原有数组下标的位置
tab[index] = loHead.untreeify(map);
else {// 如果元素长度大于6
tab[index] = loHead;// 将首节点 放入tab 数组中
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);// 重新构建红黑树
}
}
// 散列到扩容后的数组位置 hiHead 不为null
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)// 长度小于等于6 构建单向链表
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;// 构建红黑树
if (loHead != null)
hiHead.treeify(tab);
}
}
}
/**
* Returns a list of non-TreeNodes replacing those linked from
* this node.
*/
final Node<K,V> untreeify(HashMap<K,V> map) {
// 声明hd 首节点为null,tl 尾节点指针为null
Node<K,V> hd = null, tl = null;
// 循环,开始条件为双向链表首节点,
// 条件为q 节点不为null,依次对双向链表向后遍历
for (Node<K,V> q = this; q != null; q = q.next) {
// 构建 node 节点
Node<K,V> p = map.replacementNode(q, null);
// 第一次tl 为null 随后循环tl不为null
if (tl == null)
hd = p;// 将当前构建的p 节点赋值给hd 节点
else
tl.next = p;// 将尾节点的下一节点指向新创建的p 节点
tl = p;// 将构建的p 节点赋值给tl
}
// 返回链表首节点
return hd;
}
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
return new Node<>(p.hash, p.key, p.value, next);
}
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
当我们不断的向Map 中放入元素,当table 中某个数组位置的链表长度 大于等于7 时 进入 treeifyBin(tab, hash) 进入链表转红黑树 :
final void treeifyBin(Node<K,V>[] tab, int hash) {
// 声明 n ,index ,声明e 节点
int n, index; Node<K,V> e;
// 如果没有进行初始化,需要先进初始化并返回;
// 或者链表的长度大于7但是数组的长度小于64 也进行resize()
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
// 链表的长度大于7 并且数组的长度大于等于64
// 得到tab 数组对应坐标下的节点并赋值给e,如果节点不为空
// 声明hd,tl 节点
TreeNode<K,V> hd = null, tl = null;
// node 双向链表构建
do {
// 初始化 treeNode
TreeNode<K,V> p = replacementTreeNode(e, null);
// 第一次tl 为null,之后循环tl 为上一次的treeNode 节点
if (tl == null)
hd = p; // 初始的p 节点赋值给hd
else {
p.prev = tl;// 将当前节点的前置节点指向上一节点
tl.next = p;// 上一节点的下一节点指向p
}
tl = p;// 将p 赋值给tl
} while ((e = e.next) != null);
// 将链表中的下一个节点赋值给e,并且e 不为null 进入下一次循环
// 双向链表构架完成 hd 指向首节点
// 将首节点放入到 tab 数组中 ,并返回链表首节点
if ((tab[index] = hd) != null)
hd.treeify(tab);// 构建树形结构
}
}
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
return new TreeNode<>(p.hash, p.key, p.value, next);
}
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
treeify(tab) 树形结构构建(并没有将原有的双向链表结构打断):
/** 将构建好的双向链表节点转换为红黑树,注意转换完成之后,双向链表中节点之间的prev 和next 关系依然存在
* Forms tree of the nodes linked from this node.
* @return root of tree
*/
final void treeify(Node<K,V>[] tab) {
// 声明root 节点
TreeNode<K,V> root = null;
// 循环双向链表,初始值x = 链表的首节点;
// 循环终止条件 x 等于null;x=next 依次向后遍历
for (TreeNode<K,V> x = this, next; x != null; x = next) {
// 获取链表的下一个节点
next = (TreeNode<K,V>)x.next;
// 声明 x 节点的做节点为x本身,右节点 为null
x.left = x.right = null;
// 第一次循环 root 为null,后续循环 root 不为null
if (root == null) {
x.parent = null;// 将x 节点的父节点 置null
x.red = false;// x 的颜色为黑色
root = x;// 将x 节点赋值给root 节点
}
else {
// 得到节点的key
K k = x.key;
// 得到节点的hash
int h = x.hash;
// 声明kc 为null
Class<?> kc = null;
// 没有循环终止条件的for 将当前x 节点放入到 treeNode 中
for (TreeNode<K,V> p = root;;) {
// 循环开始 p 为root 节点
// 声明dir ,ph
int dir, ph;
// 得到 p 的key
K pk = p.key;
// 如果p 节点的hash 值要大于 本次要加入的x 的hash 值
if ((ph = p.hash) > h)
dir = -1;//
else if (ph < h)
dir = 1;// 如果p 节点的hash 值要小于 本次要加入的x 的hash 值
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
// 如果p 节点的hash 值要等于 本次要加入的x 的hash 值
dir = tieBreakOrder(k, pk);
// 将p 节点赋值xp 。第一次循环 xp 为root 节点
TreeNode<K,V> xp = p;
// 判断要放在p 的位置 dir小于等于0 p的左子节点赋值给p ,
// 否则将右子节点赋值给p
// 如果节点为空代表没有被元素占用可以放入,否则进入下一次循环
if ((p = (dir <= 0) ? p.left : p.right) == null) {
// 当前要放入的节点的父节点 指向xp
x.parent = xp;
if (dir <= 0)// 放入xp 的左子节点
xp.left = x;
else // 放入xp 的右子节点
xp.right = x;
// 放入成功进行重平衡,插入和删除节点的时候通过旋转和改变节点颜色,
// 让其符合红黑树的定义
root = balanceInsertion(root, x);
break; // 跳出循环
}
}
}
}
// 确保root 节点是tab 数组中元素位的首节点
moveRootToFront(tab, root);
}
/**
* Ensures that the given root is the first node of its bin.
*/
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash; // root 的table 下标位置
// 得到改位置的节点
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
if (root != first) {// 如果改位置不为root
// 声明rn 节点
Node<K,V> rn;
// 将root 节点放入改位置
tab[index] = root;
// 获取root 节点的前置节点
TreeNode<K,V> rp = root.prev;
// root 节点的下一个节点不为空
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
// 将root 下一个节点的前置节点指向 root 的前置节点
if (rp != null)// 前置节点不为null
rp.next = rn;
// 前置节点的下一节点指向 root 的下一节点 将root 节点从链表中剔除
if (first != null)// 如果first 节点不为null
first.prev = root; // 将first 节点的前置节点置为 root
root.next = first;// 将root 节点的下一节点置为 first
root.prev = null;// 将root 的前置节点 置为null
}
// 校验
assert checkInvariants(root);
}
}
当发生hash 冲突且table数组中改位置的元素为树形节点则将改节点放入到红黑树中:
putTreeVal(this, tab, hash, key, value):
/**
* map hashMap 本身,tab node 数组,h 为当前放入key-value 的key hash ,k 为key, v 为value
* Tree version of putVal.
*/
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
// root 根节点赋值
TreeNode<K,V> root = (parent != null) ? root() : this;
for (TreeNode<K,V> p = root;;) {// 循环插入节点
// 声明
int dir, ph; K pk;
// 将p 节点的hash 赋值给 ph
if ((ph = p.hash) > h)// 要插入的key hash 小于 p 的hash
dir = -1;
else if (ph < h)// 要插入的key hash 大于 p 的hash
dir = 1;
// 要插入的key hash 等于 p 的hash 并且key 是相同的,则直接返回改节点
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
// 要插入的key hash 等于 p 的hash
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
// 遍历p的左子树和右子树 查找改相同的key 如果找到直接返回改节点
return q;
}
dir = tieBreakOrder(k, pk);
}
// p 节点赋值 给xp
TreeNode<K,V> xp = p;
// 如果dir <=0 将p的做子节点赋值给p ,否则将右子节点赋值给p ,
// 如果p 为null 则代表改位置没有被占用可以放入元素
if ((p = (dir <= 0) ? p.left : p.right) == null) {
// 将节点插入到红黑树中,同时将该节点 插入到 p 和 p.next 双向链表的中间位置
// p的 next 节点赋值给xpn
Node<K,V> xpn = xp.next;
// 初始化treeNode 节点
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)// 放入p 的左子节点
xp.left = x;
else// 放入p 的右子节点
xp.right = x;
// 将x 节点插入到p 和p的下一个节点中间
xp.next = x;// p 的下一个节点指向 初始化的x
// x 节点的上一节点指向p ,x 的父节点指向p
x.parent = x.prev = xp;
if (xpn != null)// 之前p 的下一节点
((TreeNode<K,V>)xpn).prev = x; // 将之前p 的下一节点的前节点指向刚插入的x节点
// 重平衡红黑树 并保证root 节点为首节点
moveRootToFront(tab, balanceInsertion(root, x));
return null;// 插入成功返回null
}
}
}
2.3 获取元素get(“key”):
public V get(Object key) {
Node<K,V> e;// 声明节点e
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
// 声明tab 数组,声明首节点first,声明 节点e;声明n,k
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// table 数组赋值给tab ,数组不为空并且数组的长度大于0,
// 并且通过key 的hash 对应的table 的下标元素不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 如果找到的数组原始的hash 和 key 的hash 相同
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {// 取得下个节点
// 下个节点是 tree 结构,则从tree 中找到对应key的节点
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {// 从链表找到对应key 的节点
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
final TreeNode<K,V> getTreeNode(int h, Object k) {
// 从根节点进行遍历查找对应key 的节点
return ((parent != null) ? root() : this).find(h, k, null);
}
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
// 将根节点赋值给p
TreeNode<K,V> p = this;
do {
// 声明 ph ,dir,pk
int ph, dir; K pk;
// 将p 的左子节点赋值给pl;右子节点赋值给pr,声明q 节点
TreeNode<K,V> pl = p.left, pr = p.right, q;
// 如果要查找的key 对应的hash 小于p 节点的hash
if ((ph = p.hash) > h)
p = pl;// 证明要查找的节点在左子树
else if (ph < h)// 如果要查找的key 对应的hash 大于p 节点的hash
p = pr;// 证明要查找的节点在右子树
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;//如果要查找的key 对应的hash 等于p 节点的hash 则对比key
// key 相同则找到节点直接返回p 节点
else if (pl == null)// 如果p 的左子节点是null
p = pr;// 将p 指向右子节点
else if (pr == null)// 如果p 的右子节点是null
p = pl;// 将p 指向左子节点
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;// 在此判定p 执行左子节点或右子节点
else if ((q = pr.find(h, k, kc)) != null)// 从p 的右子节点寻找
return q;// 找到直接返回
else
p = pl;// 否则将p 的左子节点赋值给p 继续循环
} while (p != null);
return null;// 没有找到则返回null
}
2.4 从Map 中移除元素 remove(key):
public V remove(Object key) {
Node<K,V> e;// 声明节点e
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
// hash:对应key 的hash 值;key :对应的key,value= null;matchValue= false
// movable= true
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
// 声明tab 数组,p 节点,n,index
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
// 如果对应key 的hash 对应的数组位置上有元素
// 声明node,e 节点,声明k ,v
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;// 如果改数组的元素就是要找到元素将p 赋值给node
else if ((e = p.next) != null) {// 改数组下标的元素有后续的元素
if (p instanceof TreeNode)// 如果p 是红黑树,则从红黑树中获取对应key 的节点
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {// 改元素是链表,从链表中遍历获取改元素
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
// 如果node 不为空,既找到了改节点,则对改节点进行移除
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)// 如果是红黑树则从树中移除
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)// 如果当前数组中对应下标位置就是node
tab[index] = node.next;// 将node 的下一节点放入数组
else
p.next = node.next;// 将p 从链表中移除
++modCount;
--size;// map 中元素的个数-1
afterNodeRemoval(node);
return node;// 返回node 节点
}
}
return null;
}
// 从红黑树中获取改节点
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
// map:hashMap 本身,tab 为数组,movable= true
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;// 声明n
if (tab == null || (n = tab.length) == 0)
return;// 如果hashMap 没有进行初始换则直接返回
// 找到改节点key hash对应tab 数组的下标
int index = (n - 1) & hash;
// 将tab 数组中对应位置的元素赋值给first 节点,
//声明root 节点将first赋值给root ,
// 声明rl节点
TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
// 将node 节点的下一节点赋值给succ ,
// 声明pred 节点将node 节点的前置节点进行赋值
TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
if (pred == null)
tab[index] = first = succ;//将node 节点的下一节点赋值给first 节点,
//并将node 节点下一节点放入到数组中
else// node 节点不为root 跟节点
pred.next = succ;// 将node 节点的前置节点的下一节点指向node 的下一节点
if (succ != null)// node 的下一节点不为空
succ.prev = pred;// 将node 下一节点的前置节点指向node 的上一节点
// 此时已经将node 节点从双向链表中进行了移除
if (first == null)// 如果root 节点是空的则直接返回
return;
// 如果root 节点的parent 不为空则说明改root 节点不为根节点
if (root.parent != null)
root = root.root();// 找到红黑树的根节点并赋值给root
// 如果root节点为null 或者root 没有右子节点,
// 或者root 没有左子节点,或者root 的左子节点
// 的左子节点为null 红黑树大小,则将红黑树转换成成链表结构
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
// 将移除了改node 节点之后的红黑树next 遍历
// 组合成单向链表并返回链表的首节点
tab[index] = first.untreeify(map); // too small
return;// 完成转换直接返回
}
// 声明p 节点为当前要移除红黑树的node节点,pl 为当前节点的左子节点,
// pr为当前节点的右子节点
// 声明replacement 节点
TreeNode<K,V> p = this, pl = left, pr = right, replacement;
// 如果改节点即有左节点又有右节点,需要找到可以上位 顶贴要删除节点的位置
if (pl != null && pr != null) {
// 将要移除的node 节点的右子节点赋值给s ,声明sl 节点
TreeNode<K,V> s = pr, sl;
// 将node 节点的右子节点的左子节点 赋值给sl ,
// 如果sl 不为null ,即node 节点的右子树
// 同样拥有左节点
while ((sl = s.left) != null) // find successor
s = sl;// 继续将sl 节点赋值s 节点
// 上面while 循环完成后,s,sl 指向node 右子树,最深的左节点
// 将改s 节点的颜色赋值给c,将p 节点的颜色赋值给节点颜色,
// 将p节点严重赋值为s 节点颜色
boolean c = s.red; s.red = p.red; p.red = c; // swap colors
// 声明sr 节点为s 的右子节点
TreeNode<K,V> sr = s.right;
// 声明pp 节点为 p 的父节点
TreeNode<K,V> pp = p.parent;
// p 的右子节点的左子节点为空,即p 的右子节点只有右子树
if (s == pr) { // p was s's direct parent
p.parent = s;// 将p 的父节点指向 s节点
s.right = p;// s 的右子节点指向p节点
}
else {
TreeNode<K,V> sp = s.parent;
if ((p.parent = sp) != null) {
if (s == sp.left)
sp.left = p;
else
sp.right = p;
}
if ((s.right = pr) != null)
pr.parent = s;
}
// 将p 的左子节点置空
p.left = null;
if ((p.right = sr) != null)
sr.parent = p;
if ((s.left = pl) != null)
pl.parent = s;
if ((s.parent = pp) == null)
root = s;
else if (p == pp.left)
pp.left = s;
else
pp.right = s;
if (sr != null)
replacement = sr;
else
replacement = p;
}
else if (pl != null)// 如果只有左子节点
replacement = pl;// 左子节点赋值给replacement 节点
else if (pr != null)// 如果只有右子节点
replacement = pr;// 右子节点赋值给replacement 节点
else
replacement = p;// 改节点的左右节点都为空,将当前节点赋值给replacement 节点
if (replacement != p) {// replacement 不是p 节点
// 将replacement节点的父节点指向 p 节点的父节点,并将p 节点的父几点赋值pp
TreeNode<K,V> pp = replacement.parent = p.parent;
if (pp == null)// pp 节点为null 则原有p 节点为root 节点
root = replacement;// 将root 节点执行替换的节点
else if (p == pp.left)// p 节点不为root 节点,如果p 节点是p 父节点的左节点
pp.left = replacement;// pp 的左节点指向要替换的节点
else// p 节点是pp 的右子节点
pp.right = replacement;// pp 的右节点指向要替换的节点
// 将p 节点从树中移除
p.left = p.right = p.parent = null;
}
// 如果p 节点是红色 直接返回root 否则执行 balanceDeletion
// 即如果移除的节点是红色 则不影响红黑树的定义,否则需要重新为红黑树进行调整
TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
if (replacement == p) { // detach
TreeNode<K,V> pp = p.parent;
p.parent = null;
if (pp != null) {
if (p == pp.left)
pp.left = null;
else if (p == pp.right)
pp.right = null;
}
}
if (movable)
moveRootToFront(tab, r);// 确保root 节点为首节点
}
// 获取红黑树的根节点
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
final Node<K,V> untreeify(HashMap<K,V> map) {
Node<K,V> hd = null, tl = null;
for (Node<K,V> q = this; q != null; q = q.next) {
Node<K,V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
return hd;
}
2.4 遍历HashMap:
Map<String, Object> map = new HashMap(32);
Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = it.next();
System.out.println("key:" + entry.getKey() + " "
+ "Value:" + entry.getValue());
}
entrySet().iterator():
调用EntrySet 的 iterator()
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
// 调用HashIterator 的构造方法:
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;// 初始化下标
// next 赋值 初始化时 next = t[0]
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
// HashIterator nextNode
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;// 元素赋值
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
// next 下一元素赋值
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
// 遍历时元素判断
public final boolean hasNext() {
return next != null;
}
// 遍历时下一元素获取 it.next()
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
3 总结:
3.1 HashMap 的数据结构使用的是数组+单向链表+红黑树(红黑树结构中包包含了双向链表)的数据结构,HashMap 数据结构是在put 元素的时候进行的初始化;
3.2 在向HashMap 存放元素时,如果发生hash 碰撞会先在对应碰撞元素的数组位置形成单向链表,如果单向链表的长度大于7并且数组的长度大于64 则触发单向链表转换为红黑树;
3.3 在HashMap 中数组的长度达到阈值时回触发resize() ,先将现有的数组容量扩大一倍,然后将原有的数据向新的数组中转移;转移过程中,如果发现之前时红黑树的节点在转移时元素数量小于等于6则会触发红黑树转链表结构;
3.4在对HashMap 进行get 时,会先找到对应数组下标位置的元素进行比对,如果key 相同则返回,否则判断改数组下标位置是红黑树则进入红黑树从根节点进行查找;是链表则从链表的首节点依次向后查找;
3.5 在对HashMap 进行remove时,会先找到对应数组下标位置的元素进行比对,如果该位置只有一个元素且key 是相同的则直接进行移除;如果发现改节点右其他元素,改节点是红黑树,则将改节点从红黑树中进行移除,移除后如果发现红黑树过小则触发红黑树向链表结构转变;如果是链表则从链表中移除改元素;
4 参考: