Hashtable 概要
与HashMap主要区别是Hashtable的put,get方法都是同步的,线程安全,但是性能较差 key和value都不能为null,HashMap中key与value都可以为 null 与HashMap类似,key必须实现hashCode()和equals方法,由于equals判断前都会先判断hashCode方法是否相等,两个equals的对象的hashCode()必须相同,否则在put等方法中不会覆盖与HashMap类似,capacity和loadFactor是影响其性能的两个关键参数。capacity代表桶的个数,初始化initialcapacity为较大值可以减少扩容(rehash,transfer)开销,但是初始消耗更多空间,且增大了遍历时间(与capacity和size成正比,没有元素的数组点也需要遍历)开销。loadFactor代表其空间时间性能交换权衡系数,loadFactor默认为0.75,调大该系数使得空间利用率提高,但是get和put方法的时间性能降低。与HashMap类似,其实现基于数组,用开放定址法解决Hash冲突,每个数组点存储一个链表,当元素个数size>capacity*loadFactor时进行扩容 Hashtable迭代器以及其集合视图(keySet,values)的迭代器都具有fail-fast机制,迭代器被创建后,所有除了迭代器外对集合结构性(插入,删除,更新不是结构修改)的修改都会抛出异常。迭代器通过检查modCount来判断是否在迭代过程中出现了结构性的修改。 Hashtable是线程安全的,其线程安全是基于同步的,如果不需要线程安全建议使用HashMap,如果需要高并发,建议使用ConcurrentHashMap
Hashtable 类头部
Hashtable继承Dictionary,而HashMap继承AbstractMap。Dictionary只是提供了虚函数,没有实现任何方法,AbstractMap实现了丰富的方法,如:equals,toString等。 HashMap与Hashtable实现的其他接口都是一样的
1 2 3 4 5 6 | <code class="language-java hljs ">public class Hashtable<k,v> extends Dictionary<k,v> implements Map<k,v>, Cloneable, java.io.Serializable { public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable </k,v></k,v></k,v></k,v></k,v></k,v></code> |
主要成员变量
table数组用来存储元素链表 count计数元素个数 threshold 扩容的阈值 loadFactor 扩容因子,控制扩容时机(capacity*loadFactor
1 2 3 4 5 6 | <code class="language-java hljs "> private transient Entry<k,v>[] table; private transient int count; private int threshold; private float loadFactor; private transient int modCount = 0; transient int hashSeed; </k,v></code> |
构造方法
根据initialCapacity,loadFactor,创建table数组,计算threshold 根据Map初始化,首先创建二倍于原Map size的table数组,将原有元素transfer到新table中,该过程是同步的与HashMap不同,其容量capacity并不是2的幂次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <code class="language-java hljs "> public Hashtable(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); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); initHashSeedAsNeeded(initialCapacity); } public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } public Hashtable() { this(11, 0.75f); } public Hashtable(Map<!--? extends K, ? extends V--> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); } public synchronized void putAll(Map<!--? extends K, ? extends V--> t) { for (Map.Entry<!--? extends K, ? extends V--> e : t.entrySet()) put(e.getKey(), e.getValue()); } </code> |
基本节点 Entry
clone为浅拷贝,没有创建key和value 单链表节点除了保存key和value外,还保存了指向下一节点的指针next 有hash值域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <code class="language-java hljs "> private static class Entry<k,v> implements Map.Entry<k,v> { int hash; final K key; V value; Entry<k,v> next; protected Entry(int hash, K key, V value, Entry<k,v> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } protected Object clone() { return new Entry<>(hash, key, value, (next==null ? null : (Entry<k,v>) next.clone())); } // set get方法 public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<!--?,?--> e = (Map.Entry)o; return key.equals(e.getKey()) && value.equals(e.getValue()); } public int hashCode() { return (Objects.hashCode(key) ^ Objects.hashCode(value)); } public String toString() { return key.toString()+"="+value.toString(); } } </k,v></k,v></k,v></k,v></k,v></code> |
Hashtable 中的Holder内部类
Holder用来加载当虚拟机完全启动后才初始化的因子由于String类型的key的hashCode方法可能产生更多的hash碰撞,所以JDK7中设定了阈值,当超过阈值后使用一种特殊的hashCode计算方法,JDK1.8中已经去除相应机制初始化hashSeed时,首先判断虚拟机是否完全启动,然后根据是否使用altHashing决定hashSeed的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <code class="language-java hljs "> static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE; private static class Holder { static final int ALTERNATIVE_HASHING_THRESHOLD; static { String altThreshold = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "jdk.map.althashing.threshold")); int threshold; try { threshold = (null != altThreshold) ? Integer.parseInt(altThreshold) : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT; // disable alternative hashing if -1 if (threshold == -1) { threshold = Integer.MAX_VALUE; } if (threshold < 0) { throw new IllegalArgumentException("value must be positive integer."); } } catch(IllegalArgumentException failed) { throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed); } ALTERNATIVE_HASHING_THRESHOLD = threshold; } } final boolean initHashSeedAsNeeded(int capacity) { boolean currentAltHashing = hashSeed != 0; boolean useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); boolean switching = currentAltHashing ^ useAltHashing; if (switching) { hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0; } return switching; } </code> |
插入元素 put方法
与HashMap最大的区别在于整个put方法是被synchronized包围的,整个方法是同步的计算key的hash值,如果使用alternative hashing还需要与hashSeed进行抑或,进一步打乱与Integer.maxvalue按位与,确保hash值为正的,对table.length取余计算index值 table.index位置可能已有元素(产生hash碰撞),采用头插法,将元素插入到index位置的头部如果元素个数超过threshold,进行扩容(rehash()),扩容至原来的2倍多一的大小由于table.length变化,index需要重新计算将原table中的元素transfer到新的table中,将头插法添加新元素
注意(e.hash == hash)&& e.key.equals(key)在判断是插入还是更新时,先判断hash值是否相等,如果hash值不等,即便equals返回true也会执行插入操作,而不是更新操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | <code class="language-java hljs "> public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<k,v> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { V old = e.value; e.value = value; return old; } } modCount++; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = hash(key); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. Entry<k,v> e = tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; return null; } private int hash(Object k) { // hashSeed will be zero if alternative hashing is disabled. return hashSeed ^ k.hashCode(); } protected void rehash() { int oldCapacity = table.length; Entry<k,v>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<k,v>[] newMap = new Entry[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); boolean rehash = initHashSeedAsNeeded(newCapacity); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<k,v> old = oldMap[i] ; old != null ; ) { Entry<k,v> e = old; old = old.next; if (rehash) { e.hash = hash(e.key); } int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = newMap[index]; newMap[index] = e; } } } </k,v></k,v></k,v></k,v></k,v></k,v></code> |
查询方法 get
定位到table指定位置,然后顺链表查找注意get方法也是同步的,在put方法执行完之前,get方法也需要等待
1 2 3 4 5 6 7 8 9 10 11 | <code class="language-java hljs "> public synchronized V get(Object key) { Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<k,v> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return e.value; } } return null; } </k,v></code> |
查找算法 containsKey containsValue
查询方法也是同步的,需要等待put方法执行完对key的查询可以用hash算法直接定位到table数组指定的位置 对value的查询,需要遍历整个table数组和所有链表节点,因此时间复杂度是与(capacity和size)成正比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <code class="language-java hljs "> public synchronized boolean containsKey(Object key) { Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<k,v> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return true; } } return false; } public boolean containsValue(Object value) { return contains(value); } public synchronized boolean contains(Object value) { if (value == null) { throw new NullPointerException(); } Entry tab[] = table; for (int i = tab.length ; i-- > 0 ;) { for (Entry<k,v> e = tab[i] ; e != null ; e = e.next) { if (e.value.equals(value)) { return true; } } } return false; } </k,v></k,v></code> |
删除
首先定位到table指定位置注意删除对应位置头结点时的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <code class="language-java hljs "> public synchronized V remove(Object key) { Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<k,v> e = tab[index], 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; } </k,v></code> |
浅拷贝 clone
由于没有对key和value进行克隆,所以当通过原map修改key和value的属性时,新map中的key和value也会改变与HashMap不同的是HashMap为对每个节点重建了Entry(同样没有克隆key和value),HashTable只是重建了table中的每个头结点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <code class="language-java hljs "> public synchronized Object clone() { try { Hashtable<k,v> t = (Hashtable<k,v>) super.clone(); t.table = new Entry[table.length]; for (int i = table.length ; i-- > 0 ; ) { t.table[i] = (table[i] != null) ? (Entry<k,v>) table[i].clone() : null; } t.keySet = null; t.entrySet = null; t.values = null; t.modCount = 0; return t; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } </k,v></k,v></k,v></code> |
视图 KeySet ValueSet EntrySet
视图是针对于HashTable 的table 进行的操作,与通过HashTable操作效果相同与HashMap不同,contains,remove方法又重新写了一遍,而在HashMap中是直接调用的HashMap的已有方法,HashMap中的实现更简洁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <code class="language-java hljs "> private class EntrySet extends AbstractSet<map.entry<k,v>> { public Iterator<map.entry<k,v>> iterator() { return getIterator(ENTRIES); } public boolean add(Map.Entry<k,v> o) { return super.add(o); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry entry = (Map.Entry)o; Object key = entry.getKey(); Entry[] tab = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) if (e.hash==hash && e.equals(entry)) return true; return false; } public boolean remove(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<k,v> entry = (Map.Entry<k,v>) o; K key = entry.getKey(); Entry[] tab = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<k,v> e = tab[index], prev = null; e != null; prev = e, e = e.next) { if (e.hash==hash && e.equals(entry)) { modCount++; if (prev != null) prev.next = e.next; else tab[index] = e.next; count--; e.value = null; return true; } } return false; } public int size() { return count; } public void clear() { Hashtable.this.clear(); } } </k,v></k,v></k,v></k,v></map.entry<k,v></map.entry<k,v></code> |
迭代器
由于rehash等因素,迭代次序并不保证不变查找下一个元素算法:如果当前链表已经到尾节点,从数组中顺次查找下一个非空节点,头结点作为next() 通过模拟枚举变量KEYS,VALUES,ENTRYS,同时实现了三种视图的Iterator Enumerator是已经被废弃的迭代元素的方法,相比于Iterator他缺少了remove方法,且方法名更长 Hashtable同时对这两种接口进行了适配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | <code class="language-java hljs "> private class Enumerator<t> implements Enumeration<t>, Iterator<t> { Entry[] table = Hashtable.this.table; int index = table.length; Entry<k,v> entry = null; Entry<k,v> lastReturned = null; int type; /** * Indicates whether this Enumerator is serving as an Iterator * or an Enumeration. (true -> Iterator). */ boolean iterator; /** * The modCount value that the iterator believes that the backing * Hashtable should have. If this expectation is violated, the iterator * has detected concurrent modification. */ protected int expectedModCount = modCount; Enumerator(int type, boolean iterator) { this.type = type; this.iterator = iterator; } public boolean hasMoreElements() { Entry<k,v> e = entry; int i = index; Entry[] t = table; /* Use locals for faster loop iteration */ while (e == null && i > 0) { e = t[--i]; } entry = e; index = i; return e != null; } public T nextElement() { Entry<k,v> et = entry; int i = index; Entry[] t = table; /* Use locals for faster loop iteration */ while (et == null && i > 0) { et = t[--i]; } entry = et; index = i; if (et != null) { Entry<k,v> e = lastReturned = entry; entry = e.next; return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e); } throw new NoSuchElementException("Hashtable Enumerator"); } // Iterator methods public boolean hasNext() { return hasMoreElements(); } public T next() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); return nextElement(); } public void remove() { if (!iterator) throw new UnsupportedOperationException(); if (lastReturned == null) throw new IllegalStateException("Hashtable Enumerator"); if (modCount != expectedModCount) throw new ConcurrentModificationException(); synchronized(Hashtable.this) { Entry[] tab = Hashtable.this.table; int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length; for (Entry<k,v> e = tab[index], prev = null; e != null; prev = e, e = e.next) { if (e == lastReturned) { modCount++; expectedModCount++; if (prev == null) tab[index] = e.next; else prev.next = e.next; count--; lastReturned = null; return; } } throw new ConcurrentModificationException(); } } } </k,v></k,v></k,v></k,v></k,v></k,v></t></t></t></code> |
序列化
与HashMap实现相同,key与value分别写出,在对端逐个读入Key和value,然后加入新Map进行关联由于count在可以传输得到,所以预先确定了table的容量,减少了扩容的开销
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | <code class="language-java hljs "> private void writeObject(java.io.ObjectOutputStream s) throws IOException { Entry<k, v=""> entryStack = null; synchronized (this) { // Write out the length, threshold, loadfactor s.defaultWriteObject(); // Write out length, count of elements s.writeInt(table.length); s.writeInt(count); // Stack copies of the entries in the table for (int index = 0; index < table.length; index++) { Entry<k,v> entry = table[index]; while (entry != null) { entryStack = new Entry<>(0, entry.key, entry.value, entryStack); entry = entry.next; } } } // Write out the key/value objects from the stacked entries while (entryStack != null) { s.writeObject(entryStack.key); s.writeObject(entryStack.value); entryStack = entryStack.next; } } private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the length, threshold, and loadfactor s.defaultReadObject(); // Read the original length of the array and number of elements int origlength = s.readInt(); int elements = s.readInt(); // Compute new size with a bit of room 5% to grow but // no larger than the original size. Make the length // odd if it's large enough, this helps distribute the entries. // Guard against the length ending up zero, that's not valid. int length = (int)(elements * loadFactor) + (elements / 20) + 3; if (length > elements && (length & 1) == 0) length--; if (origlength > 0 && length > origlength) length = origlength; Entry<k,v>[] newTable = new Entry[length]; threshold = (int) Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1); count = 0; initHashSeedAsNeeded(length); // Read the number of elements and then all the key/value objects for (; elements > 0; elements--) { K key = (K)s.readObject(); V value = (V)s.readObject(); // synch could be eliminated for performance reconstitutionPut(newTable, key, value); } this.table = newTable; } </k,v></k,v></k,></code> |