「这是我参与2022首次更文挑战的第26天,活动详情查看:2022首次更文挑战」
一、概述
HashMap最早出现在JDK1.2中,是基于Map接口的实现,储存的内容是键值对(key-value),HashMap中的键和值都可以为null。HashMap的实现并不是同步的,也就是说它不是线程安全的。
二、数据结构
在JDK1.8中,HashMap的数据结构为数组+链表+红黑树
,红黑树是HashMap在JDK1.8新引入的数据结构,主要是为了解决链表过长导致查询效率变成O(n)的问题。
当插入一个键值对时,HashMap会根据key计算出hash,再根据hash确定在数组中的位置,如果发生hash碰撞,将使用链表的形式储存,当链表过长时,将链表转为红黑树。
三、属性
默认容量
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
最大容量
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
默认负载因子
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
将链表转为红黑树的阈值
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
将红黑树转为链表的阈值
/**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;
将链表转为红黑树的最小容量
/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;
储存元素的数组
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
将数据转化为Set的形式储存,主要用于迭代
/**
* Holds cached entrySet(). Note that AbstractMap fields are used
* for keySet() and values().
*/
transient Set<Map.Entry<K,V>> entrySet;
数组中元素的个数
/**
* 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;
扩容阈值
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
int threshold;
负载因子
/**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
5.3 get
get方法首先调用hash方法,计算出key的哈希值,在通过getNode方法得到对应的元素,如果不等于null,返回元素的value,否则返回null。
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
getNode方法实现:
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// 数组已经初始化,并且传入的hash值对应的数组下标元素不为null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// key等于头节点的key,返回头节点
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// key不等于头节点的key
if ((e = first.next) != null) {
// 红黑树情况
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// 链表情况,遍历链表,直到找到相同的key或者遍历到结尾
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
5.4 remove
remove方法同样先调用hash方法计算出key的哈希值,再调用removeNode方法移除元素。
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
removeNode方法:
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;
// 数组已经初始化,并且传入的hash值对应的数组下标元素不为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;
// 传入的key等于头节点的key,待删除的节点为头节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
// 红黑树情况,调用TreeNode.getTreeNode方法获得要删除的节点
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,说明之前匹配到了要删除的节点
// 如果matchValue等于false,则不需要匹配该节点的value,否则需要node的value和传入的value相等才能删除
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
// 如果该节点是红黑树节点,调用TreeNode.removeTreeNode方法移除节点
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
// 如果该节点是头节点,只需要把数组对应的下标指向下一个节点
else if (node == p)
tab[index] = node.next;
// 如果不是头节点,此时的p为前一个节点,只需要将前一个节点的next指向当前节点的next
else
p.next = node.next;
// 修改次数+1
++modCount;
// 元素个数-1
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
跋尾
本篇内容就到这里了~ 我是Zeus👩🏻🚀来自一个互联网底层组装员,下一篇再见! 📖