第五章 JAVA集合之Hashtable源码浅析

          Hashtable很少被用到,甚至在我们写代码的时候从来没有被用到,只有在背面试宝典的时候经常看到HahsMap和Hashtable的区别是HashMap是线程不安全的,Hashtable是线程安全的。那Hashtable为什么是线程安全的呢。下面来分析一下Hashtable的源码,看看它是怎么保证线程安全。

          一.概括

         Hashtable最大的特点是线程安全的,为了保证线程安全当然少不了关键字synchronized,可以说Hashtable在它提供给外部的方法前都加上了synchronized,Hashtable的实现原理和HashMap是一样的,所以Hashtable的方法和HashMap的方法基本相同,只是有些许细节可能不太一样。

         二.Hashtable的基本数据结构和属性

        1.Hashtable继承的抽象类和实现的接口         

public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>, Cloneable, java.io.Serializable
其中Dictionary抽象类是key-value的映射表,规定一个key能找到相应的value值,但是key和value不能为空,这也导致了Hahstable和HahsMap的一个不同点就是HashMap的key和value可以为null,而Hashtable的key和value不能为空。

Map接口就定义了一些对map操作的基本方法。实现Cloneable接口,说明Hashtable能进行拷贝,至于是能对Hashtable进行深拷贝还是浅拷贝,从Hashtable的源码来看是浅拷贝。

        2.Hashtable的属性和构造方法

        

 /**
     * The hash table data.
     */
    private transient Entry<K,V>[] table;

    /**
     * The total number of entries in the hash table.
     */
    private transient int count;

    /**
     * The table is rehashed when its size exceeds this threshold.  (The
     * value of this field is (int)(capacity * loadFactor).)
     *
     * @serial
     */
    private int threshold;

    /**
     * The load factor for the hashtable.
     *
     * @serial
     */
    private float loadFactor;

    /**
     * The number of times this Hashtable has been structurally modified
     * Structural modifications are those that change the number of entries in
     * the Hashtable or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the Hashtable fail-fast.  (See ConcurrentModificationException).
     */
    private transient int modCount = 0;
       table:用于存储Entry对象的数组

       count:Hashtable的大小,记录存储Hashtable存储元素的个数。

       threshold:Hashtable存储元素个数的临界值,threshold = capacity*loadFactor;当存储元素的个数大于threshold的时候,会对Hashtable进行扩容;例如:当Hashtable的初始大小为20,加载因子为0.5,那么当Hashtable的存储元素个数大于等于10的时候,就会对Hashtable进行扩容。

       loadFactor:加载因子。

       modCount:记录修改?Hashtable的次数;用来实现“fail-fast”机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你(你已经出错了)。

      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];
		//计算Hashtable的阀值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
		//useAltHashing这个值只知道在rehash中用到,但不知道具体的作用是干什么的
        useAltHashing = sun.misc.VM.isBooted() &&
                (initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    }

    /**
     *只设置初始容量的话,加载因子是0.75
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * 可以看出Hashtable的默认的初始流量是11,加载因子是0.75
	 * 这点和HashMap有点不一样,HashMap的默认初始容量是16
     */
    public Hashtable() {
        this(11, 0.75f);
    }

    /**
     * 将Mapt中的元素放入Hashtable中
     * 
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
		//先设定Hashtable的大小和加载因子
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }
    可以看出Hashtable和HashMap属性和构造方法基本是一样的,稍微有点不同的 Hashtable的默认初始容量是11,而HashMap的默认容量是16

    

    3.HashTable的主要方法

    

public synchronized V get(Object key) {
        Entry tab[] = table;
        int hash = hash(key);
		//用hash值对数组的长度进行取模运算,获取key值所对应的数组下标
        int index = (hash & 0x7FFFFFFF) % tab.length;
		//对以数组下标为index为表头的链表进行遍历
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;
            }
        }
        return null;
    }






	public synchronized V put(K key, V value) {
        // value不能为空,这也是Hashtable和HashMap的区别
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        int hash = hash(key);
		//hash值有可能是负数,0x7FFFFFFF的作用是让hash值变为正数,再对数组长度取模,算出在数组中的位置
        int index = (hash & 0x7FFFFFFF) % tab.length;
		//如果Hashtable中有key,key不变,替换key对应的value就可以了
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }

        modCount++;
		//当Hashtable的大小大于等于阀值的时候,对Hashtable进行扩容
        if (count >= threshold) {
            // 对数组进行扩容
            rehash();

            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        Entry<K,V> e = tab[index];
		//将封装了key-value的Entry对象放入数组中,如果数组这个位置还有值
		//让新的对象的next属性指向原来的值,新的对象作为表头
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
        return null;
    }


       /**
	 *对Hashtable进行扩容
	 */
	protected void rehash() {
        int oldCapacity = table.length;
        Entry<K,V>[] oldMap = table;

        // 新的长度是原来的2倍+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<K,V>[] newMap = new Entry[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        boolean currentAltHashing = useAltHashing;
        useAltHashing = sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean rehash = currentAltHashing ^ useAltHashing;

        table = newMap;
        //将原来Hashtable中的元素复制到新的扩容的数组中,
        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                if (rehash) {
                    e.hash = hash(e.key);
                }
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = newMap[index];
                newMap[index] = e;
            }
        }
    }
在这些方法的的前面看到了关键字synchronized,这就是为什么Hashtable是线程安全的。


     四.Hashtable和HashMap的不同点

     1.Hashtable和HashMap所实现的接口和抽象类不一样 。    

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable
public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
     2.Hashtable是线程安全的,这是它在每个方法前面加上了关键字synchronized,HashMap不是线程安全的,这是他们最大的不同点。但由于Hashtable在方法前面加上synchronized,如果多线程对Hashtable进行操作的话,得排队,不能同时操作。这样效率就比较低,有一个更好的选择是CurrentHashMap。CurrentHashMap加锁的粒度要比Hashtable要低,可以让多个线程同时操作。  

    3.HashMap中的key,value都可以为null,而Hashtable中的key和value都不允许为null。

    4.扩容方式不一样,Hashtable是2*count+1,HashMap是2*count,而且HashMap中的数组长度永远都是2的整次幂。







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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值