Hashtable与HashMap相同的地方很多,底层数据结构相同,解决散列冲突的方式相同,主要的不同在于Hashtable是线程安全的,当然现在的线程安全定义很泛滥,vector、Hashtable都可以说是线程安全的,不过就是在方法上加上synchronized修饰词,以同步的方式使用。在实际使用时仍然需要额外的代码保证,否则依然会抛出错误
vector示例:
public class t{
public static void main(String[] args){
final Vector<String> arr=new Vector<String>(1000);
int i=0;
while(i++<1000){
arr.add("ssh");
}
new Thread(){
public void run(){
for(int i=arr.size();i>0;i--){//get操作是原子操作,但是获取i值,再进行get操作,
arr.get(i); //不是原子的,
}
}
}.start();
new Thread(){
public void run(){
for(int i=arr.size();i>0;i--){
arr.remove(i);
}
}
}.start();
}
}
可能会抛出ArrayIndexOutOfBoundsException,以vector举例说明Hashtable操作方法虽然是同步修饰的,但是使用过程中仍然需要注意使用场景。
put操作:
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {//不需要null值
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);//根据key对象的hashCode,重新计算hash值,若为null,则hashCode方法会抛出异常
int index = (hash & 0x7FFFFFFF) % tab.length;//计算table数组下标,原理在上篇HashMap中讲过
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {//key已存在则覆盖value,返回旧value
V old = e.value;
e.value = value;
return old;
}
}
modCount++; //否则,增加修改次数,判断是否需要扩展table
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];//头插方式添加新Entry
tab[index] = new Entry<>(hash, key, value, e);
count++; //Entry个数增加
return null;
}
从中可见,Hashtable的key、value不能为null,自然也不会有HashMap中的putForNullKey、getForNullKey。
Entry数组table和Entry节点个数count是以transient修饰,Hashtable中自定义writeObject和readObject,实现底层数组自定义序列化和反序列化,参见 ArrayList的remove、序列化
关于迭代操作:
HashMap和Hashtable 都存在一个entrySet
//Hashtable
private transient volatile Set<Map.Entry<K,V>> entrySet = null;//利用volatile编译后指令保持可见性
//HashMap
private transient Set<Map.Entry<K,V>> entrySet = null;
HashMap的entrySet()方法返回:
public Set<Map.Entry<K,V>> entrySet() {//entrySet调用一个私有的方法
return entrySet0();
}
private Set<Map.Entry<K,V>> entrySet0() {
Set<Map.Entry<K,V>> es = entrySet;//如果为空则返回创建的EntrySet,避免iterator()时
return es != null ? es : (entrySet = new EntrySet());//抛出NullPointException
}
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {//返回迭代器
return newEntryIterator();
}
.........
}
Iterator<Map.Entry<K,V>> newEntryIterator() {
return new EntryIterator();//实际返回为EntryIterator类型对象
}
由方法返回结果看出,如果entrySet为null,返回一个EntrySet类型对象,对象的iterator()方法返回为Iterator<Map.Entry<K,V>>类型对象;
如果entrySet不为null,返回entrySet属性本身,即Set<Map.Entry<K,V>>类型对象,调用iterator()返回同样为Iterator<Map.Entry<K,V>>类型对象。
所以调用entrySet().iterator()方法,实际返回的是一个Iterator<Map.Entry<K,V>>接口的子类EntryIterator类型对象。由于EntryIterator继承自HashIterator<Map.Entry<K,V>>:
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {//Iterator接口中存在三个方法,next()、hasNext()、remove()
return nextEntry(); //抽象类HashIterator实现了其中的后两个,在EntryIterator子类
} //中实现了next方法,这也是为什么调用一个iterator()方法,返回一个
}//迭代器而已,却要放到最下面,因为要返回底层类EntryIterator的迭代器实例。
private abstract class HashIterator<E> implements Iterator<E> {
int expectedModCount; // For fast-fail 英文已经说白了,为了快速失败
HashIterator() {
expectedModCount = modCount;//修改次数与预期修改次数,在ArrayList的remove中已经谈过
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}
final Entry<K,V> nextEntry() {//EntryIterator对象调用next()方法时调用的方法
if (modCount != expectedModCount) //因为除了EntryIterator外,还有ValueIterator和
throw new ConcurrentModificationException();//KeyIterator,检测修改次数,发出快速失败
...........
}
public void remove() {//除了hasNext外,迭代器的其他两个操作都会在开头检测快速失败
............
}
}
由以上可以看出,HashMap的迭代器有三种,ValueIterator、KeyIterator和EntryIterator,三种迭代器都继承于相同的抽象类HashIterator,并给出各自不同的next()方法。Hashtable的entrySet()返回:
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet==null)
entrySet = Collections.synchronizedSet(new EntrySet(), this);
return entrySet;//返回同步的set容器
}
private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return getIterator(ENTRIES);//返回指定类型的迭代器
}
................
}
为了向前兼容,使用原始的迭代器类型:
private <T> Iterator<T> getIterator(int type) {//为了包容Enumeration类型
if (count == 0) {
return Collections.emptyIterator();
} else {
return new Enumerator<>(type, true);
}
}
private class Enumerator<T> implements Enumeration<T>, Iterator<T> {//同时实现了Enumeration和Iterator
protected int expectedModCount = modCount;//为了检测快速失败
public boolean hasMoreElements() {//hasNext实际调用的方法
....
}
public T nextElement() {//next方法实际调用的方法
.........
}
public boolean hasNext() {//Iterator中方法
return hasMoreElements();
}
...........
public T next() {//在调用nextElement之前,检查快速失败
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return nextElement();
}
}
使用迭代器可以参考迭代器模式,将聚合对象的存储和遍历分开,满足单一功能职责,在不破坏封装的前提下,可以提供一个迭代器类来完成对聚合对象的遍历,不过在JDK中一般选择的方式都是作为内部类实现。
总结:
HashMap和Hashtable最大区别仍然是方法是否同步,底层数据结构相同,都是基于数组-链表形式,HashMap中key-value可以为null,Hashtable中不可以,HashMap的迭代方式为Iterator,Hashtable为了向前兼容,实际使用的是Enumeration,HashMap的底层数组table长度为2的整数次幂,Hashtable数组长度则一直为一个奇数,两者关于定位table数组下标的算法不同,都自定义了数组元素的序列化和反序列化。