HashTable原理与源码分析

2019-01-21 02:39:00来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

本文版权归 远方的风lyh和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作,如有错误之处忘不吝批评指正!

HashTable内部存储结构

 HashTable内部存储结构为数组+单向链表的形式存储数据,即定义的 Entry<?,?>[] table 变量

   


源码分析

变量定义

   //使用Entry数组存储数据 (Entry 单向链表)
    private transient Entry<?,?>[] table;
    //已经存储在table 的 Entry 个数
    private transient int count;
    /****
    * Entry数组扩容阈值(count)  count>=threshold 时扩容 Entry数组会进行扩容 
    * 建议不要设置超过1 更要不设置太大,导致链表长度过长 会导致查询很慢
     * 比如 HashTble initialCapacity =5  loadFactor=0.75 则计算 threshold =3
     * when count >=3 时 table开始扩容(具体如何扩容的看扩容代码)
     * ***/ 
    private int threshold;
    //负载因子 用于计算 threshold  
    private float loadFactor;
    //记录修改次数
    private transient int modCount = 0;
    //table数组的最大长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

Entry点向链表结构

/**
 * Hashtable bucket collision list entry
 *单向链表
 */
private static class Entry<K,V> implements Map.Entry<K,V> {
    //hash值
    final int hash;
    //key
    final K key;
    //value
    V value;
    //后继
    Entry<K,V> next;

    protected Entry(int hash, K key, V value, Entry<K,V> next) {
        this.hash = hash;
        this.key =  key;
        this.value = value;
        this.next = next;
    }

    @SuppressWarnings("unchecked")
    protected Object clone() {
        return new Entry<>(hash, key, value,
                              (next==null ? null : (Entry<K,V>) next.clone()));
    }

    // Map.Entry Ops
    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    public V setValue(V value) {
        if (value == null)
            throw new NullPointerException();

        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;

        return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
           (value==null ? e.getValue()==null : value.equals(e.getValue()));
    }

    public int hashCode() {
        return hash ^ Objects.hashCode(value);
    }

    public String toString() {
        return key.toString()+"="+value.toString();
    }
}

构造函数

  /***
      * 初始化HashTable 指定初始化容量(initialCapacity),负载因子(loadFactor)
      * 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;
        //初始化Entry数组
        table = new Entry<?,?>[initialCapacity];
        //计算扩容阈值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

   /***
    * 初始化HashTable 指定初始化容量(initialCapacity)
    * 默认负载因子大小为0.75
    * ***/
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /***
      * 默认初始化 Entry数组 大小为 11
      * loadFactor =0.75
      ***/    
    public Hashtable() {
        this(11, 0.75f);
    }

    /***
     * 
     * 
     ***/
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

put方法

/***
 **添加元素   key value
 ** 注意:put方法是加锁的 这就是 hashTable 为啥是线程安全的原因 阻塞的
 ****/
public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    //hashCode取的是key的HashCode
    int hash = key.hashCode();
    //根据hashCode & long最大值散列 再对 数组长度取模获取到所要插入的数组下标
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    //查看数组该下标下是否存在元素 如存在便利 value(新)覆盖key相同的值
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }
    //保存
    addEntry(hash, key, value, index);
    return null;
}

/***
 *保存
 ***/
private void addEntry(int hash, K key, V value, int index) {
    //记录put次数 +1
    modCount++;

    Entry<?,?> tab[] = table;
    //已存储的Entry个数 >= 阈值 扩容
     if (count >= threshold) {
        // Rehash the table if the threshold is exceeded
        rehash();

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

    // Creates the new entry.
    @SuppressWarnings("unchecked")
    //查询数组原有的Entry链表
    Entry<K,V> e = (Entry<K,V>) tab[index];
    //将Entry<K,V> 保存 并将Entry<K,V>.next 指向原来的Entry链表
    tab[index] = new Entry<>(hash, key, value, e);
    //数组长度+1
    count++;
}

扩容机制

/****
  **扩容
  ** 触发  count(已存储的Entry个数) >= threshold(阈值)
  ****/
   protected void rehash() {
    //扩容前 数组(table)长度   
    int oldCapacity = table.length;
    //扩容前的 table数组
    Entry<?,?>[] oldMap = table;

    // overflow-conscious code
    //扩容后的 数组长度为 oldCapacity*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;
    }
    // 新建一个数组长度为 oldCapacity*2+1 数组
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

    modCount++;
    //计算阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    table = newMap;
    //将旧数组中的Entry 重新计算转移到新数组中
    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;

            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            e.next = (Entry<K,V>)newMap[index];
            newMap[index] = e;
        }
    }
}

 特点:

    1. key、value 都不允许为空 key不允许为空 因为hashcode取的是key 该对象的HashCode()
    2. HashTable 默认构造方法 容量初始化为 11 负载因子为 0.75   若使用带负载因子的构造方法创建HashTable 请不要讲负载因子设置过大 比如 初始化容量设为 1 负载因子设为1000 这样会导致查询很慢
    3. HashTable是数组+单项链表Entry<k,v>的结构来存储数据
    4. 数组table最大长度为 Integer.Max - 8;
    5. 扩容条件 count(HashTbale 存储链表的个数) >= threshold(阈值 计算方法 table.length * loadFactor)
    6. 线程安全

原文链接:https://www.cnblogs.com/lyhc/p/10278256.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:设计模式:中介模式

下一篇:Spring Boot 学习目录