jdk7 HashMap源码分析
最近研究了HashMap的源码,在此做个笔记。jdk7和jdk8的HashMap源码有很大的不同,以下是jdk7版本的HashMap。如有错漏,请不吝指正。
HashMap属性
/**
* The default initial capacity - MUST be a power of two.
* 默认初始容量,必须是2的幂
*/
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.
* 最大容量,如果两个带参数的构造函数隐式指定了较大的值,则使用。
* 必须是2的幂 且小于等于 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;
/**
* An empty table instance to share when the table is not inflated.
* 当表没有膨胀时,要共享的空表实例。
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
/**
* The table, resized as necessary. Length MUST Always be a power of two.
* 根据需要调整表的大小。长度必须总是2的幂。
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
/**
* The number of key-value mappings contained in this map.
* 这个map中包含的键-值映射的数目。
*/
transient int size;
/**
* The next size value at which to resize (capacity * load factor).
* 调整大小的下一个大小值(容量*装载系数)。
* @serial
*/
// If table == EMPTY_TABLE then this is the initial capacity at which the
// table will be created when inflated.
// 如果table == EMPTY_TABLE,那么这是膨胀时创建该表的初始容量。
int threshold;
/**
* The load factor for the hash table.
* 哈希表的装载因子。
* @serial
*/
final float loadFactor;
/**
* 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).
* 这个HashMap被结构修改的次数
* 结构修改是指改变HashMap中映射的数量或修改其内部结构(例如,重新哈希)。
* 此字段用于使HashMap的集合视图上的迭代器快速失效。
* (见ConcurrentModificationException)。
*/
transient int modCount;
/**
* The default threshold of map capacity above which alternative hashing is
* used for String keys. Alternative hashing reduces the incidence of
* collisions due to weak hash code calculation for String keys.
* <p/>
* This value may be overridden by defining the system property
* {@code jdk.map.althashing.threshold}. A property value of {@code 1}
* forces alternative hashing to be used at all times whereas
* {@code -1} value ensures that alternative hashing is never used.
* 映射容量的默认阈值,在该阈值之上,可选散列用于字符串键。
* 可选哈希减少了由于字符串键的弱哈希码计算而产生的冲突。
* 这个值可以通过定义系统属性{@code jdk.map.althashing.threshold}来覆盖。
* 属性值{@code 1}强制在任何时候使用可选散列,
* 而{@code -1}值确保永远不会使用可选散列。
*/
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
hashmap 构造函数
/**
* 以下是构造方法中可能会使用到的属性
*
* 默认初始容量,须是2的幂,默认16
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
/**
* 在构造函数中未指定时使用的加载因子。
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 最大容量,默认2的30次方
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 下一次表扩容时的容量大小
* 如果table == EMPTY_TABLE,则这是膨胀时将在其中创建表的初始容量。
*/
int threshold;
public HashMap() {
//调用构造方法,传递初始容量和加载因子
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//主要是按hashmap的初始容量和加载因子创建hashmap对象
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)//初始容量小于0,报异常
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//初始容量大于允许的最大容量,则将初始容量赋值为允许的最大容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//加载因子小于0或是非数字值,报异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//把这个加载因子参数赋值给hashmap对象的loadFactor
this.loadFactor = loadFactor;
//把初始化容量赋值给threshold
threshold = initialCapacity;
init();
}
//子类的初始化钩子,父类是个空方法,在子类中具体实现
void init() {
}
put:添加或修改数据
以下是进行put时会调用的方法
put()
/**
* 根据需要调整表的大小。长度必须总是2的幂,表没有膨胀时是一个空表实例
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
/**
* 当表没有膨胀时,要共享的空表实例。
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
/**
* 需要调整大小的下一个大小值(容量*装载系数),即阈值.
* 如果table == EMPTY_TABLE,那么这是膨胀时创建该表的初始容量。
*/
int threshold;
//添加一个键值对到hashmap对象中,方法返回的是久值
//添加成功返回null
//修改成功返回久值
public V put(K key, V value) {
//先判断表是否为空表,
//如果为空表,则进行表膨胀,初始容量为threshold,在构造方法中初始化,默认为16
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//再判断key是否为空
//为空则添加key=null的entry数据
//遍历table[0]上的链表,找到key=null的结点,找到则更新值,
//找不到则新建key为null的结点,添加在table[0]的位置
if (key == null)
return putForNullKey(value);
int hash = hash(key);//根据key计算出更散列的hash
int i = indexFor(hash, table.length);//根据hash算出下标
//key不为空,遍历对应下标位置下的元素,找到则给原来的entry赋新的value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//判断这个entry的hash,和key是否相同,都相同则给原来的entry赋新的value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);//这个方法与hashmap没有关系,在hashmap中它是一个空方法
return oldValue;
}
}
modCount++;//修改次数+1
//找不到,则新添加entry,把entry的hash,key,value以及数组下标都传过去
addEntry(hash, key, value, i);
return null;
}
putForNullKey()
/**
* Offloaded version of put for null keys
*/
private V putForNullKey(V value) {
//遍历table[0],找到null key的结点,修改值
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);//在hashmap中这个方法为空
return oldValue;//返回旧值
}
}
//modCount代表的是修改次数
modCount++;
//找不到则在table[0]添加新的结点
addEntry(0, null, value, 0);
return null;
}
addEntry():添加新的结点
/**
*在指定的bucket中添加一个带有指定键、值和散列码的新条目(entry)。此方法负责在适当的情况下调整表的大小。
*子类覆盖这个来改变put方法的行为。
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
//先判断hashmap的大小是否大于等于阈值,且key对应的位置不为空才会进行扩容
//条件都符合,则先进行扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//扩容成原来的2倍
resize(2 * table.length);
//重新计算hash
hash = (null != key) ? hash(key) : 0;
//重新计算结点的位置
bucketIndex = indexFor(hash, table.length);
}
//最终新建并添加entry对象
createEntry(hash, key, value, bucketIndex);
}
transfer():扩容时的数据转移
/**
* Transfers all entries from current table to 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;
//rehash一般为false
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;
}
}
}
inflateTable()
/**
* Inflates the table.
* toSize 下一次表扩容时表需要达到的容量大小
*/
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
// 找到一个最小2次幂,这个幂的值大于等于toSize
int capacity = roundUpToPowerOf2(toSize);
//计算下一次表扩容时的阈值,即表需要达到的容量大小=扩容后表容量*加载因子
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//新建Entry对象数组,该数组即存储数据的表,其容量为capacity
table = new Entry[capacity];
//hashseed的作用是用来生成entry的hash值的,hashseed默认为0;
initHashSeedAsNeeded(capacity);
}
roundUpToPowerOf2()
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";java
//如果下一次表扩容时的容量大小大于最大容量,则返回最大容量
//否则返回最小2次幂,这个幂的值大于等于number
//Integer.highestOneBit():传入一个int参数i,返回其二进制最高位1的权值
//(number - 1) << 1:使(nember-1)右移一位
//number-1:
//当number不是2次幂时,如当number=3,即二进制0011时,Integer.highestOneBit((3-1) << 1)结果为4;
//当number是2次幂时,如当number=2,即二进制0010时,Integer.highestOneBit((2-1) << 1)结果为2
//能够确保无论是什么数,返回的都是最小2次幂且这个幂的值大于等于number
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
initHashSeedAsNeeded()
/**
与此实例相关联的随机值,应用于键的哈希码,使哈希碰撞更难找到。如果为0,则禁用可选散列。
hashseed可以使hsahmap的数组更加散列
HashMap提供了一个功能,可以在启动虚拟机的时候在VM Options中设置jdk.map.althashing.threshold的值,当容量大于这个设置的值的时候,hashseed的值会被修改
*/
transient int hashSeed = 0;
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
/**
* holds values which can't be initialized until after VM is booted.
* 保存在VM启动后才能初始化的值。
*/
private static class Holder {
/**
* Table capacity above which to switch to use alternative hashing.
*/
static final int ALTERNATIVE_HASHING_THRESHOLD;
static {
//获取VM启动时设置的jdk.map.althashing.threshold 参数的值
//设置该虚拟机参数,如:-Djdk.map.althashing.threshold=3
String altThreshold = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
"jdk.map.althashing.threshold"));
int threshold;
try {
threshold = (null != altThreshold)
? Integer.parseInt(altThreshold)
: ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;
// disable alternative hashing if -1
if (threshold == -1) {
threshold = Integer.MAX_VALUE;
}
if (threshold < 0) {
throw new IllegalArgumentException("value must be positive integer.");
}
} catch(IllegalArgumentException failed) {
throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
}
ALTERNATIVE_HASHING_THRESHOLD = threshold;
}
}
hash()
/**
检索对象哈希代码,并对结果哈希应用一个补充哈希函数,这可以防止质量较差的哈希函数。这一点非常关键,因为HashMap使用的是2次幂长度的哈希表,否则这些哈希表就会在较低位上没有差异的哈希码之间发生冲突。注意:空键总是映射到散列0,因此索引为0。
*/
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
//此函数确保在每个位位置上只有常数倍差异的哈希码具有有限的碰撞次数(默认负载系数大约为8)。
h ^= (h >>> 20) ^ (h >>> 12);java
return h ^ (h >>> 7) ^ (h >>> 4);
}
indexFor():计算hashmap表的下标
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
//通过与的方式,计算下标
return h & (length-1);
}
resize()
//扩容
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//如果原来的容量已经最大了
if (oldCapacity == MAXIMUM_CAPACITY) {
//把阈值调到Integer的最大值
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
//转移数据到新数组
//initHashSeedAsNeeded(newCapacity):初始化哈希掩码值。我们推迟初始化,直到我们真正需要它。
transfer(newTable, initHashSeedAsNeeded(newCapacity));
//hashmap的hash表指向新数组的地址
table = newTable;
//修改阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
createEntry()
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
//在对应位置添加结点
//1、新建entry对象,并给该对象的hash, key, value,e属性赋值
//2、让该对象的next指向hashmap的table所指向的entry对象
//3、让hashmap的table指向新建的entry对象
table[bucketIndex] = new Entry<>(hash, key, value, e);
//hashmap大小加1
size++;
}
Entry类
//HashMap的内部类
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
/**
* Creates new entry.
*/
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 final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
get:获取数据
以下是进行get时会调用的一些方法
get()
public V get(Object key) {
//先判断是否为空,为空则通过getForNullKey()来获取value
if (key == null)
return getForNullKey();
//否则通过getEntry(key)获取Value
Entry<K,V> entry = getEntry(key);
//如果找不到返回null,找到返回该key对应entry的value
return null == entry ? null : entry.getValue();
}
getForNullKey()
private V getForNullKey() {
//hashmap没有任何数据返回null
if (size == 0) {
return null;
}
//遍历table[0],找到key为null的entry,并返回该entry的value
//table[0]:key为null的entry只存放在table[0]处
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
//找不到返回null
return null;
}
getEntry()
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//先计算entry的hash值
int hash = (key == null) ? 0 : hash(key);
//根据entry的hash值和长度计算entry所在的下标,找到该下标中存储的entry
//开始遍历该entry对应的链表,找到hash值和key都相等的entry对象并返回该对象
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;
}
remove:删除数据
以下是进行remove时会调用的一些方法
remove()
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
removeEntryForKey()
final Entry<K,V> removeEntryForKey(Object key) {
//如果hsahmap大小为零直接返回null
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key); //计算key对应entry的hash
int i = indexFor(hash, table.length);//计算下标
Entry<K,V> prev = table[i];//获取该下标对应链表的第一个(entry)结点
Entry<K,V> e = prev;
//从第一个结点开始遍历该entry对象对应的链表
while (e != null) {
Entry<K,V> next = e.next;//先找到正在遍历的entry对象的下一个对象
Object k;
//判断entry对象的hash和key属性是否等于要查询的hash和key
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
//修改次数加1
modCount++;
//hashmap大小减一
size--;
//如果要删除的是第一个结点,则直接让hashmap的table对应下标指向第一个结点的下一个结点
if (prev == e)
table[i] = next;
//如果不是第一结点,则把要删除的结点的前一个结点的next指向要删除的结点的下一个结点
else
prev.next = next;
e.recordRemoval(this);//在hashmap中为空方法
return e;//返回要删除的entry
}
//为遍历下一个结点做准备
prev = e;
e = next;
}
return e;
}
关于modCount的作用
modCount示例代码
//modCount与一个异常有关
public static void main(string[] args) {
HashMap<String, String> hashMap = new HashMap<>();//对象.modCount
//添加顺序不代表迭代器遍历顺序
hashMap.put("1","1");
hashMap.put("2","2");
/**以下for循环代码会报异常
for (String key: hashMap.keySet()){
if (key. equals("2")){
hashMap.remove(key);
}
}S
for增强的遍历,其实相当于一个语法糖,反编译后的代码其实是以下的通过迭代器遍历的方式,因此研究下面的代码就可以了
*/
//在遍历hashMap的时候,使用hashSMap的删除操作会报java.util.concurrentModificationException异常
//1、在hashMap.keySet().iterator()中获取的迭代器的expectedModCount属性会赋值为modCount
Iterator i$ = hashMap.keySet().iterator();
//2、在i$.hasNext()中会判断modCount与expectedModCount是否相等,不相等会报异常
while(i$.hasNext()){
String key = (String)i$.next();//
if (key. equals("2")){
//3、在hashMap.remove(key)删除成功后,会modCount++,导致modCount与expectedModCount不相等,下次调用迭代器的hasNext()方法会报java.util.concurrentModificationException异常
hashMap.remove(key);
}
}
}
keySet()
//通过new KeySet()获取一个 KeySet对象
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
KeySet对象
//keySet()返回的是一个KeySet对象
private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
newKeyIterator()
// Subclass overrides these to alter behavior of views' iterator() method
Iterator<K> newKeyIterator() {
return new KeyIterator();
}
KeyIterator类继承了 HashIterator类
private final class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
HashIterator类
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry<K,V> current; // current entry
HashIterator() {
//在新建HashIterator的时候,会把modCount赋值给expectedModCount属性
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
final Entry<K,V> nextEntry() {
//在查找下一个Entry的时候会判断modCount是否等于expectedModCount,由于删除操作会改变modCount的值,所以在迭代过程中,使用hashmap的remove()方法会报错
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}