size记录了所有键值对的数目,包括数组(内部实现)中的和数组某些位置附属链表(hash值相同,不允许覆盖已存在的键值对,所以要以链表形式附加)中的键值对。
HashMap的内部实现是数组+链表,通过键的hash值来定位键值对在数组中的位置,是一种离散结构,所以数组的某些索引上没有存储元素。
1. 默认参数:
// 默认初始容量 - 必须是2的乘方值
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 加载因子,在0~1之间取值,太小导致空间浪费;太大导致哈希冲突频繁。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
2. 读取
// 返回和指定的键关联的键值对
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode()); // 计算哈希值
for (Entry<K,V> e = table[indexFor(hash, table.length)]; // 遍历键值对
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) // 键值对的哈希值相等并且键相同或者键的值相等
return e;
}
return null;
}
3. 添加
// 在此映射中关联指定值与指定键。如果该映射以前包含了一个该键的映射关系,则旧值被替换。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
// 遍历,如果有,则更新值,返回旧值,modCount不变
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this); // HashMap实现为空,子类可以重写
return oldValue;
}
}
// 增加新的键值对,修改次数加1
modCount++;
addEntry(hash, key, value, i);
return null;
}
// key为null时优化
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) { // 遍历查看键是否存在
if (e.key == null) { // 更新值,修改次数不会改变
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 没有找到则新增键值对,修改次数加1
modCount++;
addEntry(0, null, value, 0);
return null;
}
// 该方法取而代之put方法为构造器和伪构造器(克隆,读取对象)所调用。该方法不会增大table或者检查更改等。它调用createEntry而不是addEntry方法。
private void putForCreate(K key, V value) {
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
// 查找已存在的与该键对应的映射。这不会在克隆或反序列化时发生。仅仅会在输入的Map是可排序的且排序可变的情况下发生。
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
e.value = value;
return;
}
}
createEntry(hash, key, value, i);
}
// 遍历输入Map中的所有映射,依次调用putForCreate方法
private void putAllForCreate(Map<? extends K, ? extends V> m) {
for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<? extends K, ? extends V> e = i.next();
putForCreate(e.getKey(), e.getValue());
}
}
// 将指定映射的所有映射关系复制到此映射中,这些映射关系将替换此映射目前针对指定映射中所有键的所有映射关系。
public void putAll(Map<? extends K, ? extends V> m) {
int numKeysToBeAdded = m.size();
if (numKeysToBeAdded == 0)
return;
if (numKeysToBeAdded > threshold) {
int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
targetCapacity = MAXIMUM_CAPACITY;
int newCapacity = table.length;
while (newCapacity < targetCapacity)
newCapacity <<= 1;
if (newCapacity > table.length)
resize(newCapacity);
}
for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<? extends K, ? extends V> e = i.next();
put(e.getKey(), e.getValue());
}
}
}
// 用键、值、哈希码创建一个新的映射到数组指定的位置。根据需要来扩容。
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
// 和addEntry方法类似。主要用于克隆、反序列化等伪构造操作中,不用考虑扩容问题。
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
size++;
}
4. 扩容
// 重新计算该Map元素的哈希值并把元素存放到更大容量的数组中。当键的数量达到阀值时该方法自动调用。
// 如果当前容量已经是最大值,该方法不会增大容量,而是将阀值设置为Integer.MAX_VALUE,这样该方法就不会被后续调用。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable); // 把当前数组的映射转移到新的数组中
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
// 把所有的映射从当前数组转移到新的数组中
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null; // 原始数组的该位置赋为null
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity); // 重新计算位置
e.next = newTable[i]; // 早加入的映射放到链表的尾端
newTable[i] = e; // 新的映射加入到链表的首端
e = next; // 处理下一个映射
} while (e != null);
}
}
}
5. 删除
// 从hashmap中删除指定键的映射
// 返回被删除映射的值(null值有两种可能:没有找到指定键的映射;该映射的值为null)
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length); // 根据哈希算法取得数组中的位置
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
// 遍历(数组)当前位置的链表
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) { // 键的哈希值和键都相等
modCount++;
size--;
if (prev == e) // 链表的第一个元素(table[i])符合要求
table[i] = next; // 链表的第二个元素变成第一个
else
prev.next = next; // 把e从链表中删除,前后元素关联
e.recordRemoval(this);
return e;
}
prev = e; // 当前元素作为前一个元素
e = next; // 下一个元素作为当前元素。这两句相当于指针往后移动一位
}
return e;
}
// 处理流程和removeEntryForKey类似,虽然传入的是键值对对象,遍历还是根据键来的
final Entry<K,V> removeMapping(Object o) {
if (!(o instanceof Map.Entry))
return null;
Map.Entry<K,V> entry = (Map.Entry<K,V>) o;
Object key = entry.getKey();
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
if (e.hash == hash && e.equals(entry)) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
// 清除所有的键值对,大小变为0
public void clear() {
modCount++;
Entry[] tab = table;
for (int i = 0; i < tab.length; i++)
tab[i] = null;
size = 0;
}
分享到:
相关推荐
《HashMap 源码解析——JDK11版本》 HashMap是Java中广泛使用的非同步散列表,其设计和实现是高效且灵活的。在JDK1.8之前,HashMap的底层数据结构采用的是数组+链表的方式,这种方式称为“拉链法”来解决哈希冲突。...
Java集合系列之HashMap源码分析 Java集合系列之HashMap源码分析是Java集合系列中的一篇非常重要的文章,它详细介绍了Java集合系列之HashMap源码,具有很高的参考价值。下面我们将对HashMap源码进行详细的分析。 ...
JAVA之hashmap源码分析 Mobile-Dev-Analysis Analysis of android or java 红岩网校工作站移动开发部学员分组学习 为了让大家学的更加坚固,采取小组学习的方式帮助大家学习,同时在学习研究的过程中需要不断的做...
hashmap的原理啊思想。
微信小程序详细图文教程 泉州大白网络科技 目录 一.微信小程序申请 二....1.申请服务器 2.部署服务器 3.域名申请和配置 三....一....申请,并认证(未认证不能发布,认证需要300元,目前只支持企业认证)详细见官网说明。...
1.面试必考之HashMap源码分析与实现 伸缩性角度看HashMap的不足
JAVA之hashmap源码分析 无头Android堆分析器 “哈哈!” -纳尔逊 此存储库已弃用 创建该项目的目的是通过重新打包其他存储库中的堆转储解析器来提供堆转储解析器。 LeakCanary现在有它自己的。 该解析器在Maven ...
面试中,HashMap的源码分析与实现是一个常见的考察点,因为它涉及到数据结构、并发处理和性能优化等多个核心领域。本篇文章将深入探讨HashMap的内部工作原理、关键特性以及其在实际应用中的考量因素。 HashMap基于...
#### 二、HashMap线程安全问题分析 在多线程环境中,`HashMap`的主要线程安全问题包括但不限于: 1. **链表死循环问题**:在JDK 1.7中,当多个线程同时进行`put`操作时,可能会出现链表死循环的情况,这是一个严重...
JAVA之hashmap源码分析Superword是Java开源项目,致力于研究英语单词分析和辅助阅读,包括但不限于拼写相似度,定义相似度,发音相似度,拼写转换规则,前缀和动态前缀,后缀以及动态后缀,词根,复合词,文本辅助...
通过以上分析,我们可以看到Java 1.8 HashMap的put方法在处理哈希冲突时,不仅使用了链表,还在节点数量达到一定阈值时切换为红黑树,有效提高了数据结构的性能。同时,通过扩容机制,HashMap能够保持较低的哈希冲突...
首先在阅读HashMap源码前,我们需要知道的: 一.数组:连续的存储结构,存储相同类型的数据。对于指定下标的查找,时间复杂度为o(1);对于定值的查找,需要遍历数组,时 间复杂度为o(n),对于有序数组,则可采用二...
哈希映射(HashMap)是Java编程语言中广泛使用的数据结构之一,主要提供键值对的存储和查找功能。HashMap的实现基于哈希表的概念,它通过计算对象的哈希码来快速定位数据,从而实现了O(1)的平均时间复杂度。在深入...
在本分析中,我们将会详细探讨HashMap在不同负载因子(loadFactor)、循环次数(loop)、哈希表长度(maptablelen)和映射长度(maplen)等条件下的行为和特性。 负载因子(loadFactor)是HashMap中的一个关键参数...
深入理解Java中的HashMap源码,有助于我们更好地掌握这个常用数据结构的工作原理。HashMap是Java集合框架的重要组成部分,它实现了Map接口,提供了一种快速查找、插入和删除元素的方式。本文将详细解析HashMap的内部...
下面我们将从HashMap的优点、缺点、使用方法、源码分析等方面进行深入分析。 HashMap的优点 HashMap是一种超级快速的查询速度,时间复杂度可以达到O(1)的数据结构。同时,它也是一种动态的可变长存储数据结构,...