概述
Hashtable作为早期的集合,在非并发的情况下,Hashtable已经基本被HashMap取代。而在有同步需求的时候,也可以使用效率更高的ConcurrentHashMap,而不是每个方法都用synchronize修饰的Hashtable。但同时,Hashtbale相对于HashMap与ConcurrentHashMap的来说,实现非常简单。通过Hashtable。我们可以大致了解java中散列表实现的基本原理,为HashMap与ConcurrentHashMap的学习打下基础。
顾名思义,Hashtable是散列表的一种的实现。与List相比,Hashtable的特点是可以存储key-value键值对,并且通过key来快速访问键值对。是不是很像通过下标访问对应元素的数组?与数组相比,Hashtable的优势在于key可以为任意类型。如果数组的下标可以是任意对象,我们可以这样实现一个简易的Hashtable:
1 | public class SimpleHashtable{ |
不过,java中数组的下标只能是int。那么,如果我们找到一种方法,可以让所有类型的key都映射到int呢?这种方法,通常被称为散列函数。最明显的散列函数恐怕就是hashcode了:
1 | public class SimpleHashtable{ |
但是在SimpleHashtable中,如果数组下标重复了(a.hashcode()==b.hashcode()),新值将会覆盖旧值。这种情况叫做散列冲突,可以利用链表来解决(如下)。用链表解决的方法叫做称为拉链法。采取拉链法,除了可以处理散列冲突,还可以避免最大数组容量2^31-1所带来限制:
1 | public class SimpleHashtable{ |
注意到,SimpleHashtable中的table容量只有16。当SimpleHashtable的元素远远超过16时,get方法的效率将会大大降低,所需时间逐渐逼近于遍历list,远远超过了table[i]。所以,类似于ArrayList,SimpleHashtable需要有一个方法扩容table,来降低散列冲突,减少遍历list的机会与时间。
1 | public class SimpleHashtable{ |
至此,SimpleHashtable便大体完成了。有了SimpleHashtable作为铺垫,理解Hashtable便容易许多。
导航
get/put
首先是get方法,很简单:
1 | public synchronized V get(Object key) { |
接着是put方法,有点长,但也很容易理解:
1 | public synchronized V put(K key, V value) { |
rehash
在addEntry方法中,我们看到Hashtable使用count与threshold字段,来判断是否需要进行再散列。而Hashtable也有SimpleHashtable中rehash()的几大步骤:构建新table、 重新散列元素至新table、设置下一次扩容的threshold。
1 | protected void rehash() { |
遍历
遍历散列表是非常常见的操作,和Map差不多的三种遍历方式(key、value、entry)无需多言,除此之外,Hashtable还可以通过Enumeration遍历key或者value:
1 | public synchronized Enumeration<K> keys() { |
Hashtable并没有提供通过Enumeration遍历entry的方法,不过有意思的是,Enumerator的实现中却可以返回entry:
1 | private class Enumerator<T> implements Enumeration<T>, Iterator<T> { |
为什么已经实现,却没有相应的api呢?我们来看看Enumerator实现的Iterator方法:
1 | private class Enumerator<T> implements Enumeration<T>, Iterator<T> { |
原来Iterator的next方法也是使用nextElement()实现的,所以返回的entry是为了支持通过Iterator遍历entry。