HashTable源码解析

HashTable

属性成员

    //hash表数组
    private transient Entry<?,?>[] table;

    //元素个数
    private transient int count;

    //扩容阈值
    private int threshold;

    //负载因子
    private float loadFactor;

    //改动次数
    private transient int modCount = 0;

构造函数

    /**  无参构造函数
         构造一个具有默认初始容量 (11) 和负载因子 (0.75) 的空哈希表。
     */
    public Hashtable() {
        this(11, 0.75f);
    }

	/**
     使用指定的初始容量和默认加载因子 (0.75) 构造一个新的空哈希表。 
     @param initialCapacity 哈希表的初始容量。 
     @exception IllegalArgumentException 如果初始容量小于零抛出异常
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

 /**
    使用指定的初始容量和指定的负载因子构造一个新的空哈希表。
     @param initialCapacity 哈希表的初始容量。
     @param loadFactor 哈希表的加载因子。
     @exception IllegalArgumentException 如果初始容量小于零,或者负载因子为非正数。
     */
    public Hashtable(int initialCapacity, float loadFactor) {
        //如果指定容量小于0,不用存了都报错
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        //如果负载因子小于0,或者loadFactor是非数字的数
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        //如果初始容量为空,则设置为1
        if (initialCapacity==0)
            initialCapacity = 1;
        //负载因子赋值
        this.loadFactor = loadFactor;
        //初始化table
        table = new Entry<?,?>[initialCapacity];
        //扩容阈值赋值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

	 /**
     构造一个与给定 Map 具有相同映射关系的新哈希表。哈希表的初始容量足以容纳给定 Map 中的映射和默认负载因子 (0.75)。
     @param  其映射将放置在此映射中的映射。如果指定的映射为空,则@throws NullPointerException。 
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
        //设置容量为传入map的两倍,如果还小于默认容量就是用默认容量11
        this(Math.max(2*t.size(), 11), 0.75f);
        //将传入哈希表t放入table里
        putAll(t);
    }

hash值和index计算

hash值

  • HashTable中的hash值计算直接使用了Object的hashcode()方法计算
  • HashMap(1.8中):
  	 // 1. 取hashCode值: h = key.hashCode() 
     //  2. 高位参与低位的运算:h ^ (h >>> 16)  
      static final int hash(Object key) {
         int h;
         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }

index

  • HashTable中的index下标(对应位桶数组的下标)直接用hash值对表长取余(hash & 0x7FFFFFFF) % tab.length;
  • HashMap(1.8)的index计算:将对哈希码扰动处理后的结果 与运算(&) (数组长度-1),最终得到存储在数组table的位置(即数组下标、索引) h & (length-1);

核心方法

线程安全

HashTable在核心方法前加了synchronized关键字,表示为同步方法,以达到线程同步的目的,不过这样的方法,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。

put方法

public synchronized V put(K key, V value):存入键值对key-value 键和值都不能是 null

/**
    存入键值对key-value  键和值都不能是 null。
     可以通过使用与原始键相同的键调用 get 方法来检索该值。
     @param key 哈希表键 @
     param value 值
     @return 指定键在此哈希表中的前一个值,或者 null 如果它没有一个 @exception NullPointerException 如果键或值是  null @see Objectequals(Object) @see get(Object)
     */
    public synchronized V put(K key, V value) {

        //确保该值不为空
        if (value == null) {
            throw new NullPointerException();
        }

      
        //创建tab数组指向table
        Entry<?,?> tab[] = table;
        //计算hash值
        int hash = key.hashCode();
        //key对应的位桶下标(hash值对表长度取余)
        int index = (hash & 0x7FFFFFFF) % tab.length;

        
        //创建entry指向对应位置的位桶头结点
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        //遍历链表
        for(; entry != null ; entry = entry.next) {
            //判断结点的hash值和key是否与传入的相等
            if ((entry.hash == hash) && entry.key.equals(key)) {
                //如果相等 保存旧值
                V old = entry.value;
                //赋值新值
                entry.value = value;
                //返回旧值
                return old;
            }
        }
        
        //如果没有找到key相等的结点,调用addEntry方法将结点添加进表里
        addEntry(hash, key, value, index);
        //返回空
        return null;
    }
	
 private void addEntry(int hash, K key, V value, int index) {
        //改动次数+1
        modCount++;
        
        //创建指针保存table地址
        Entry<?,?> tab[] = table;
        //如果元素个数>扩容阈值
        if (count >= threshold) {
            // 如果超过阈值,则重新散列表(扩容)
            rehash();
            
            //指向新表
            tab = table;
            //重新计算hash值
            hash = key.hashCode();
            //计算位置
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // 创建新结点
        //位桶数组index位置
        //创建辅助指针e指向头结点
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        //头插入  结点放入桶中,新结点的next指针指向e(原头结点)  完成头插
        tab[index] = new Entry<>(hash, key, value, e);
        //元素个数+1
        count++;
    }



resize()扩容方法
protected void rehash() {

        //保存旧容量
        int oldCapacity = table.length;
        //oldMap保存旧表
        Entry<?,?>[] oldMap = table;

        // 新容量为旧容量的两倍+1
        int newCapacity = (oldCapacity << 1) + 1;

        //如果新容量>integer的最大值-8
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            //如果旧容量为integer的最大值-8
            if (oldCapacity == MAX_ARRAY_SIZE)
                // 使用旧容量继续跑
                return;
            //旧容量设置为integer的最大值-8
            newCapacity = MAX_ARRAY_SIZE;
        }
        //创建容量为newCapacity的hash表
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        //改动次数+
        modCount++;

        //重新计算扩容阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //table指向新表
        table = newMap;

        //遍历旧表进行数据转移
        for (int i = oldCapacity ; i-- > 0 ;) {
            //从最后一个桶开始遍历,old指向链表头结点
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                //辅助指针e先指向头结点
                Entry<K,V> e = old;
                //old后移
                old = old.next;
                //计算新位置
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                //e的next指向新表对应位置的头结点
                e.next = (Entry<K,V>)newMap[index];
                //头插法  (e已经连接了原头结点)
                newMap[index] = e;
            }
        }
    }
get方法

public synchronized V get(Object key):返回指定键映射到的值,如果此映射不包含键的映射,则返回 null

public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        //计算key的hash值
        int hash = key.hashCode();
        //计算下标
        int index = (hash & 0x7FFFFFFF) % tab.length;
        //遍历对应位置的桶,从头结点开始
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            //如果找到hash值和key与传入的相等的则返回对应的值
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        //找不到返回空
        return null;
    }
remove方法

public synchronized V remove(Object key):从此哈希表中删除key(及其相应的value)。如果key不在哈希表中,则此方法不执行任何操作。

 /**
     @param key 需要删除的键
     @return 键在此哈希表中映射到的值,或者null 如果键没有映射
     */
    public synchronized V remove(Object key) {

        Entry<?,?> tab[] = table;
        //计算hash值
        int hash = key.hashCode();
        //计算位置
        int index = (hash & 0x7FFFFFFF) % tab.length;

        //辅助指针指向头结点
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
        //prev记录前一个结点,e为当前结点
        //遍历桶
        for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {

            //如果hash值和key和结点对应相等
            if ((e.hash == hash) && e.key.equals(key)) {
                //改动次数+1
                modCount++;
                
                if (prev != null) {
                    //前一个结点指针当前结点的后一个结点(越过当前结点,相当于删除)
                    prev.next = e.next;
                } else {
                    //如果是头结点为要删除的结点,将桶头结点设置为头结点的下一个结点
                    tab[index] = e.next;
                }
                //元素个数-1
                count--;
                //保存旧值
                V oldValue = e.value;
                //将e的值设为空
                e.value = null;
                //返回旧值
                return oldValue;
            }
        }
        //如果没找到对应的 返回空
        return null;
    }

HashMap和Hashtable的区别

  • HashMap是非线程安全的,Hashtable是线程安全的,因为使用了synchronized关键字来保证线程安全。

  • HashMap允许key和value都为null,而Hashtable不能null

  • Hashtable和HashMap扩容的方法不一样,Hashtable中数组默认大小11,扩容方式是 old*2+1。HashMap中数组的默认大小是16,而且一定是2的指数,增加为原来的2倍。

  • 两者通过hash值散列到hash表的算法不一样,Hashtable是古老的除留余数法,直接使用Object的hashcode,而后者是强制容量为2的幂,重新根据hashcode计算hash值,在使用hash和(hash表长度 – 1)进行与运算,也等价取膜,但更加高效,取得的位置更加分散,偶数,奇数保证了都会分散到。前者就不能保证。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值