【学习笔记-集合】HashMap 源码浅析
/**
* HashMap主要方法解析,jdk1.7版本的HashMap
* HashMap数据是通过数组和链表结合的方式(链表散列)存储。
* 在put时候根据key值得到hash值(地址)即数组下标,之后如果得到相同下标则放在链表前面,之前的数据在链表尾部。
* 之前的数据在链表尾部。
* 在查找数据时候根据hashcode获取数组下标,在链表中使用equals根据key查找value.
* 一、构造
* 4个构造相对之前的jdk版本功能基本不变,但是代码封装更完善。
* 构造前一个参数是容量,相当于数组大小,后一个是负载因子
*/
public HashMap(int initialCapacity, float loadFactor) {
//当初始容量<0,抛出异常非法的参数容量
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//初始容量不能大于最大容量值,最大容量值为MAXIMUM_CAPACITY = 1 << 30;
//左移一位相当于乘以2,所以左移30位相当于2^30.
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//负载因子不为空并且<=0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//保存参数并且初始化数组
this.loadFactor = loadFactor;
threshold = initialCapacity;
//此初始化将插入数据,主要使用Entry
init();
}
//无参构造默认容量是16,负载因子0.75
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//指定容量参数,默认负载因子0.75
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/构造与指定map相同映射的新HashMap
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
//如果容量不够,扩大table数组
inflateTable(threshold);
//将map中的元素添加到HashMap中
putAllForCreate(m);
}
/**
* 二、创建数据链
* 静态内部类,包含键值,节点next和hash值,由于他的存在,才会让table数组项以链表方式存在
*/
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
//添加新条目
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
。
。
。
。
。
/**
* 三、存
* 实现快速存取
* 添加数据
*/
public V put(K key, V value) {
//如果数组为空,添加数组容量
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key为空,保存null在table的第一个位置,所以HashMap可以为null
if (key == null)
return putForNullKey(value);
//计算hash值
int hash = hash(key);
//计算key的hash值在table中的位置(索引)
int i = indexFor(hash, table.length);
//从i迭代e,找到key保存的位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//判断该链上是否有hash(key)值相同的情况,若存在,则将其value值覆盖,保留新value
//新值等于旧值,返回旧值
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//修改次数加一
modCount++;
//将key,value值添加在i处
addEntry(hash, key, value, i);
return null;
}
//都是添加Entry,这个是HashMap实际容量超过容器容量,下面的方法是没超过的情况
void addEntry(int hash, K key, V value, int bucketIndex) {
//当HashMap大小不小于临界值(容量*负载因子)大小,并且数组bucketIndex位置不为null,改变HashMap大小,记录索引
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//否则调用createEntry方法
createEntry(hash, key, value, bucketIndex);
}
//HashMap实际容量未超过默认容量或者初始化容量
void createEntry(int hash, K key, V value, int bucketIndex) {
//保存bucketIndex的所在位置到e中
Entry<K,V> e = table[bucketIndex];
//设置bucketIndex位置元素为新Entry,并且设置e为新Entry下一个节点
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
/**
* 四、取
* 实现快速存取
* 获取数据
*/
public V get(Object key) {
//若key为null,调用getForNullKey取出value
if (key == null)
return getForNullKey();
//根据key值算出hash值并取出table对应索引处的值
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//计算hash值
int hash = (key == null) ? 0 : hash(key);
//根据hash值取出table对应索引处的值
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;
}
业精于勤,荒于嬉;行成于思,毁于随;