Map在我们工作中是相当常用的一个数据结构,它的实现类HashMap被我们广泛应用,下面我们一起来分析以下它是如何工作的吧!
1.HashMap特点
- 允许null值,非线程安全
- 存储采用键值对方式,一键对应一值,key值不可重复。
- 初始长度为16,默认加载因子为0.75,map长度超过160.75的长度后自动扩容为原有的1.5倍,如再超过当前map长度加载因子的长度时继续扩容
结构上采用数组加链表的方式,结构如图
我们再使用HashMap时一般使用的是put(),get(),remove()方法,以及内部扩容机制,下面我们来看一下
说到HashMap的源码,我们就不得不看一下源码中的全局变量
/**
* HashMap的默认初始化大小
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* HashMap长度的最大值
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* HashMap的默认加载因子
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
*链表被转换成红黑树的阈值,意义为当链表的长度大于8时将链表转为红黑树,
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 当红黑树的长度小于等于6时,将红黑树转话为链表
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 这个值为如果一个元素中挂载的链表太长,便会对整个HashMap中的元素重新计算Hash值,并且对HashMap扩容,但只能是对于当前Map长度小于64时才会生效
*/
static final int MIN_TREEIFY_CAPACITY = 64;
//当前代码为HashMap中存取元素的对象 next指向的是下一个Node(节点对象)
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
1.put()方法主要逻辑如下
- 获取key的Hash值
- 根据key的Hash值来进行运算获取数组下标,如此时根据数组下标获取无数据,则将数据放入此下标,如根据下标获取出来的有值,则将数据挂载到值的next中。
- 如数组值的链表中数据超过8个,结构由链表转换为红黑树,如数组值少于8个,则将红黑树转换为链表
- 当数组中的元素第一次拥有next值时,map进行扩充,再拥有时还扩充,但当map的长度大于或等于64时,数据再挂载到next值时,变不会再进行扩充,只有当数组的长度大于长度*加载因子的长度时才会扩充
以上是put()方法的主要逻辑,为了方便大家理解,我把源码贴出来,每步标上注释
//当前代码为获取key的hash值,并对hash值进行扰动,扰动的意思为减少hash碰撞
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//当前代码为put方法核心方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//table是HashMap全局变量,table中存储了Node数组,判断table是否为空,如果为空调用resize()方法
if ((tab = table) == null || (n = tab.length) == 0)
//resize()方法为对数组的大小进行调整以及链表红黑树转换,并对新的Node数组进行重新划分位置
n = (tab = resize()).length;
//如果table数组中不存在这个元素,则直接进行添加
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果table数组中存在此数据,则判段他们Key的Hash与equals是否同时相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果从table中获取的数据是TreeNode类型,则调用红黑树put方法,将此数据put到红黑树中
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果从table中获取的数据链中最后一个元素next位置为null,则挂载到最后一个数据上
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果从table中获取的数据链的长度大于等于7,则将链表转换称红黑树(或对map进行扩容)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果传入数据中的value,替换掉了原有数据中的value,则将原有value返回
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果当前map的实际长度大于长度*加载因子的长度时,则进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
2.get()方法核心逻辑如下
- 获取传入key的Hash值
- 根据当前key的Hash值与key本身去匹配,如两者都匹配则返回,如无则返回null
//获取key的hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//get核心方法
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//获取当前table数组,不为空则继续
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果当前table不为空,则根据标获取数据,如获取到数据则与传入信息进行比对,如成功则立即结束,减少对数据中next的便利,减少消耗
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果第一个数据不满足,则对链表进行便利,如有则立即结束返回
if ((e = first.next) != null) {
//判断节点是否为红黑树的类型,如是,则调用红黑树的方法遍历数据
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//如果是链表则循环判断next中的数据
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
3.remove()方法核心逻辑如下
- 获取当前需要删除key值的Hash
- 根据hash与需要删除key进行比对,同时满足则删除,删除之前需要判断删除元素的next值是否为null,如为空直接将数据置为null,如不为空则将next中的值放在当前位置
//获取当前key的Hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//删除核心方法
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//判断table是否为空长度是否为大于0,根据下标获取值是否可以获取到,获取不到直接返回null
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//如是当前数据,则赋值给node
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
//如p是红黑树类型,则调用红黑树获取节点方法
if (p instanceof TreeNode)
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不为null,这个if一定会进,用于判断如果node节点中next中有数据,则用next直接覆盖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);
//node中的next直接覆盖元素
else if (node == p)
tab[index] = node.next;
//此时node的值为p.next,故如删除node,则将node.next赋值给p.next就可以
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
至此HashMap的主要使用方法分析完毕,如有错误欢迎指正,共同学习
大家可以想一个问题,为什么当链表的长度大于8时要转换成红黑树,而不是转换成二叉树呢,欢迎留言
码字不易,觉得不错点赞哦!
点击查看更多内容
1人点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦