HaspMap的原理

實現簡單的Map


前幾天有想法弄懂HashMap的實現的原理,我自己也YY了一個想法去實現一個簡單的Map, 代碼如下:

public class KeyValuePair<K,V> {
	
	public  K Key;
	public  V Value;
	
	public K getKey() {
		return Key;
	}
	
	public void setKey(K key) {
		Key = key;
	}
	
	public V getValue() {
		return Value;
	}
	
	public void setValue(V value) {
		Value = value;
	}
}

然後使用List作爲Container對數據進行存儲,主體的內部實現原理如下:

public class MyMap<K, V> {
	private List<KeyValuePair<K, V>> map;
	
	public MyMap() {
		map = new ArrayList<KeyValuePair<K, V>>();
	}
	
	public V put(K k, V v) {
		KeyValuePair<K, V> keyValuePair = new KeyValuePair<K, V>();
		keyValuePair.setKey(k);
		keyValuePair.setValue(v);
		map.add(keyValuePair);
		return v;
	}
	
	public V get(K k) {
		for (KeyValuePair pair : map) {
			if (pair.getKey().equals(k)) {
				return (V) pair.getValue();
			}
		}
		return null;
	}
}

雖然也能實現類似的效果,但我們可以看到這個的map的時間複雜度是O(n),當集合數量很大時,則效率可以的非常的糟糕,下面做一個對比的測試:

@Test
public void MapTest(){
	
	long start=System.currentTimeMillis();
	MyMap<String,String> map =new MyMap();
	for (int i=0;i<10000;i++){
		map.put("Key"+i,"value"+i);
	}
	for (int i=0;i<10000;i++){
		map.get("Key"+i);
	}
	long end=System.currentTimeMillis();
	System.out.println("耗時:"+(end-start));
	
	 start=System.currentTimeMillis();
	Map<String,String> hashMap =new HashMap<>();
	for (int i=0;i<10000;i++){
		hashMap.put("Key"+i,"value"+i);
	}
	for (int i=0;i<10000;i++){
		hashMap.get("Key"+i);
	}
	end=System.currentTimeMillis();
	System.out.println("耗時:"+(end-start));
}

運行結果如下:

耗時:1815
耗時:14

整整慢了100多倍!

HashMap的實現原理


對於上面的代碼,我們應該知道性能最慢的是查找對應的key值,對於ArrayList來說,可能插入也是很大的性能消耗。在JDK中使用一個數組來存儲key,索引是根據Key的Hash值來確定,而每一個key對應數據單元是一個鏈表。用圖表示效果如下:

下面我們JDK的原理進行分析:

存值

  1. 首先定義一個數組,其類型是一個Key-Value類型
  2. 根據key的Hash值來確定當前的索引
  3. 根據索引值來判斷當前是否有值,如果當前有值則把當前的值插入當前數據之前

取值

1.根據key的Hash值來確定當前的索引,根據索引來找到鏈表的首節點

2.遍歷鏈表,找到指定的Key對應的節點,取出當前值

具體的實現代碼如下(可以利用上面的代碼):

public class KeyValuePair<K,V> {
	
	public  K Key;
	public  V Value;
	public KeyValuePair next;
	
	public KeyValuePair getNext() {
		return next;
	}
	
	public void setNext(KeyValuePair next) {
		this.next = next;
	}
	public KeyValuePair(){
	
	}
	public KeyValuePair(K k, V v){
		this.Key=k;
		this.Value=v;
	}
	public K getKey() {
		return Key;
	}
	
	public void setKey(K key) {
		Key = key;
	}
	
	public V getValue() {
		return Value;
	}
	
	public void setValue(V value) {
		Value = value;
	}
}

HashMap的實現:

public class MyHashMap<K, V> {
	
	private  int defalutLength = 16;
	private int size;
	private KeyValuePair<K, V>[] arr;
	public MyHashMap() {
		arr = new KeyValuePair[defalutLength];
		size = 0;
	}
	
	public V put(K k, V v) {
		int index = findIndex(k);
		//todo:find out of index
		if (arr[index] == null) {
			arr[index] = new KeyValuePair(k, v);
		} else {
			KeyValuePair tempPair = arr[index];
			arr[index] = new KeyValuePair(k, v);
			arr[index].setNext(tempPair);
		}
		size++;
		return v;
	}
	
	private int findIndex(K key) {
		int index=key.hashCode() % defalutLength;
		return index>0?index:(-1)*index;
	}
 
	public V get(K k) {
		int index = findIndex(k);
		if (arr[index] == null) {
			return null;
		}
		KeyValuePair<K, V> current = arr[index];
		while (current.next != null) {
			if (current.getKey().equals(k)) {
				return current.getValue();
			}
			current = current.next;
		}
		return null;
	}
	public  int size(){
		return this.size;
	}
	
}

同樣我們修改測試的代碼:

@Test
public void MapTest(){
		
	long start=System.currentTimeMillis();
	MyMap<String,String> map =new MyMap();
	for (int i=0;i<10000;i++){
		map.put("Key"+i,"value"+i);
	}
	for (int i=0;i<10000;i++){
		map.get("Key"+i);
	}
	long end=System.currentTimeMillis();
	System.out.println("耗時:"+(end-start));
	
	 start=System.currentTimeMillis();
	Map<String,String> hashMap =new HashMap<>();
	for (int i=0;i<10000;i++){
		hashMap.put("Key"+i,"value"+i);
	}
	for (int i=0;i<10000;i++){
		hashMap.get("Key"+i);
	}
	end=System.currentTimeMillis();
	System.out.println("耗時:"+(end-start));
	 
	
	
	start=System.currentTimeMillis();
	MyHashMap<String,String> myhashMap =new MyHashMap<>();
	for (int i=0;i<10000;i++){
		myhashMap.put("Key"+i,"value"+i);
	}
	for (int i=0;i<10000;i++){
		myhashMap.get("Key"+i);
	}
	end=System.currentTimeMillis();
	System.out.println("耗時:"+(end-start));
	 
}

運行結果:

耗時:2337
耗時:26
耗時:337

我們看到我們使用的鏈表在插入數據的時候進行整理,極大的提高了Map的效率,但離Jdk的性能還有很大的差距。

優化散列算法


對於Map的查找的性能的瓶頸主要在最後的鏈表的查找,我們可以把Key的數據進行擴大,讓Key分佈的更加平均,這樣就能減少最後鏈表迭代次數,實現思路:

  1. 添加一個報警百分比,當key的使用率長度大於當前的比例,我們對key的數組進行擴容
  2. 擴容後對原來的Key進行重新散列

修改後代碼如下:

public class MyHashMap<K, V> {
	
	private  int defalutLength = 16;
	private final double defaultAlfa = 0.75;
	private int size;
	private int arrLength;
	private KeyValuePair<K, V>[] arr;
	
	public MyHashMap() {
		arr = new KeyValuePair[defalutLength];
		size = 0;
		arrLength=0;
	}
	
	public V put(K k, V v) {
		int index = findIndex(k);
		//todo:find out of index
		if(arrLength>defalutLength*defaultAlfa){
			extentArr();
		}
		if (arr[index] == null) {
			arr[index] = new KeyValuePair(k, v);
			arrLength++;
		} else {
			KeyValuePair tempPair = arr[index];
			arr[index] = new KeyValuePair(k, v);
			arr[index].setNext(tempPair);
		}
		size++;
		return v;
	}
	
	private int findIndex(K key) {
		
		int index=key.hashCode() % defalutLength;
		return index>0?index:(-1)*index;
	}
	private void extentArr(){
		  defalutLength=defalutLength*2;
		KeyValuePair<K, V>[] newArr=new KeyValuePair[defalutLength];
		for (int i=0;i<defalutLength/2;i++){
			if(arr[i]!=null){
				int index= findIndex(arr[i].getKey());
				newArr[index]=arr[i];
			}
		}
		arr=newArr;
	}
	public V get(K k) {
		int index = findIndex(k);
		if (arr[index] == null) {
			return null;
		}
		
		KeyValuePair<K, V> current = arr[index];
		while (current.next != null) {
			if (current.getKey().equals(k)) {
				return current.getValue();
			}
			current = current.next;
		}
		return null;
	}
	public  int size(){
		return this.size;
	}
	
}

最終測試性能結果如下:

耗時:2263
耗時:23
耗時:33

性能已經很接近了,至於爲什麼有差異,可能jdk有其它更多的優化(比如當鏈表長度大於8時,使用紅黑樹),但本文就討論到這裏。

(本文完)

作者:老付 如果覺得對您有幫助,可以下方的訂閱,或者選擇右側捐贈作者,如果有問題,請在捐贈後諮詢,謝謝合作 如有任何知識產權、版權問題或理論錯誤,還請指正。 自由轉載-非商用-非衍生-保持署名,請遵循:創意共享3.0許可證 交流請加羣113249828: 點擊加羣 或發我郵件 [email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章