🤯烧点脑子使劲看--HashMap源码分析(上)

🤯烧点脑子使劲看--HashMap源码分析(上)

「这是我参与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👩🏻‍🚀来自一个互联网底层组装员,下一篇再见! 📖

分类:
后端
标签: