Java容器源码(七)——HashTable源码分析(基于JDK8)


更多Java容器源码分析可以参考:Java容器源码分析系列(持续更新中!)

(一)、概述

  1. HashTable其实和HashMap差不多,只不过HashTable是线程安全的,而HashMap则是线程不安全的,HashTable使用synchroized关键字实现了同步。同时HashTable不允许value值为null。
  2. HashTable也是基于数组和链表实现的,实现方式基本相同,但是感觉HashMap的实现更加细致。
  3. 我觉得在看HashTable之前,建议先看一下HashMap的源码。这里可以看一下我关于HashMap源码分析的文章:Java容器源码(五)——HashMap源码分析(基于JDK8)

(二)、类名

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable
  1. HashTable继承自Dictionary、实现了Map接口、Cloneable接口、Serializable接口
  2. Dictionary接口:它是一个抽象类,定义了一些简单的增删改查方法
  3. Map接口:实现了Map的接口
  4. Cloneable接口:实现了浅克隆的功能
  5. Serializable接口:实现了序列化和反序列的功能

(三)、成员变量

	/**
     * 同样使用键值对来存储元素,类型为键值对数组
     */
    private transient Entry<?,?>[] table;

    /**
     * HashTable中元素的个数
     */
    private transient int count;

    /**
     * 和HashMap一样,阈值
     *  当元素的总数超过这个阈值,就会进行扩容操作
     */
    private int threshold;

    /**
     * 负载因子
     */
    private float loadFactor;

    /**
     * 修改次数
     */
    private transient int modCount = 0;
     /**
     * 数组的最大容量
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  1. HashTable的成员变量大部分和HashMap是相同的,并且也同样使用键值对来存储元素。规定了数组的最大容量。

(四)、构造方法

 	/**
     *  参数为初始容量和负载因子的构造方法
     *  下面两个构造方法都是调用这个构造方法的
     */
    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);
		//如果传入的初始容量为0,那就调整初始容量为1
        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        //根据初始容量创建数组
        table = new Entry<?,?>[initialCapacity];
        //计算阈值。阈值=容量*负载因子。
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

    /**
     * 参数为初始容量的有参构造方法,同样负载因子是0.75
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * 无参构造方法
     *  这里要注意的是HashTable的默认初始容量是11,负载因子是0.75
     */
    public Hashtable() {
        this(11, 0.75f);
    }

    /**
     * 传入一个Map类型的参数,将map中的元素添加到HashTable中
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
    	//调用上面第一个构造函数,初始化数组
        this(Math.max(2*t.size(), 11), 0.75f);
        //添加所有元素
        putAll(t);
    }
  1. HashTable和HashMap的构造方法基本相同,没有太多亮点。
  2. 值得注意的是,HashTable的默认初始容量为11,而HashMap的默认初始容量是16

(四)、rehash方法

	/**
     * 其实就是扩容的方法,相当于HashMap中的resize方法
     */
    @SuppressWarnings("unchecked")
    protected void rehash() {
    	//获得旧的数组的长度
        int oldCapacity = table.length;
        //获得旧数组
        Entry<?,?>[] 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<?,?>[] newMap = new Entry<?,?>[newCapacity];
		//修改次数+1
        modCount++;
        //根据新数组容量重新计算出新的阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //将数组指向新创建的数组
        table = newMap;
		//从后遍历旧数组中的每一个位置
        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
            	//获取每一个节点
                Entry<K,V> e = old;
                old = old.next;
				//重新计算hash值
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                //每次添加的节点,都放在index位置中的第一个,作为头节点
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }
  1. 这个方法其实就是HashMap中的resize()方法,因为元素总数超过阈值了,需要进行扩容操作
  2. 不同点在于HashMap的扩容是2倍的初始容量,而HashTable则是2倍的初始容量+1

(五)、addEntry方法

	/**
	*  这个方法主要用来在指定位置上添加元素
	*/
 	private void addEntry(int hash, K key, V value, int index) {
 		//修改次数+1
        modCount++;
		//获得数组
        Entry<?,?> tab[] = table;
        //如果元素总数已经大于阈值
        if (count >= threshold) {
            // 需要对数组进行扩容,减少哈希冲突
            rehash();
			//获得数组
            tab = table;
            //计算哈希值
            hash = key.hashCode();
            //计算位置
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // 在index位置的第一个节点放上元素,作为头节点
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        //元素总数+1
        count++;
    }
  1. HashTable的addEntry需要注意的地方是,每次往HashTable中添加元素时,插入的位置总是index的第一个位置,作为头节点。

(六)、put方法

 	/**
     *  put方法添加元素 
     */
    public synchronized V put(K key, V value) {
        //如果value值为null,则抛出异常
        if (value == null) {
            throw new NullPointerException();
        }

        // 获得数组
        Entry<?,?> tab[] = table;
        //根据key计算哈希值
        int hash = key.hashCode();
        /**
        * 这里计算index位置的方法和HashMap是不一样的
        * 这里是用 hash 与 0x7FFFFFFF,也就是舍弃了最高位,然后再对数组的长度求余
        */
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        //获取数组的index位置
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        //遍历数组中index位置的所有链表节点
        for(; entry != null ; entry = entry.next) {
        	//如果key相等
            if ((entry.hash == hash) && entry.key.equals(key)) {
            	//将旧的元素替换为新的元素
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
		//往HashTable中添加元素
        addEntry(hash, key, value, index);
        return null;
    }
  1. 具体实现和HashMap中差不多,这里就不过多赘述了,注释也比较详细。

(七)、remove方法

 	/**
     *  删除节点的remove方法
     */
    public synchronized V remove(Object key) {
    	//获得数组
        Entry<?,?> tab[] = table;
        //计算key的哈希值
        int hash = key.hashCode();
        //根据哈希值计算在数组中的位置
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        //获取到数组index位置上的第一个元素
        Entry<K,V> e = (Entry<K,V>)tab[index];
        //遍历index位置上的链表
        for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
            //如果碰到key相同的
            if ((e.hash == hash) && e.key.equals(key)) {
                //修改次数加1
                modCount++;
                //如果存在前驱节点
                if (prev != null) {
                    prev.next = e.next;
                } else {
                	//不存在前驱节点,将e.next置为头节点
                    tab[index] = e.next;
                }
                //元素总数-1
                count--;
                //返回删除的值
                V oldValue = e.value;
                e.value = null;
                return oldValue;
            }
        }
        return null;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值