HashMap内部实现机制及优化----第一篇
HashMap是链表(请参考LinkedList算法详解)和数组(ArrayList解读)组合的一个综合性的算法,理解本文前最好阅读本文的两篇
他的结构类似于:
数组保存时链表的头部,下面来一步一的详细解释HashMap主要源码:
对象:
// 默认容量(数组容量) static final int DEFAULT_INITIAL_CAPACITY = 16; //最大容量(数组容量) static final int MAXIMUM_CAPACITY = 1 << 30; //加载因子(当size>=容量*加载因子大于或等于table.length,容量扩大一倍) static final float DEFAULT_LOAD_FACTOR = 0.75f; //链表数组 transient Entry[] table; //元素个数 transient int size; //此值=容量*加载因子 int threshold;
//加载因子
final float loadFactor;
上面介绍的HashMpa的属性暂时可能有点难理解。下面介绍他们的用处:
对象初始化:
public HashMap(int initialCapacity, float loadFactor) {
//判断传入的初始化容量是否为小于0,如果是抛出异常
if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //判断传入的初始化容量是否为大于MAX,如果是就等于MAX
if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //判断传入的初始化加载因子是否符合要求。
if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 初始化容量
int capacity = 1;
//如果传入的容量小于初始化容量向左移动一位(也就是乘二)
while (capacity < initialCapacity) capacity <<= 1;
//加载因子 this.loadFactor = loadFactor; //加载因子(当size>=threshold,容量扩大一倍)
threshold = (int)(capacity * loadFactor); //初始化table
table = new Entry[capacity]; init(); }
上面介绍的对象,大家现在可以先别管(记住有这东西),对于现在有点难理解。大家可以集合源码整体的看看效果更好。
增加:
public V put(K key, V value) { //判断是否为空,如果为空就空处理。 if (key == null) return putForNullKey(value); //取处理后的hash(下面有介绍) 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;
想要理解上面的代码就必须理解hash的存储机制,下面来介绍下:
1.根据key的hash值(hashCode()),到HashMap提供的hash方法里面加工一下(这里暂时这么理解)
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); }
这是一段神奇的代码~表示看不懂,你们可以深入研究。参数h为对象hashcode()方法返回值。
2.取到加工后的hash码后然后根据indexFor()方法取到index(这里可以说是hash算法的核心,所有操作的基于它的。)
static int indexFor(int h, int length) { return h & (length-1); }
h是加工后的hash码,length是当前数组的长度,这个方法取到的长度不会超过length-1,所以index不会超过数组的长度.
前面介绍的两个方法是hash算法的核心,每个不同对象都有不同的hash码(在hashmap里面相同的对象hash码一定是相等的,而且==或则equals也返回true),而当对象通过hash算法取到的index也是一个随机的,所以存储的位置是根据hash值来决定的,在这里我们来个假设:如果没有hashmap通过hash值来取索引,你会怎样去根据k来取到value?可想而知,hash码取索引节约了很多时间。
3.取到索引后判断指定索引里面的Entry(单向链表)是否存在添加的k,也就是(e.hash == hash && ((k = e.key) == key || key.equals(k))),如果返回true就直接覆盖,返回false添加新元素,覆盖不多讲,这里讲添加。首先介绍一下hash的Entry数据结构:
static class Entry<K,V> implements Map.Entry<K,V> { 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; } .........省略 }
看了前面的LinedList详解,就能理解这种数据结构的基本操作(这里是个单向链表,也就是没有previous)相对而言操作简单些,前面介绍的hashmap对象里面就有一个table,他是Entry数组,也就是table里面保存了多条链表。整个数据结构类似于2纬表(所以也经常叫hash表)。
添加:
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); }
index和hash,k,v已经从put方法传过来了,已经知道要放在哪一条链表中了(知道table的index),每次添加就是把table[index]后移动,然后把添加的元素添加到头部,这里不具体介绍,添加后然后判断当前size是否等于或大于threshold(table.length*loadfactor),加载因子的作用在这里体现。间接性的控制链表的长度,或者是控制数组的长度。如果条件满足,数组扩大一倍,也就是调用resize(length)方法,接下来介绍resize():
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 = newTable; threshold = (int)(newCapacity * loadFactor); }
这里的方法很简单,这里调用了transfer(newTable):
void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
看resize是不是还没有添加元素到新的table中来?所以这个方法是加载table。还记得indexFor(h,length)这个方法吗?这个方法是根据数组的来确定index的。现在新数组的长度扩大了两倍所以元素的index改变了。这个方法就是重新加载新table。
基本的put操作已经介绍完。
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处理因为没有key.hashcode()会报NullPointException异常,所以要来个空处理,这里根据hash码就能取到index(因为他是根据hash值来存储的)。然后遍历指定的index处的Entry.next.next......的k是存在,如果存在返回value,不存在返回null.
基本操作也介绍完(remove....等一些方法自己可以打开源码看)。
下面给出我实现的MyHashMap:
package servlet; import java.util.AbstractMap; import java.util.Iterator; import java.util.Map; import java.util.Set; @SuppressWarnings("unchecked") public class MyHashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>,Iterable<K> { static final int MAX_CAPCITY = 1 << 30; // max size static final int DEFAULT_INITAL_CAPCITY = 16; // default size; static final float DEFAULT_LOAD_FACTOR = 0.75f; // default load factor transient int capcity;//容量 transient float factor;//加载因子 transient int threshold;//桶值 transient int size;//大小 transient Entry[] table;//hash表 public MyHashMap(int capcity, float factor) { if (capcity < 0 || capcity > MAX_CAPCITY || Float.isNaN(factor) || factor <= 0) throw new IllegalArgumentException(); this.capcity = 1; while(this.capcity < capcity){ this.capcity<<=1; } this.factor = factor; table = new Entry[this.capcity]; threshold = (int) (capcity * factor); } public MyHashMap() { this(DEFAULT_INITAL_CAPCITY, DEFAULT_LOAD_FACTOR); } /* * return size * * @see java.util.AbstractMap#size() */ public int size() { return size; } public V get(Object k) { int hash = hash(k.hashCode()); for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { if (e.h == hash && e.k == k || e.k.equals(k)) { return e.v; } } return null; } public V put(K key, V value) { int hash = hash(key.hashCode());// 计算键hash值 int index = indexFor(hash, table.length);// 计算出index // 遍历当前列是否有存在所添加的k for (Entry<K, V> column = table[index]; column != null; column = column.next) { // 如果存在覆盖 if (column.h == hash && key == column.k || key.equals(column.k)) { V oldv = column.v; column.v = value; return oldv; } } addEntry(hash, key, value, index);// 往列中添加新数据 return value; } /** * * 添加新对象 */ public void addEntry(int hash, K k, V v, int index) { table[index] = new Entry<K, V>(hash, k, v, table[index]); if (size++ >= threshold) resize(table.length * 2);// 新数组容量 } /** * 调整数组大小 * @Author : JimmyYong */ void resize(int newLength) { if (table.length == MAX_CAPCITY) {// 当达到最大容量 threshold = Integer.MAX_VALUE;// 直接int最大值 return;// 数组不变 } Entry[] entry = new Entry[newLength]; transfer(entry); table=entry; threshold = (int) (newLength * factor); } /** * 从组hash表 * @Author : JimmyYong */ void transfer(Entry[] entry) { // 从组hash表 for (int i = 0; i < table.length; i++) { for (Entry<K, V> e = table[i]; e != null; e = e.next) { // 取到索引 int index = indexFor(e.h, entry.length); e.next = entry[index]; entry[index] = e; table[i]=null; } } } @SuppressWarnings("hiding") private class Entry<K, V> { K k; V v; Entry<K, V> next; int h; public Entry(int h, K k, V v, Entry<K, V> next) { this.h = h; this.k = k; this.v = v; this.next = next; } public K getKey(){ return k; } public V getValue(){ return v; } } public Set<java.util.Map.Entry<K, V>> entrySet() { return null; } 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); } public void putAll(Map map) { int targetCapcity = map.size(); if (targetCapcity >= MAX_CAPCITY) { targetCapcity = MAX_CAPCITY; } int newLength = table.length; while (newLength < targetCapcity) { newLength <<= 1; } resize(newLength); for (int index = 0; index < table.length; index++) { for (Entry<K, V> e = table[index]; e != null; e = e.next) { } } } public V remove(Object k){ int hash=hash(k.hashCode()); int index=indexFor(hash,table.length); Entry<K,V> previous = table[index];//后一个对象 for(Entry<K,V> e = previous; e != null; e = e.next){ if(e.h == hash && e.k == k || e.k.equals(k)){ size--; //判断是否为链表的头 if(e == table[index]){ table[index] = null; return e.v; } previous.next=e.next;//移除 return e.v; } previous=e; } return null; } private abstract class MyHashIterator<E> implements Iterator<E> { int index;// 保存table数组的index Entry<K, V> current;// 当前对象 Entry<K, V> next = table[index];// 下一个对象 int length = table.length; public MyHashIterator(){ for(;next==null && index<table.length-1;next=table[++index]); } public boolean hasNext() { return next!=null; } public Entry<K, V> getEntry() { current = next; if (current != null) { next = current.next; } for (; next == null && index < length-1; next = table[++index]); return current; } public void remove() { } } private class MyKeyIterator extends MyHashIterator<K>{ public K next() { return getEntry().getKey(); } } private class MyValueIterator extends MyHashIterator<V>{ public V next() { return getEntry().getValue(); } } private class MyEntryIterator extends MyHashIterator<Entry<K,V>>{ public Entry<K,V> next() { return getEntry(); } } public Iterator<K> getMyKeyIterator(){ return new MyKeyIterator(); } public Iterator<V> getMyValueIterator(){ return new MyValueIterator(); } public Iterator<Entry<K,V>> getMyEntryIterator(){ return new MyEntryIterator(); } public Iterator<K> iterator() { return getMyKeyIterator(); } }
此类我优化了很久,还是不能超过hashmap的速度。极度郁闷!看来还要努力学习!
源码下载