自己實現一個HashMap

【版權申明】未經博主同意,謝絕轉載!(請尊重原創,博主保留追究權)
https://blog.csdn.net/qq_36000403/article/details/91353988
出自【zzf__的博客】

底層數據結構:數組,鏈表
支持動態擴容

支持以下操作

put(K key, V value)
getIndex(K k, int length)
get(K k)
getNode(Node<K, V> node, K k)

MyMap接口

public interface MyMap<K, V> {

	// 向集合中插入數據
	public V put(K k, V v);

	// 根據k 從Map集合中查詢元素
	public V get(K k);

	// 獲取集合元素個數
	public int size();

	// Entry的作用 === Node節點
	interface Entry<K, V> {
		K getKey();

		V getValue();

		V setValue(V value);
	}
}

具體實現MyHashMap

public class MyHashMap<K, V> implements MyMap<K, V> {

	// 定義table 存放HasMap 數組元素 默認是沒有初始化容器 懶加載
	Node<K, V>[] table = null;
	// 實際用到table 存儲容量 大小
	int size;
	// HashMap默認負載因子,負載因子越小,hash衝突機率越低
	float DEFAULT_LOAD_FACTOR = 0.75f;
	// table默認初始大小 16
	static int DEFAULT_INITIAL_CAPACITY = 16;


	public V put(K key, V value) {
		// 1.判斷table 數組大小是否爲空(如果爲空的情況下 ,做初始化操作)
		if (table == null) {
			table = new Node[DEFAULT_INITIAL_CAPACITY];
		}
		
		// 2. 判斷是否需要擴容
		// 實際存儲大小=負載因子*初始容量=DEFAULT_LOAD_FACTOR0.75*DEFAULT_INITIAL_CAPACITY16=x
		// 如果size>x的時候就需要開始擴容數組,擴容數組大小之前兩倍
		if (size > (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY)) {
			// 需要開始對table進行屬數組擴容
			resize();
		}

		// 3.取出hash值指定下標位置對應的那個Node
		int index = getIndex(key, DEFAULT_INITIAL_CAPACITY);
		Node<K, V> indexNode = table[index];
		Node<K, V> newNode = null;
		
		// 4.沒有發生hash衝突(index衝突)問題
		if (indexNode == null) {
			
			newNode = new Node<K, V>(key, value, null);
			size++;
			
		// 5.發生hash衝突(index衝突)問題
		} else {
			Node<K, V> curNode = indexNode;
			//遍歷鏈表的元素
			while (curNode != null) {
				// 1.key相同,修改值
				//這裏即用到了equals和==是因爲 k有可能被重寫hashCode方法,並且k有可能是基本數據類型(用==比較)。
				//equals和==位置不能調換,因爲如果k被重寫hashCode,假如==在前,比較的是對象的地址
				if (curNode.getKey().equals(key) || curNode.getKey() == key) {
					return curNode.setValue(value);
					
				//2 key不同,添加到鏈表末尾
				//		兩種情況: 1.hascode不同但取模餘數相同  2.或者hashCode 相同
				} else if (curNode.next == null){
					
					// 說明遍歷到最後一個node且該操作不是修改值。創建新的node,下一個結點爲indexNode
						newNode = new Node<K, V>(key, value, indexNode);
						size++;
				}
				//取鏈表下一個node
				curNode = curNode.next;
			}
		}
		//將新創建的node放在table[index]
		table[index] = newNode;
		return null;
	}

	// 對table進行擴容
	private void resize() {

		// 1.生成新的table 是之前的兩倍的大小 DEFAULT_INITIAL_CAPACITY*2
		Node<K, V>[] newTable = new Node[DEFAULT_INITIAL_CAPACITY << 1];
		// 2.重新計算index索引,存放在新的table裏面
		for (int i = 0; i < table.length; i++) {
			//存放在之前的table的Node
			Node<K, V> oldNode = table[i];
			//遍歷該鏈表,將oldTable的值一個個放進新的table中
			while (oldNode != null) {
				//table[i] = null;// 賦值爲null---爲了垃圾回收機制能夠回收 將之前的node刪除

				//1.重新計算該Node在新table的index
				K oldK = oldNode.key;
				int newIndex = getIndex(oldK, newTable.length);
				
				//2.這裏記錄oldNext,因爲下面將oldNodex.next指向新table的結點
				Node<K, V> oldNext = oldNode.next;
				
				//3.oldNode每次都插入newTable[newIndex],如果newTable[newIndex]已經有值,則形成鏈表
					oldNode.next = newTable[newIndex];
					newTable[newIndex] = oldNode;
				
				//4.繼續取oldNext
				oldNode = oldNext;
			}
		}
		// 3.將newtable賦值給老的table
		table = newTable;
		DEFAULT_INITIAL_CAPACITY = newTable.length;
		newTable = null;// 賦值爲null---爲了垃圾回收機制能夠回收
	}
	
	public int getIndex(K k, int length) {
		int hashCode = k.hashCode();
		int index = hashCode % length;
		return index;
	}

	public V get(K k) {

		Node<K, V> node = getNode(table[getIndex(k, DEFAULT_INITIAL_CAPACITY)], k);
		return node == null ? null : node.value;
	}

	public Node<K, V> getNode(Node<K, V> node, K k) {
		while (node != null) {
			if (node.getKey().equals(k)) {
				return node;
			}
			node = node.next;
		}
		return null;
	}

	public int size() {

		return size;
	}
	
	// 測試方法.打印所有的鏈表元素
	void print() {

		for (int i = 0; i < table.length; i++) {
			Node<K, V> node = table[i];
			System.out.print("下標位置[" + i + "]");
			while (node != null) {
				System.out.print("[ key:" + node.getKey() + ",value:" + node.getValue() + "]");
				node = node.next;
			}
			System.out.println();
		}
	}
	
	// 定義節點
	class Node<K, V> implements Entry<K, V> {
		// 存放Map 集合 key
		private K key;
		// 存放Map 集合 value
		private V value;
		// 下一個節點Node
		private Node<K, V> next;

		public Node(K key, V value, Node<K, V> next) {
			super();
			this.key = key;
			this.value = value;
			this.next = next;
		}

		public K getKey() {
			return this.key;
		}

		public V getValue() {
			return this.value;
		}

		public V setValue(V value) {
			// 設置新值的返回老的 值
			V oldValue = this.value;
			this.value = value;
			return oldValue;
		}

	}

}

總結:

在put時再進行容器的初始化,整個過程下來,我覺得最難的那部分就是擴容,當出現索引衝突(hash衝突)時如何處理,這個流程各位朋友自己嘗試實現一遍相信會理解很多。

github地址

https://github.com/sunnyColten/MyCollection

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