HashMap原理剖析
阿新 • • 發佈:2018-02-26
object 是把 continue city http 就是 遞歸實現 mage ima
什麽叫hash?
就是把一個不固定的長度的二進制值映射成一個固定長度的二進制值。
hash算法:就是對應的這個映射規則。
hash值:固定長度的二進制值。
什麽叫hash表?HashMap底層的存儲結構就是hashtable。
什麽是hash算法?
1、除留余數法(應用於根據key找到hash表的index值)
key % table.length = index 得到的這個index數字就是對應的hashtable的索引
2、沖突解決(沖突hashtable中的元素index相同)
table.length = 10
對應的hash值key.hashCode() = 2
2 % 10 = 2
另一個key對應的hash值key.hashCode() = 12
12 % 10 = 2
那麽,該如何解決?
解決hash沖突
01.沖突過程:key4占掉key3的位置(後進來的元素占掉先進來的)
02.那麽key3去哪混?
key4中的next指針會指向key3的位置(Obejct key4_next = new Object(key3))
下面我們jdk源碼的實現方式自定義一個簡化版的HashMap,以便於加深理解。
1.定義MyMap接口
1 package cn.yzx.hashmap; 2 /** 3 * 自定義的Map接口 4 * @author Xuas5 * 6 */ 7 public interface MyMap<K,V> { 8 9 public V put(K k,V v);//將k v屬性包裝成對象存儲到hash表中 10 11 public V get(K k);//根據key獲取value 12 13 public int size();//獲取hash表的長度 14 15 /** 16 * 內部接口Entry<K,V> 17 * Entry對象就是存儲到hash表中的數據對象 18 * hash表中每個元素存儲的數據結構是一個Entry對象(包括key,value,next指針三個屬性)19 */ 20 public interface Entry<K,V> { 21 public K getKey();//獲取key 22 public V getValue();//獲取value 23 } 24 }
2.定義MyHashMap實現類
1 package cn.yzx.hashmap; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class MyHashMap<K,V> implements MyMap<K, V> { 7 8 /** 9 * hash表的默認長度 10 * jdk中的static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 11 */ 12 private static int defaultLength = 16; 13 14 /** 15 * 負載因子(閾值) 16 * 當表中元素數量超過 表容量 * 負載因子,那麽表長度就需要擴容。(預警作用) 17 * static final float DEFAULT_LOAD_FACTOR = 0.75f; 18 */ 19 private static double defaultLoader = 0.75; 20 21 /** 22 * 表中存儲元素的個數 23 */ 24 private int size = 0; 25 26 /** 27 * 存儲數據的Entry[] 28 */ 29 private Entry<K,V>[] table = null; 30 31 /** 32 * 構造初始化屬性值 33 * public HashMap(int initialCapacity, float loadFactor) {} 34 * @param length 35 * @param loader 36 */ 37 public MyHashMap(int length,double loader) { 38 defaultLength = length; 39 defaultLoader = loader; 40 table = new Entry[defaultLength]; 41 } 42 43 /** 44 * 無參構造使用默認值 45 */ 46 public MyHashMap() { 47 this(defaultLength, defaultLoader); 48 } 49 50 /** 51 * 需要把k,v存儲到hash表中 52 */ 53 @Override 54 public V put(K k, V v) { 55 56 if(size >= defaultLength * defaultLoader) {//進行擴容 57 up2size(); 58 } 59 60 //01.獲取表中索引 61 int index = getIndex(k); 62 //02.判斷此index上是否存在數據 63 Entry<K,V> entry = table[index]; 64 if(null == entry) {//為空進行填充entry數據 65 table[index] = new Entry(k,v,null);//因entry==null,故next為null 66 size++; 67 }else {//此index上存在數據 68 table[index] = newEntry(k,v,entry);//新entry覆蓋此位置上的原entry,並將新entry的next指針指向原entry 69 size++; 70 } 71 72 return table[index].getValue(); 73 } 74 75 /** 76 * 實例化Entry對象 77 * @param k 78 * @param v 79 * @return 80 */ 81 private Entry<K,V> newEntry(K k,V v,Entry<K,V> next) { 82 return new Entry(k,v,next); 83 } 84 85 /** 86 * 擴容:默認擴容是擴大一倍 87 * 疑問:擴容後原表中的元素在新表中的位置會變化嗎? 88 * 一定會。因為表中索引index是通過hash算法得到的。即:除留余數法 key.hashCode() % table.length = index 89 * 這個table.length已經變化了!(再散列過程,重新散列分配位置 計算新的索引index) 90 */ 91 private void up2size() { 92 Entry<K,V>[] newTable = new Entry[defaultLength * 2]; 93 } 94 95 /** 96 * 再散列(重新分配位置) 97 * @param newTable 98 */ 99 private void againHash(Entry<K,V>[] newTable) { 100 //01.獲取原表中的數據 101 List<Entry<K,V>> list = new ArrayList<Entry<K,V>>(); 102 103 for(int i=0 ; i<table.length; i++) { 104 if(null == table[i]) {//此判斷目的:不一定hash表中所有的元素都有數據(index:i是hash算法計算出來的) 105 continue; 106 } 107 findEntryByNext(table[i], list); 108 } 109 110 if(list.size() > 0) { 111 //重新初始化hash表屬性 112 size = 0; 113 defaultLength = defaultLength * 2; 114 table = newTable; 115 116 for(Entry<K,V> entry : list) { 117 if(null != entry.next) { 118 entry.next = null; //初始化next指針 119 } 120 put(entry.getKey(), entry.getValue());//將數據添加到擴容後的hash表中 121 } 122 } 123 } 124 125 /** 126 * 因為hash表是鏈表結構,所以通過next遞歸將原表中元素添加到List集合中,以便後續存入新表中 127 * @param entry 128 * @param list 129 */ 130 private void findEntryByNext(Entry<K,V> entry,List<Entry<K,V>> list) { 131 if(null != entry && null != entry.next) { 132 list.add(entry); 133 findEntryByNext(entry.next, list); 134 }else { 135 list.add(entry); 136 } 137 } 138 139 /** 140 * 根據key使用hash算法返回hash表的索引index 141 * 根據key的hash值對hash表長度取模。得到index索引就是表中的存儲位置 142 * @param k 143 * @return 144 */ 145 private int getIndex(K k) { 146 int l = defaultLength; 147 148 int index = k.hashCode() % l; 149 return index >= 0 ? index : -index; 150 } 151 152 /** 153 * 根據key遞歸的去找key相等的value值 154 */ 155 @Override 156 public V get(K k) { 157 158 int index = getIndex(k);//獲取索引 159 if(null == table[index]) { 160 return null; 161 } 162 return findValueByEqualKey(k, table[index]); 163 } 164 165 /** 166 * 表中兩個元素是同一個索引(同一個索引index下存在不同的key) 167 * 所以我們需要拿到參數傳進來的key和index下與之對應的key相同的那個元素的value 168 * 使用遞歸實現 169 * @param k 170 * @param entry 171 * @return 172 */ 173 private V findValueByEqualKey(K k,Entry<K,V> entry) { 174 if(k == entry.getKey() || k.equals(entry.getKey())) { 175 return entry.getValue(); 176 }else { 177 if(null != entry.next) { 178 return findValueByEqualKey(k, entry.next); 179 } 180 } 181 return null; 182 } 183 184 @Override 185 public int size() { 186 // TODO Auto-generated method stub 187 return 0; 188 } 189 190 /** 191 * 內部類實現Entry接口(因為Entry是接口無法實例化,此內部類目的是實例化Entry對象) 192 * static class Node<K,V> implements Map.Entry<K,V> {} 193 * @author Xuas 194 * 195 * @param <K> 196 * @param <V> 197 */ 198 class Entry<K,V> implements MyMap.Entry<K, V> { 199 200 K k; 201 202 V v; 203 204 Entry<K,V> next;//next指針,next是指向Entry對象的 205 206 public Entry(K k,V v,Entry<K,V> next) { 207 this.k = k; 208 this.v = v; 209 this.next = next; 210 } 211 212 @Override 213 public K getKey() { 214 return k; 215 } 216 217 @Override 218 public V getValue() { 219 return v; 220 } 221 222 } 223 224 }
3.測試
1 package cn.yzx.hashmap; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 public class Test { 7 public static void main(String[] args) { 8 // 測試jdk的HashMap 9 Map<String, String> jdkmap = new HashMap<String, String>(); 10 11 Long begin = System.currentTimeMillis(); 12 for (int i = 0; i < 1000; i++) { 13 jdkmap.put("key" + i, "value" + i); 14 } 15 16 for (int i = 0; i < 1000; i++) { 17 System.out.println(jdkmap.get("key" + i)); 18 } 19 Long end = System.currentTimeMillis(); 20 System.out.println("jdk‘time:" + (end - begin)); 21 22 System.out.println("*************************************************************"); 23 // 自定義的MyHashMap 24 MyMap<String, String> mymap = new MyHashMap<String, String>(); 25 26 Long begin2 = System.currentTimeMillis(); 27 for (int i = 0; i < 1000; i++) { 28 mymap.put("key" + i, "value" + i); 29 } 30 31 for (int i = 0; i < 1000; i++) { 32 System.out.println(mymap.get("key" + i)); 33 } 34 Long end2 = System.currentTimeMillis(); 35 System.out.println("jdk‘time:" + (end2 - begin2)); 36 37 } 38 }
這裏本人對於性能的對比沒做太深研究。
就醬~
HashMap原理剖析