在数据结构中,数组列表插入删除不方便,链表查找效率不高。而HashMap拥有了这两者的优点。HashMap本质上是由数组+链表组成的结构。
源码分析
注意:此部分参考自[https://dzone.com/articles/hashmap-internal]
java.util.HashMap.java
/**
* 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.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
这是说hashmap所能拓展的最大值为 2^30=1,073,741,824
java.util.HashMap.java
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
这是说数组默认的大小是16(数组的大小总是以2为底的幂,所以很容易理解为什么其扩容会如此快,数学中称为指数爆炸
),另一个常量DEFAULT_LOAD_FACTOR
代表负载系数,即当HashMap的规模达到其现有规模的75%,就会扩容两倍。
java.util.HashMap.java
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
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;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
put方法主要有几个过程:
1)使用用户自宝义的hashCode()作为hash()方法的参数重新生成hashcode
2)使用重新生成的hashcode和数组的长度计算数组下标索引
3)如果key存在,则覆盖元素,否则创建一个新的实体(Entry)放在上面计算位置(index)中
java.util.HashMap.java
/**
* Applies a supplemental hash function to a given hashCode, which
* defends against poor quality hash functions. This is critical
* because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
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);
}
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
indexFor方法中的h是hashCode,length默认是数组容量,即分桶的数量。
h&(length-1)
这里的&运算
相当于取模运算,但是&
比%
效率更高。
hashMap实现自己的哈希函数来弥补对象自身质量不佳的哈希函数。因为容量总是2的N次方法,使用二次hash函数使高位也参与运算,防止低位不变造成严重哈希冲突。简而言之,目的是为了使key分布得更均匀,提高操作效率。
java.util.HashMap.java
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
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;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
put和get计算哈希值分桶的算法是相同的。这里值得关注的是,当两个key有相同的hash值时,如何将新值传进去?
1)通过计算所得hashCode找到数组元素,即分桶(bucket),每个桶都是一个链表结果,遍历链表寻找具有相同的key的节点,如果找到覆盖其value
2)如果找不到,使用要插入的key和value新建一个节点,next指向原来头节点,将桶的头节点重新赋值为新的节点。
实现自己的HashMap:
这里的实现比较粗糙,只为了说明原理,更优的实现,请阅读Java官方源码。
package collections;
public class MyHashMap<K, V> {
static final int DEFALUT_INITAL_CAPACITY = 16;
private Entry<K, V>[] table; // Entry数组
private int size;
public MyHashMap() {
table = new Entry[DEFALUT_INITAL_CAPACITY];
}
public int size() {
return size;
}
public V put(K key, V value) {
if (key == null)
return null;
int hash = key.hashCode();
int index = indexFor(hash, table.length);
// 如果key已经存在,修改value
for (Entry<K, V> e = table[index]; e != null;) {
if (e.getKey().equals(key)) {
V oldValue = e.getValue();
e.setValue(value);
return oldValue;
}
e = e.next;
}
Entry<K, V> e = table[index];
// 如果key不存在,则新建一个Entry
table[index] = new Entry<K, V>(key, value, e);
size++;
return null;
}
public V get(K key) {
if (key == null)
return null;
int hashCode = key.hashCode();
int index = indexFor(hashCode, table.length);
for (Entry<K, V> head = table[index]; head != null; head = head.next) {
if (head.key.hashCode() == hashCode && head.key.equals(key)) { // 先比较hashCode为了加法比较效率
return head.getValue();
}
}
return null;
}
// 根据hashcode求数组的位置
static int indexFor(int h, int length) {
return h % (length - 1);
}
private final class Entry<K, V> {
final K key;
V value;
Entry<K, V> next;
public Entry(K k, V v, Entry n) {
this.key = k;
this.value = v;
this.next = n;
}
public final V getValue() {
return value;
}
public final void setValue(V value) {
this.value = value;
}
public final Entry<K, V> getNext() {
return next;
}
public final void setNext(Entry<K, V> next) {
this.next = next;
}
public final K getKey() {
return key;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry e1 = (Entry) o;
if (e1.getKey() == getKey() || (e1.getKey() != null) && e1.getKey().equals(e1.getKey())) {
Object v1 = e1.getValue();
if (v1 == value || (v1 != null && v1.equals(value)))
return true;
}
return false;
}
@Override
public final int hashCode() {
return (key == null ? 0 : key.hashCode() ^ (value == null ? 0 : value.hashCode()));
}
@Override
public final String toString() {
StringBuilder sb = new StringBuilder("[");
sb.append(key);
sb.append("=");
sb.append(value);
sb.append("]");
return sb.toString();
}
}
public static void main(String[] args) {
MyHashMap<String, Object> map = new MyHashMap<>();
map.put("name", "cgz");
map.put("age", 25);
System.out.println(map.get("name"));
System.out.println(map.get("age"));
System.out.println(map.size());
}
}