HashMap源码分析(转)
一、 数据结构 Map 将实际数据存储在 Entry 类的数组中。 代码片段:
- transient Entry[] table;//HashMap 的成员变量,存放数据
- static class Entry<K,V> implements Map.Entry<K,V> {// 内部类 Entry
- final K key;
- V value;
- Entry<K,V> next; // 指向下一个数据
- final int hash;
- /**
- * Creates new entry.
- */
- Entry( int h, K k, V v, Entry<K,V> n) {
- value = v;
- next = n;
- key = k;
- hash = h;
- }
transient Entry[] table;//HashMap 的成员变量,存放数据
static class Entry<K,V> implements Map.Entry<K,V> {// 内部类 Entry
final K key;
V value;
Entry<K,V> next;// 指向下一个数据
final int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
执行下面代码后,可能的存储内部结构是 : Map map = new HashMap();
map.put("key1","value1");
map.put("key2","value2"); map.put("key3","value3");
执行
put
方法时根据
key
的
hash
值来计算放到
table
数组的下标,如果
hash
到相同的下标,则新
put
进去的元素放到
Entry
链的头部,如上图所示。
put
方法的源码后面详细解释。
static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量, 1073741824
static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默认的负载因子,如果没有通过构造方法传入负载因子,则使用 0.75 。
transient Entry[] table; // 存放具体键值对的 Entry 数组
transient int size; //HashMap 的大小
int threshold;// 阀值 threshold = (int)(capacity * loadFactor); 即容量 * 负载因子,执行 put 方法时如果 size 大于 threshold 则进行扩容,后面 put 方法将会看到
final float loadFactor; // 用户设置的负载因子
static final int DEFAULT_INITIAL_CAPACITY = 16;// 默认的初始大小,如果执行无参的构造方法,则默认初始大小为 16
static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量, 1073741824
static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默认的负载因子,如果没有通过构造方法传入负载因子,则使用 0.75 。
transient Entry[] table; // 存放具体键值对的 Entry 数组
transient int size; //HashMap 的大小
int threshold;// 阀值 threshold = (int)(capacity * loadFactor); 即容量 * 负载因子,执行 put 方法时如果 size 大于 threshold 则进行扩容,后面 put 方法将会看到
final float loadFactor; // 用户设置的负载因子
transient volatile int modCount;//HashMap 实例被改变的次数,这个同 ArrayList
构造方法一、
- public HashMap() {
- this.loadFactor = DEFAULT_LOAD_FACTOR;
- threshold = ( int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
- table = new Entry[DEFAULT_INITIAL_CAPACITY];
- init();
- }
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
使用了默认的容量和默认的负载因子。 构造方法二、
- public HashMap(int initialCapacity) {
- this(initialCapacity, DEFAULT_LOAD_FACTOR);
- }
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);
- // Find a power of 2 >= initialCapacity
- int capacity = 1;
- while (capacity < initialCapacity)
- capacity <<= 1;
- this.loadFactor = loadFactor;
- threshold = ( int)(capacity * loadFactor);
- table = new Entry[capacity];
- init();
- }
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);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
用户传入了初始容量和负载因子,这两个值是 HashMap 性能优化的关键,涉及到了 HashMap 的扩容问题。 HashMap 的容量永远是 2 的倍数,如果传入的不是 2 的倍数则被调整为大于传入值的最近的 2 的倍数,例如如果传入 130 ,则 capacity 计算后是 256 。是这段代码起的作用:
- while (capacity < initialCapacity)
- capacity <<= 1;
while (capacity < initialCapacity)
capacity <<= 1;
构造方法四、
- public HashMap(Map<? extends K, ? extends V> m) {
- this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
- DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); // 计算 Map 的大小
- putAllForCreate(m); // 初始化
- }
- private void putAllForCreate(Map<? extends K, ? extends V> m) {
- for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {// 通过 entryset 进行遍历
- Map.Entry<? extends K, ? extends V> e = i.next();
- putForCreate(e.getKey(), e.getValue());
- }
- }
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);// 计算 Map 的大小
putAllForCreate(m);// 初始化
}
private void putAllForCreate(Map<? extends K, ? extends V> m) {
for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {// 通过 entryset 进行遍历
Map.Entry<? extends K, ? extends V> e = i.next();
putForCreate(e.getKey(), e.getValue());
}
}
根据传入的
map
进行初始化。
三、关键方法 1 ) put
- public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value);// 单独处理,总是放到 table[0] 中
- int hash = hash(key.hashCode());// 计算 key 的 hash 值,后面介绍性能的时候再说这个 hash 方法。
- int i = indexFor(hash, table.length);// 将 hash 和 length-1 取 & 来得到数组的下表
- 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);
- return oldValue;
- }
- } // 如果这个 key 值,原来已经则替换后直接返回。
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
- 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);// 如果 table[bucketIndex] 中已经存在 Entry 则放到头部。
- if (size++ >= threshold)// 如果大于了阀值,则扩容到原来大小的 2 倍。
- resize( 2 * table.length);
- }
- 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 中,注意转移后会重新 hash ,所以位置可能会跟之前不同,目的是均匀分不到新的 table 中。
- table = newTable;
- threshold = ( int)(newCapacity * loadFactor);
- }
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);// 单独处理,总是放到 table[0] 中
int hash = hash(key.hashCode());// 计算 key 的 hash 值,后面介绍性能的时候再说这个 hash 方法。
int i = indexFor(hash, table.length);// 将 hash 和 length-1 取 & 来得到数组的下表
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);
return oldValue;
}
}// 如果这个 key 值,原来已经则替换后直接返回。
modCount++;
addEntry(hash, key, value, i);
return null;
}
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);// 如果 table[bucketIndex] 中已经存在 Entry 则放到头部。
if (size++ >= threshold)// 如果大于了阀值,则扩容到原来大小的 2 倍。
resize(2 * table.length);
}
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 中,注意转移后会重新 hash ,所以位置可能会跟之前不同,目的是均匀分不到新的 table 中。
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
2 ) get 方法
- public V get(Object key) {
- if (key == null)
- return getForNullKey();
- int hash = 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.equals(k)))
- return e.value;// 找到则返回
- }
- return null;// 否则,返回 null
- }
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = 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.equals(k)))
return e.value;// 找到则返回
}
return null;// 否则,返回 null
}
3)remove 方法
- 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 链未遍历完则一直遍历
- 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] 执行 e.next
- table[i] = next;
- else // 否则将前一个的 next 指向 e.next
- prev.next = next;
- e.recordRemoval( this);
- return e;
- }
- prev = e; // 未找到则继续往后遍历
- e = next;
- }
- return e;
- }
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 链未遍历完则一直遍历
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] 执行 e.next
table[i] = next;
else // 否则将前一个的 next 指向 e.next
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;// 未找到则继续往后遍历
e = next;
}
return e;
}
4)HashMap 的遍历方法
- Map map = new HashMap();
- map.put( "key1","value1");
- map.put( "key2","value2");
- map.put( "key3", "value3");
- for(Iterator it = map.entrySet().iterator();it.hasNext();){
- Map.Entry e = (Map.Entry)it.next();
- System.out.println(e.getKey() + "=" + e.getValue());
- }
- System.out.println( "-----------------------------------------");
- for(Iterator it = map.keySet().iterator();it.hasNext();){
- Object key = it.next();
- Object value = map.get(key);
- System.out.println(key+ "="+value);
- }
- System.out.println( "-----------------------------------------");
- System.out.println(map.values());
- 输出为:
- key3=value3
- key2=value2
- key1=value1
- -----------------------------------------
- key3=value3
- key2=value2
- key1=value1
- -----------------------------------------
- [value3, value2, value1]
Map map = new HashMap();
map.put("key1","value1");
map.put("key2","value2");
map.put("key3", "value3");
for(Iterator it = map.entrySet().iterator();it.hasNext();){
Map.Entry e = (Map.Entry)it.next();
System.out.println(e.getKey() + "=" + e.getValue());
}
System.out.println("-----------------------------------------");
for(Iterator it = map.keySet().iterator();it.hasNext();){
Object key = it.next();
Object value = map.get(key);
System.out.println(key+"="+value);
}
System.out.println("-----------------------------------------");
System.out.println(map.values());
输出为:
key3=value3
key2=value2
key1=value1
-----------------------------------------
key3=value3
key2=value2
key1=value1
-----------------------------------------
[value3, value2, value1]
四、性能相关 1 ) hash 如果总计算到相同的数组下标,则得进行 Entry 的遍历来取值和存放值,必然会影响性能。 所以 HashMap 提供了 hash 方法,来解决 key 的 hashCode 方法质量不高问题。
- public V put(K key, V value) {
- ...
- int hash = hash(key.hashCode());
- ...
- }
- static int hash(int h) {
- // This function ensures that hashCodes that differ only by
- // constant multiples at each bit position have a bounded
- // number of collisions (approximately 8 at default load factor).
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
- }
public V put(K key, V value) {
...
int hash = hash(key.hashCode());
...
}
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
2 )初始容量和负载因子 因为 put 的时候可能需要做扩容,扩容会导致性能损耗,所以如果可以预知 Map 大小的话,可以设置合理的初始大小和负载因子来避免 HashMap 的频繁扩容导致的性能消耗。
- void addEntry(int hash, K key, V value, int bucketIndex) {
- ntry<K,V> e = table[bucketIndex];
- table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
- if (size++ >= threshold)
- resize( 2 * table.length);
- }
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);
}
五、同 Hashtable 的区别 1 ) HashMap 允许 key 和 value 都可以为 null,Hashtable 都不可以为空。 HashMap 的 put 方法,代码片段:
- if (key == null)
- return putForNullKey(value);
if (key == null)
return putForNullKey(value);
Hashtable 的 put 方法,代码片段:
- if (value == null) {
- throw new NullPointerException();
- }
- Entry tab[] = table;
- int hash = key.hashCode();
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
int hash = key.hashCode();