基礎存儲數據的hash桶由Entry結構的數組存放
而entry數據結構,有hash,key和value,還有一個指向下一個節點的引用next對象node
這裏就和hashmap中的數據結構不同了,hashmap中的數據結構是node,雖然結構上差很少,可是setvalue的非空判斷和hashcode的散列取值都是和node不同的數組
那麼這些數據在何時用呢???
下面來一一瞭解安全
這裏須要注意一下了,咱們前面提到說hashmap中的構造函數,其實其實是不對hash桶進行實例化的,可是hashtable不同,他會直接實例化大小,而且實例化成你指定的大小
並且這裏默認的初始化容器的大小是11,負載因子代銷默認0.75,負載因子的做用就是規定最大容量:hash桶的大小*負載因子 數據結構
public TestHashTable(int initialCapacity, float loadFactor) { //非空判斷 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: " + loadFactor); //至少設置爲1 if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry<?,?>[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); }
1.這裏的put方法加了synchronized修飾符,用來標識線程安全
2.這裏進行put取索引位置的時候,是直接用的key的hashcode方法,而且對hashcode結果進行取正數(& 0x7FFFFFFF),而後對hash桶進行取餘%
而後就是判斷這個key是否存在於這個hash桶中,若是存在更新舊值,並返回舊值
不存在,那麼就添加一個entry,因此put操做的關鍵就是addEntry函數
而咱們add操做其實就是找到對應的散列位置,而後用頭插法源碼分析
private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; //頭插法 tab[index] = new Entry<>(hash, key, value, e); count++; }
說實話,這裏相比hashmap來講簡單多了,主要是少了樹化的操做post
刪除就比較簡單了,就是找到對應的索引位置,而後再查找鏈表,若是是頭節點,直接把entry.next設置爲索引位置的數據,若是不是,就要獲取到pre節點,而後pre.next = entry.nextthis
public synchronized V remove(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; V oldValue = e.value; e.value = null; return oldValue; } } return null; }
主要是for循環這個地方有點意思,其他的到還好,無非就是返回舊值而已spa
修改很少作操做了,和添加,刪除操做差很少,只是沒有裏面的多餘操做,就是找到元素就直接返回了.net
1.hashtable是容許放空鍵值的,也就是鍵和值均可以放null
2.還有hashtable是線程安全的
3.hashmap再1.8以後是數組+鏈表+紅黑樹,hashtable仍是很光棍-》數組+鏈表
4.擴容須要說一下,hashmap會擴容到比設置值大的最小2次冪,hashtable就羣魔亂舞隨意了
5.hashmap和hashtable都是取餘,可是有點不一樣,由於hashmap是2次冪,因此取餘的方式不同是:(n - 1) & hash,爲何這樣,請複習hashmap源碼分析。。。
protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; //直接左移一位,也就是擴大2倍而後+1 =》 大小擴爲 2n + 1 int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { //Integer.MAX_VALUE - 8 if (oldCapacity == MAX_ARRAY_SIZE) //若是老的容量已經達到這個值,anemia繼續保持 // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; //不然設置爲容許的最大值 } //建立新的hash桶 Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; //設置新的閾值 threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; //遍歷hash桶,從後往前 for (int i = oldCapacity ; i-- > 0 ;) { //遍歷全部索引下的鏈表,吧鏈表添加到新的hash桶上 for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; //先取正,而後取餘 int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } }
總結一下吧:
說實話,看完hashtable沒花多久時間,相比較hashmap給人的驚爲天人的操做,hashtable相對來講就比較樸實無華了,惟一的幾個亮點就是線程安全,而後。。。。
想不出來了,只能說存在即合理,不能說hashtable會比較low,也許是我眼拙,大道至簡,也許沒有那些花裏胡哨的纔是真正最實用的
參考:https://juejin.im/post/5a03b258518825188e515d89https://blog.csdn.net/yyc1023/article/details/80619623