面試手寫HashMap,手撕HashMap

最困難的事情就是認識自己!

個人博客,歡迎訪問!

前言:

            現在面試時,面試官經常會問到HashMap,簡單點就會問下HashMap的一些關鍵知識點,困難些的可能會當場讓你手寫一個HashMap,考察下你對HashMap底層原理的瞭解深度;所以,今天特別手寫了一個簡單的HashMap,只實現了 put、get、containsKey、keySet 方法的 HashMap,來幫助我們理解HashMap的底層設計原理。

本文參考:https://blog.csdn.net/huangshulang1234/article/details/79713303

 

手撕HashMap:

        首先定義接口:

import java.util.Set;

/**
 *@Title: MyMap 
 * @Description: 自定義map接口
 * @date: 2019年7月13日 下午3:56:57
 */
public interface MyMap<K,V> {
    
	/**
	 * @Description: 插入鍵值對方法
	 * @param k
	 * @param v
	 * @return
	 *@date: 2019年7月13日 下午3:59:16
	 */
	public V put(K k,V v);
	/**
	 * @Description:根據key獲取value
	 * @param k 
	 * @return
	 *@date: 2019年7月13日 下午3:59:40
	 */
	public V get(K k);
	
	/**
	 * @Description: 判斷key鍵是否存在
	 * @param k  key鍵
	 * @return
	 *@date: 2019年7月23日 下午4:07:22
	 */
	public boolean containsKey(K k);
	
	
	/**
	 * @Description: 獲取map集合中所有的key,並放入set集合中
	 * @return
	 *@date: 2019年7月23日 下午4:24:19
	 */
	public Set<K>  keySet();
	
	
	

	
//------------------------------內部接口 Entry(存放key-value)---------------------
		
	/**
	 * @Title: Enter 
	 * @Description: 定義內部接口 Entry,存放鍵值對的Entery接口
	 * @date: 2019年7月13日 下午4:00:33
	 */
	 interface Entry<K,V>{
		/**
		 * @Description: 獲取key方法
		 * @return
		 *@date: 2019年7月13日 下午4:02:06
		 */
		public K getKey();
		/**
		 * @Description:獲取value方法
		 * @return
		 *@date: 2019年7月13日 下午4:02:10
		 */
		public V getValue();
	}
	
}

         接口實現類:

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 *@Title: MyHashMap 
 * @Description:   MyMap接口的實現類
 * @date: 2019年7月13日 下午4:04:56
 */
@SuppressWarnings(value={"unchecked","rawtypes","hiding"})
public class MyHashMap<K, V> implements MyMap<K, V>{
	
	/**
	 * Entry數組的默認初始化長度爲16;通過位移運算向左移動四位,得到二進制碼 "00010000",轉換爲十進制是16
	 */
	private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
	/**
	 * 負載因子默認爲0.75f;負載因子是用來標誌當使用容量佔總容量的75%時,就需要擴充容量了,
	 * 擴充Entry數組的長度爲原來的兩倍,並且重新對所存儲的key-value鍵值對進行散列。
	 */
	private static final float DEFAULT_LOAD_FACTOR = 0.75f;
	
	/**
	 * 可設置的初始容量
	 */
	private int defaultInitSize;
	/**
	 * 可設置的負載因子
	 */
	private float defaultLoadFactor;
	
	/**
	 * 當前已存入的元素的數量
	 */
	private int entryUseSize;
	
	/**
	 * 存放key-value鍵值對對象的數組
	 */
	private Entry<K, V>[] table = null;

	
    /**
     * 無參構造,數組初始大小爲16,負載因子大小爲0.75f
     */
	public  MyHashMap() {
		this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR);
	}
    
	/**
	 * 有參構造,自己設置數組初始大小和負載因子大小
	 * @param defaultInitialCapacity  數組初始大小
	 * @param defaultLoadFactor2    負載因子
	 */
	public MyHashMap(int defaultInitialCapacity, float defaultLoadFactor2) {
		//判斷初始容量參數是否合法
		if (defaultInitialCapacity < 0) {
			//拋出非法參數異常
			throw new IllegalArgumentException("輸入的初始容量參數是非法的  :"+defaultInitialCapacity);
		}
		//判斷負載因子參數是否合法,Float.isNaN()方法是判斷數據是否符合 0.0f/0.0f
		if (defaultLoadFactor2 < 0 || Float.isNaN(defaultLoadFactor2)) {
			throw new IllegalArgumentException("輸入的負載因子參數是非法的  :"+defaultLoadFactor2);
		}
		this.defaultInitSize = defaultInitialCapacity;
		this.defaultLoadFactor = defaultLoadFactor2;
		//初始化數組
		table = new Entry[this.defaultInitSize];
	}
    
	/**
	 * @Description: 集合中的put方法
	 * @param k
	 * @param v
	 * @return 如是更新則返回key的舊value值,如是插入新的key-value則返回null
	 *@date: 2019年7月13日 下午6:29:47
	 */
	@Override
	public V put(K k, V v) {
		V oldValue = null;
		//是否需要擴容?
		//擴容完畢後一定會需要重新進行散列	
		if (entryUseSize >= defaultInitSize * defaultLoadFactor) {
			//擴容並重新散列,擴容爲原來的兩倍
			resize(2 * defaultInitSize);
		}
		//根據key獲取的HASH值、數組長度減1,兩者做'與'運算,計算出數組中的位置
		int index = hash(k) & (defaultInitSize -1);
		//如果數組中此下標位置沒有元素的話,就直接放到此位置上
		if (table[index] == null) {
			table[index] = new Entry(k, v, null);
			//總存入元素數量+1
			++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.getValue();
					//key已存在,直接更新value
					e.value = v;
					return oldValue;
				}
				//獲取數組此下標位置上鍊表的下個元素
				e = e.next;
			}
			//JDK1.7中的鏈表頭插法,直接佔據數組下標位置
		    table[index] = new Entry<K,V>(k, v, entry);
		    //總存入元素數量+1
		    ++entryUseSize;
		}
		return oldValue;
	}

	
	/**
	 * @Description: 根據key獲取value值
	 * @param k
	 * @return
	 *@date: 2019年7月13日 下午6:34:49
	 */
	@Override
	public V get(K k) {
		//通過hash函數和數組元素容量做  【與】運算得到數組下標
		int index = hash(k) & (defaultInitSize -1);
		if (table[index] == null) {
			return null;
		}
		else {
			//獲取到數組下標位置元素
			Entry<K, V> entry = table[index];
			Entry<K, V> e = entry;
			do {
				if (k.equals(e.getKey())) {
					return e.getValue();
				}
				//獲取數組下標位置對應鏈表中的下一個元素
				e = e.next;
			} while (entry != null);
		}
		return null;
	}
	
	
	/**
	 * @Description:擴容並重新將元素進行散列
	 * @param i  擴容後的大小
	 *@date: 2019年7月13日 下午5:06:06
	 */
	public void resize(int size){
		Entry<K,V>[] newTable = new Entry[size];
		//改變數組的初始大小
		defaultInitSize = size ;
		//將已存放鍵值對數量置爲0
		entryUseSize = 0 ;
		//將已存的元算根據最新的數組的大小進行散列
		rehash(newTable);
	}
	
	
	/**
	 * @Description: 重新進行散列
	 * @param newTable
	 *@date: 2019年7月13日 下午5:10:07
	 */
	public void rehash(Entry<K, V>[] newTable){
		List<Entry<K, V>> entryList = new ArrayList<>();
		for(Entry<K, V> entry : table){
			if (entry != null) {
				do {
					//將原來數組中的元素放到list集合中
					entryList.add(entry);
					//如果此數組下標的位置存在鏈表的話,需要遍歷下列表,將列表中的鍵值對數據取出來放到集合中
					entry = entry.next;
				} while (entry != null);	
			}
		}
		
		//將舊的數組引用覆蓋,讓引用指向堆中新開闢的數組
		if (newTable.length > 0) {
			table = newTable;
		}
		
		//所謂重新的散列hash,就是將元素重新放入到擴容後的集合中
		for(Entry<K, V> entry : entryList){
			//重新put
			put(entry.getKey(), entry.getValue());
		}
	}
	
	
	/**
	 * @Description: 根據key獲取hashcod碼值
	 * @param key
	 * @return
	 *@date: 2019年7月13日 下午5:52:22
	 */
	public int hash(K key){
		int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
	}
	
	
	/**
	 * @Description: 判斷是否存在此key
	 * @param k  key鍵
	 * @return
	 *@date: 2019年7月23日 下午4:52:22
	 */
	public boolean containsKey(K k) {
		boolean flag = false;
		
		int index = hash(k) & (defaultInitSize -1);
		if (table[index] == null) {
			return flag;
		}
		else {
			//獲取到數組下標位置元素
			Entry<K, V> entry = table[index];
			Entry<K, V> e = entry;
			do {
				if (k.equals(e.getKey())) {
					flag = true;
					return flag;
				}
				//獲取數組下標位置對應鏈表中的下一個元素
				e = e.next;
			} while (e != null);
		}
		
		return flag; 
	}
	
	
	/**
	 * @Description: 獲取map集合所有的key
	 * @return
	 *@date: 2019年7月23日 下午5:52:22
	 */
	@Override
	public Set<K> keySet() {
		if (entryUseSize == 0) {
			return null;
		}
		Set<K> entrySet = new HashSet<K>();
		for(Entry<K, V> entry : table){
			if (entry != null) {
				do {
					//將原來數組中的元素的key放到set集合中
					entrySet.add(entry.getKey());
					//如果此數組下標的位置存在鏈表的話,需要遍歷下列表,將列表中元素的key取出來放到集合中
					entry = entry.next;
				} while (entry != null);	
			}
		}
	    return entrySet;
	}	
	
	



	
	
	
//----------------------------------------內部類 Entry(存放key-value)----------------
	
	


	/**
	 * @Title: Entry 
	 * @Description: 實現了key-value簡直對接口的java類
	 * @date: 2019年7月13日 下午6:12:16
	 */
    class Entry<K, V> implements MyMap.Entry<K, V>{
		/**
		 * 鍵值對對象的key
		 */
		private K key;
		/**
		 * 鍵值對對象的value
		 */
		private volatile  V value;
		/**
		 * 鍵值對對象指向下一個鍵值對對象的指針
		 */
		private Entry<K, V> next;
		
		/**
		 * 無參構造
		 */
		public  Entry() {
			
		}
		
	    
	    /**
	     * 有參構造
	     * @param key
	     * @param value
	     * @param next
	     */
	    public Entry(K key, V value, Entry<K, V> next) {
			super();
			this.key = key;
			this.value = value;
			this.next = next;
		}


		/**
	     * 獲取key
	     */
		@Override
		public K getKey() {
			return key;
		}
	    
		 /**
	     * 獲取value
	     */
		@Override
		public V getValue() {
			return value;
		}
	}

}

          測試方法:

import org.junit.Test;


/**
 * @Title: TestMyMap
 * @Description:
 * @date: 2019年7月13日 下午6:49:25
 */
public class TestMyMap {
    
	/**
	 * @Description:單元測試
	 * 
	 *@date: 2019年7月23日 下午7:07:22
	 */
	@Test
	public void test() {
		MyMap<String, String> map = new MyHashMap<>();

		for (int i = 0; i < 100; i++) {
			//插入鍵值對
			map.put("key" + i, "value" + i);
		}

		for (int i = 0; i < 100; i++) {
			System.out.println("key" + i + ",value is:" + map.get("key" + i));
		}
		
		//根據key獲取value
		System.out.println("\n"+"此key:key88 的value是   "+map.get("key88"));
		
	    //判斷key是否存在
	    System.out.println(map.containsKey("key885")+"   此key:key885 不存在!");
	    
	    //獲取map集合中所有的key
	    System.out.println(Arrays.toString(map.keySet().toArray()));
	    
	    
	    MyMap<String, String> mapOther = new MyHashMap<>();
	    Set<String> keySet = mapOther.keySet();
	    //獲取map集合中所有的key
	    System.out.println((keySet == null)?null:Arrays.toString(mapOther.keySet().toArray()));
	    
	}

}

不要忘記留下你學習的足跡 [點贊 + 收藏 + 評論]嘿嘿ヾ

一切看文章不點贊都是“耍流氓”,嘿嘿ヾ(◍°∇°◍)ノ゙!開個玩笑,動一動你的小手,點贊就完事了,你每個人出一份力量(點贊 + 評論)就會讓更多的學習者加入進來!非常感謝! ̄ω ̄=

發佈了22 篇原創文章 · 獲贊 33 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章