大家好,我是chowley,前几天面试的时候被问到了HashMap的内容,
今天我就来做一个详细的总结,帮助大家快速上手HashMap的关键点!
在Java集合框架中,HashMap
是一种常见且重要的数据结构,广泛应用于各种场景。了解其内部实现原理,不仅有助于大家更好地使用,也可以帮助我们了解面试中问题的关键点。
HashMap
是一种通过键值对映射关系存储数据的容器。每个键对应一个值,通过键可以快速查找对应的值。
HashMap
的内部结构包括一个数组和一些链表/红黑树。数组的每个元素称为“桶”,每个桶存储着一个链表(或红黑树)的头节点。键值对通过哈希算法映射到具体的桶。
HashMap
中的键通过哈希算法映射到具体的桶。键的哈希码通过调用键的hashCode
方法获得。
通过一系列位运算操作,HashMap
计算键的哈希码在数组中的位置,确定存储的桶。
由于哈希算法可能导致不同的键映射到同一个桶,产生哈希冲突。HashMap
通过链表和红黑树来解决冲突。当桶中的链表长度达到一定阈值时,链表会转换成红黑树,以提高查询效率。
// 无参构造方法
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 使用默认负载因子
}
// 接受另一个Map作为参数
public HashMap(Map extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
// 指定初始容量大小
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 指定初始容量大小和负载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
// 初始容量暂时存放到 threshold ,在resize中再赋值给 newCap 进行table初始化
this.threshold = tableSizeFor(initialCapacity);
}
HashMap的构造方法主要设置了默认的初始容量、最大容量、负载因子等参数,以及初始化了一些阈值。
put
方法final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node[] tab; Node p; int n, i;
// 如果数组为空或长度为0,则进行初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 计算桶的位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 如果桶中已经有元素
else {
Node e; K k;
// 判断桶中第一个元素的哈希值、键是否与当前元素相同
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果桶中是树形结构,则调用树形插入方法
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
// 桶中是链表结构
else {
for (int binCount = 0; ; ++binCount) {
// 如果链表中没有找到相同的键,则将新节点插入到链表的末尾
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果链表长度达到阈值,将链表转化为树形结构
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 如果找到相同的键,则更新值
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果找到相同的键,更新值
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 结构性修改计数器增加
++modCount;
// 如果元素个数超过阈值,进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
put方法根据键的哈希值计算桶的位置,然后根据当前桶中的元素情况,选择合适的方式进行插入,包括链表插入和红黑树插入。如果插入后元素个数超过阈值,触发扩容操作。
public V get(Object key) {
Node e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node getNode(int hash, Object key) {
Node[] tab; Node first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 如果桶中第一个元素的哈希值、键与当前元素相同,直接返回第一个元素
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 如果桶中是树形结构,调用树形查找方法
if ((e = first.next) instanceof TreeNode)
return ((TreeNode)first).getTreeNode(hash, key);
// 桶中是链表结构,遍历链表查找
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
return null;
}
get方法根据键的哈希值计算桶的位置,然后根据桶中的元素情况,选择合适的方式进行查找,包括链表查找和红黑树查找。
final Node[] resize() {
Node[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// 如果旧数组不为空,进行扩容操作
if (oldCap > 0) {
// 如果旧数组已经达到最大容量,则不再扩容
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 计算新的容量和扩容阈值
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 如果旧数组为空,使用默认容量和阈值
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 如果新的阈值为0,计算新的阈值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 更新阈值
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node[] newTab = (Node[])new Node[newCap];
table = newTab;
// 如果旧数组不为空,将元素转移到新数组
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode)e).split(this, newTab, j, oldCap);
else { // preserve order
Node loHead = null, loTail = null;
Node hiHead = null, hiTail = null;
Node 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) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
resize方法用于对HashMap进行扩容。它根据当前的数组容量和扩容阈值,计算新的容量和新的扩容阈值,并创建新的数组。然后,将旧数组中的元素根据其哈希值重新分配到新数组中。
通过对HashMap
源码的简要解读,我们可以了解到其内部实现采用数组+链表/红黑树的方式,通过哈希算法将键映射到具体的桶中。在插入元素时,会根据桶中的结构选择合适的方式进行插入。
同时,HashMap
具有动态扩容机制,当元素个数超过一定阈值时,会触发扩容操作。深入了解这些源码细节有助于更好地理解HashMap
的工作原理。
好了,以上就是本文的全部内容,如有问题欢迎留言讨论。
我是chowley,一个专注互联网技术和软件质量保障领域的博主,我们下次再见!
欢迎点赞、评论、收藏,it's important for me.
欢迎点赞、评论、收藏,it's important for me.
欢迎点赞、评论、收藏,it's important for me.