前言:算法
平時工做的時候,用的最多的就是ArrayList和HashMap了,今天看了遍HashMap的源碼,決定本身手寫一遍HashMap。數組
1、建立MyHashMap接口ide
咱們首先建立一個MyHashMap的入口,暴露一個外部調用的接口,裏面簡單的定義一下put和get。函數
public interface MyHashMap<K,V> { public V put(K k,V v); public V get(K k); interface Entry<K,V>{ public K getKey(); public V getValue(); } }
2、建一個實現類MyHashMapImpl性能
接口定義完成以後,那就要開始實現了,咱們首先建立一個類MyHashMapImpl來實現MyHashMap。而後咱們定義一些變量。以及構造函數,好比咱們定義的數組初始長度爲16,加載因子爲0.75。這兩個參數會涉及到自動擴容,咱們後面再說。this
public class MyHashMapImpl<K, V> implements MyHashMap<K, V> {
//數組的初始長度
private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//閥值比例(加載因子)
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private int defaultInitSize;
private final float defaultLoadFactor;
//Map當中entry的數量
private int entryUseSize;
//數組
private Entry<K, V>[] table;
//構造函數
public MyHashMapImpl() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public MyHashMapImpl(int defaultInitialCapacity, float defaultLoadFactor) {
if (defaultInitialCapacity < 0)
//容量不合規
throw new IllegalArgumentException("Illegal initial capacity" + defaultInitialCapacity);
if (defaultLoadFactor <= 0 || Float.isNaN(defaultLoadFactor))
//不合規的加載因子
throw new IllegalArgumentException("Illegal load factor" + defaultLoadFactor);
this.defaultInitSize = defaultInitialCapacity;
this.defaultLoadFactor = defaultLoadFactor;
table = new Entry[this.defaultInitSize];
}
}
3、重寫put方法spa
咱們首先重寫下put方法,能夠看到,當Map中存儲的數據大於加載因子*初始化數據長度的時候,會第一時間觸發擴容機制,擴容的過程也就是從新設置一個更大的數組,並把本來的數組地址指過去,而且把本來的值從新put進去。這個過程若是頻繁發生仍是很消耗機器性能的,因此咱們在寫代碼的時候最好是預估好初始大小,儘可能不觸發擴容機制。code
@Override public V put(K k, V v) { V oldValue; //是否須要擴容 //擴容完畢,確定須要從新散列 if (entryUseSize >= defaultInitSize * defaultLoadFactor) { resize(2 * defaultInitSize); } int index = hash(k) & (defaultInitSize - 1); if (table[index] == null) { table[index] = new Entry<K, V>(k, v, null); ++entryUseSize; } else { Entry<K, V> entry = table[index]; Entry<K, V> e = entry; while (e != null) { if (k == e.getKey() || k.equals(e.getKey())) { oldValue = e.value; e.value = v; return oldValue; } e = e.next; } table[index] = new Entry<K, V>(k, v, entry); ++entryUseSize; } return null; } private void resize(int i) { Entry[] newTable = new Entry[i]; defaultInitSize = i; entryUseSize = 0; rehash(newTable); } private void rehash(Entry<K, V>[] newTable) { //獲得原來老得entry集合,注意遍歷單鏈表 List<Entry<K, V>> entryList = new ArrayList<Entry<K, V>>(); for (Entry<K, V> entry : table) { if (entry != null) { do { entryList.add(entry); entry = entry.next; } while (entry != null); } } //覆蓋舊的引用 if (newTable.length > 0) { table = newTable; } //從新hash也就是從新put entry到hashmap for (Entry<K, V> entry : entryList) { put(entry.getKey(), entry.getValue()); } } class Entry<K, V> implements MyHashMap.Entry<K, V> { private K key; private V value; private Entry<K, V> next; public Entry() { } public Entry(K key, V value, Entry<K, V> next) { this.key = key; this.value = value; this.next = next; } @Override public K getKey() { return key; } @Override public V getValue() { return value; } }
4、重寫get方法blog
若是要拿到數組中的值,咱們首先要獲取對應的位置。其中有一個基本概念要說一下,每個數據經過hash函數都會獲得一個值,而且這個值是固定的,因此咱們能夠經過k.hashCode()接口
來獲取對應的hash值,而後按照散列算法均勻分散hash值,而後經過hashcode獲取對應的值,獲得基本數組的下標。這時候就能拿到咱們存在map中的值了,可是hash值並非必定是惟一的,也就是說能夠能a.hash和b.hash值是同樣的,可是a不等於b,因此若是兩個數據hash值相同,會觸發hash衝突。嚴重下降hashmap的性能,本次hash方法的做用也就是儘可能減小hash衝突。使數據排列的更加均勻一些。當咱們遇到hash衝突的時候能夠再次hash解決衝突。
@Override public V get(K k) { int index = hash(k) & (defaultInitSize - 1); if (table[index] == null) { return null; } else { Entry<K, V> entry = table[index]; do { if (k == entry.getKey() || k.equals(entry.getKey())) { return entry.value; } entry = entry.next; } while (entry != null); } return null; }