1. 程式人生 > >HashMap原理剖析

HashMap原理剖析

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 Xuas
5 * 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原理剖析