HashMap(1.7)源码学习
一. 1.7 和1.8区别
- 数据结构:
- 1.7: 数组 + 链表
- 1.8 : 数组 + 链表 + 红黑树
- put:
- 1.7: 头插法
- 1.8: 尾插法
- hash计算:
- 1.7 : Objects.hashCode(getKey()) ^ Objects.hashCode(getValue())
- 1.8: Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()) -- > (h = key.hashCode()) ^ (h >>> 16)
- 扩容:
- 1.7: 再次哈希
- 1.8: 分为 j 和 j + oldvalue
- 初始化
- 1.7: 默认有一个空的数组,table指向这个数组,当put时会判断是否为EMPTY_TABLE,然后进行初始化
- 1.8: put时resize,然后对oldTab.length进行判断
二.源码部分
1.基本属性
AbstractMap<K, V>
:AbstractMap 提供了 Map 的基本实现,使得我们以后要实现一个 Map 不用从头开始,只需要继承 AbstractMap, 然后按需求实现/重写对应方法即可。
Map
是Java集合框架的根接口,另一个是Collection接口
Cloneable
接口是一个标记接口,也就是没有任何内容
Serializable
接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
/**
* The default initial capacity - MUST be a power of two.
* 默认的初始容量-必须是2的幂。
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量:1,073,741,824
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
* 默认负载因子0.75
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* An empty table instance to share when the table is not inflated.
* 创建对象的时候默认table指向EMPTY_TABLE
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
/**
* The table, resized as necessary. Length MUST Always be a power of two.
* 存放键值对
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
/**
* The number of key-value mappings contained in this map.
* 实际数量
*/
transient int size;
//阈值
int threshold;
/**
* The load factor for the hash table.
* 负载因子
* @serial
*/
final float loadFactor;
transient int modCount;
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
2.构造函数
和1.8相同有4种情况
- 空参: 默认容量16,加载因子0.75
- 指定容量
- 指定容量、负载因子
- 已有集合传入
3.put
public V put(K key, V value) {
//如果表没初始化,则去初始化
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//key为空,抛出异常
if (key == null)
return putForNullKey(value);
//计算hash值
int hash = hash(key);
//找到数组下标h & (length-1)
int i = indexFor(hash, table.length);
//遍历链表
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;
//这个方法是留给LinkedHashMap实现的
e.recordAccess(this);
return oldValue;
}
}
//找不到添加新的结点
modCount++;
addEntry(hash, key, value, i);
return null;
}
4.addEntry
//put方法传入hash.key.vualue.和数组下标
void addEntry(int hash, K key, V value, int bucketIndex) {
//添加前还是判断是否需要扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//2 * table.length
resize(2 * table.length);
//重新计算数组下标
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//创建结点
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
//头插法
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
5.inflateTable()
//初始化表
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
//找到>=size的最小2的幂
int capacity = roundUpToPowerOf2(toSize);
//越界判断
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//为table新建个数组
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
6.扩容
- 和1.8不一样的一小点是,因为1.7的参数直接为新容量的大小
- 因为1.8没有默认的空表,通过capacity和threshold来区别各种情况,而1.7不需要进行初始化的操作逻辑就简单的多。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//如果原来的容量已经为最大值,则将阈值也调整为最大,return
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//创建新容量的数组
Entry[] newTable = new Entry[newCapacity];
//数据迁移
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
//重新计算阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
/**
* Transfers all entries from current table to newTable.
* 将所有表项从当前表转移到newTable。
*/
void transfer(Entry[] newTable, boolean rehash) {
//取新容量
int newCapacity = newTable.length;
//遍历表
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
//如果是true就重新计算key的hash值(?防止哈希冲突)
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//获取数组下标
int i = indexFor(e.hash, newCapacity);
//头插法
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器