HashMap与Hashtable(二)

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数组下标的算法不同,都自定义了数组元素的序列化和反序列化。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值