最困難的事情就是認識自己!
個人博客,歡迎訪問!
前言:
現在面試時,面試官經常會問到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()));
}
}
❤不要忘記留下你學習的足跡 [點贊 + 收藏 + 評論]嘿嘿ヾ
一切看文章不點贊都是“耍流氓”,嘿嘿ヾ(◍°∇°◍)ノ゙!開個玩笑,動一動你的小手,點贊就完事了,你每個人出一份力量(點贊 + 評論)就會讓更多的學習者加入進來!非常感謝! ̄ω ̄=