Hashtable源码分析
来源:互联网 发布:产品数据分析总结 编辑:程序博客网 时间:2024/06/05 17:25
Hashtable和HashMap一样,都是一个哈希表,不允许键和值为null,该类是一个线程安全的,每个方法都加了synchronized关键字。下面是该类的继承关系图:
从上图可以看到,Hashtable继承自Dictionary类,而HashMap继承自AbstractMap,所以这两个类的祖宗就是不一样的。这篇文章主要介绍Hashtable和HashMap的异同点。
对于HashMap不了解的朋友可以参考下面两篇文章:
1. JDK1.8 HashMap源码分析
2. JDk1.7 HashMap源码分析
构造器
底层结构
JDK1.8中HashMap的底层结构是数组+链表+红黑树,JDK1.7中HashMap的底层结构是数组+链表;而Hashtable的底层结构是数组+链表,本文的源码均基于JDK.1.8进行分析。
由于Hashtable和JDK1.7中的HashMap都采用了数组+链表的结构,那么本文将以JDK1.8中的Hashtable和JDK1.7中的HashMap进行比较相同和不同的地方。
初始容量和加载因子
Hashtable和HashMap一样,都有初始容量和加载因子两个影响性能的参数,并且加载因子默认也是0.75。
构造方法
Hashtable的构造方法如下:
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); } 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); }
可以看到,Hashtable和HashMap的构造方法相同的是,均是对初始容量和加载因子完成了设置;不同的地方有2点:
1. HashMap对底层数组采取的懒加载,即当执行第一次插入时才会创建数组;而Hashtable在初始化时就创建了数组;
2. HashMap中数组的默认初始容量是16,并且必须的是2的指数倍数;而Hashtable中默认的初始容量是11,并且不要求必须是2的指数倍数。
基本操作
Hashtable作为哈希表,基本操作有插入一个键值对、按照键查询值以及删除键值对。下面逐个分析。
put(K k,V v)
put的实现如下:
public synchronized V put(K key, V value) { //值不允许为null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; //得到键的hash int hash = key.hashCode(); //得到对应hash在数组中的桶索引 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") //得到桶中链表头节点 Entry<K,V> entry = (Entry<K,V>)tab[index]; //从头开始遍历 for(; entry != null ; entry = entry.next) { //一旦hash值相等并且键相等,替换旧值 if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } //如果没有找到相同键,那么添加新节点 addEntry(hash, key, value, index); return null; }
下面看一下addEntry方法,其实现如下:
private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; //如果尺寸超过了阈值,进行rehash 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++; }
从上面的代码可以看到,当插入一个节点时,如果哈希表的尺寸已经达到了扩容的阈值,那么进行rehash(),之后再将节点插入到链表的头部,这一点和HashMap是一样的,即新节点总是位于桶的头结点。
下面看一下rehash()方法, rehash()方法首先将数组扩容,然后再将数据从旧哈希表中移到新哈希表中,其实现如下:
protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // 扩容,newCapacity=2*oldCapacity+1 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<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; //rehash for (int i = oldCapacity ; i-- > 0 ;) { 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; } } }
rehash()方法主要分为两步:
1. 扩容。扩容策略为newCapacity=2*oldCapacity+1
2. rehash。将节点rehash之后再当做头节点接到新的桶中
在上面的put方法中可以看到很多点与JDK1.7中不同的地方:
1. Hashtable的put()是线程安全的,而HashMap的put()方法不是线程安全的
2. HashMap中键和值均允许为null;Hashtable中均不允许
3. 计算hash的方式不同。Hashtable中使用键的哈希码作为哈希值,而HashMap中的哈希值将根据键的哈希值经过计算得到,其计算方式如下:
final int hash(Object k) { int h = hashSeed;//默认为0 if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
并且HashMap中当hashSeed变化时,同一个键得到的hash值将会不一样。
4. 得到数组中桶的方式不一样。由于HashMap中桶的个数必须是2的指数倍数,因此得到桶索引处的方法为:
static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); }
该方法就相当于对长度求模;而Hashtable中当hash值小于0x7FFFFFFF时和HashMap中一样,当大于0x7FFFFFFF时则不同。
5. 扩容策略。Hashtable扩容时策略是newCapacity=oldCapacity*2+1;而HashMap是newCapacity=2*oldCapacity
HashMap和Hashtable中put方法的相同点有如下2点:
1. 新节点总是作为桶的头节点
2. rehash时桶中的链表顺序会颠倒
get(K k)操作
Hashtable的get()方法用于根据键得到值,其实现如下:
public synchronized V get(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; }
可以看到该实现和HashMap是相同的,只不过是计算hash以及得到桶中索引的方式不同而已。 、
remove(Object o)操作
Hashtable的remove()方法用于根据键删除键值对,其实现如下:
public synchronized V remove(Object key) { Entry<?,?> tab[] = table; //计算hash值 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; }
可以看到删除节点的操作是先计算hash,得到桶的索引,然后再遍历桶中的链表,这和HashMap中的实现一样。
迭代器
由于Hashtable没有实现Iterable接口,所以不能foreach循环遍历其键值,这是因为Hashtable从JDK1.0起就存在了,不过可以使用keys()方法得到键的集合,使用values()得到值的集合。keys()方法的实现如下:
public synchronized Enumeration<K> keys() { return this.<K>getEnumeration(KEYS); }
其中Enumeration是一种类似于Iterator的接口,可以使用该类进行遍历。下面看一下getEnumeration(int type)方法,其实现如下:
private <T> Enumeration<T> getEnumeration(int type) { if (count == 0) { return Collections.emptyEnumeration(); } else { return new Enumerator<>(type, false); } }
可以看到,在哈希表不为空时,返回Enumerator对象,该类的定义如下:
private class Enumerator<T> implements Enumeration<T>, Iterator<T> { Entry<?,?>[] table = Hashtable.this.table; int index = table.length; Entry<?,?> entry; Entry<?,?> lastReturned; 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<?,?> 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; } @SuppressWarnings("unchecked") public T nextElement() { Entry<?,?> 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<?,?> 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; @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 == lastReturned) { modCount++; expectedModCount++; if (prev == null) tab[index] = e.next; else prev.next = e.next; count--; lastReturned = null; return; } } throw new ConcurrentModificationException(); } } }
该类既实现了Enumeration接口,也实现了Iterator接口,构造方法中指明了是否使用Iterator接口的方法。Enumeration接口的方法有:
public interface Enumeration<E> { boolean hasMoreElements(); E nextElement();}
而Iterator接口的定义如下:
public interface Iterator<E> { boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); }}
可以看到该两个接口基本是一致的。在Enumerator的实现中可以发现,除了remove()方法,Iterator接口的另外两个方法都是使用的Enumeration接口的实现,而remove()方法只有在iterator参数为true时才能使用,否则抛出异常。 在keys()的调用过程中可以发现传入的iterator这个参数为false,那么什么时候这个参数会为true呢?
在使用values()方法得到值的集合时,iterator参数会为true,答案如下:
public Collection<V> values() { if (values==null) values = Collections.synchronizedCollection(new ValueCollection(), this); return values; }
由于values()的返回值是一个Collection,必须支持foreach遍历,并且由于Hashtable是线程安全的,所以values使用了Collections.synchronziedCollection()方法对ValueCollection就行了同步封装。ValueCollection类的定义如下:
private class ValueCollection extends AbstractCollection<V> { public Iterator<V> iterator() { return getIterator(VALUES); } public int size() { return count; } public boolean contains(Object o) { return containsValue(o); } public void clear() { Hashtable.this.clear(); } }
主要关注iterator()方法,内部调用了getIterator()方法,该方法如下:
private <T> Iterator<T> getIterator(int type) { if (count == 0) { return Collections.emptyIterator(); } else { return new Enumerator<>(type, true); } }
可以看到这时Enumerator的第二个参数为true。
总结
本文的Hashtable的代码是基于JDK1.8的,而与之比较的是1.7中的HashMap,因为它们的底层结构都是数组+链表。虽然大的结构上两个类相同,但是还是有主要的几点不同:
1. Hashtable是线程安全的;而HashMap不是线程安全的
2. 构造器的区别。Hashtable默认初始容量为11,HashMap为16
3. put方法的区别,主要包括hash的计算,桶中索引的计算,rehash
- HashTable & HashSet 源码分析
- Hashtable 源码分析
- HashTable源码分析
- Hashtable源码分析
- Hashtable源码分析
- STL hashtable 源码分析
- HashTable源码分析
- hashtable源码分析
- HashTable源码分析
- Java-HashTable源码分析
- Hashtable的源码分析
- 《Java源码分析》:Hashtable
- HashTable源码分析
- HashTable源码分析
- HashTable源码分析
- HashTable源码分析
- HashTable源码分析
- HashTable源码分析
- 喷水装置(一)
- 关于顺序表-SDUT OJ 3329 +α
- ORBSlam2学习研究(Code analysis)-ORBSlam2中的闭环检测和后端优化LoopClosing
- 程序猿的自我感想
- python科学计算笔记(十)pandas中时间、日期以及时间序列处理
- Hashtable源码分析
- 51nod oj 1298 圆与三角形
- NYOJ 112 指数运算
- 博客---错了就是错了
- Java——入门“HelloWorld”
- Python 动态生成多维数组
- nginx和swoole高并发原理
- HDU
- STM32F103C8T6之通用异步收发器(发送接收中断)