HashMap与Hashtable(二)

来源:互联网 发布:centos系统还原 编辑:程序博客网 时间:2024/05/17 09:05

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

//Hashtableprivate transient volatile Set<Map.Entry<K,V>> entrySet = null;//利用volatile编译后指令保持可见性//HashMapprivate 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
原创粉丝点击