源码解读—HashTable

在上一篇学习过HashMap(源码解读—HashMap)之后对hashTable也产生了兴趣,随即便把hashTable的源码看了一下。和hashMap类似,但是也有不同之处。

public class Hashtable<K,V>
  extends Dictionary<K,V>
  implements Map<K,V>, Cloneable, java.io.Serializable 

实现接口:Map,Cloneable,Serializable

继承自Dictionary,此抽象类同AbstractMap,

1、四个构造方法:

第一个构造方法: public Hashtable() ;

    /**
     * 用默认容量(11)和加载系数(0.75)构造一个空的hashTable
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     */
    public Hashtable() {
        //此处竟然没有定义成常量!!!看来oracle的开发人员也是人!!!
        this(11, 0.75f);
    }

第二个构造方法:  public Hashtable(int initialCapacity)

    /**
     * 用指定的容量和默认的系数构造一个新的hashTable
     * Constructs a new, empty hashtable with the specified initial capacity
     * and default load factor (0.75).
     *
     * @param     initialCapacity   the initial capacity of the hashtable.
     * @exception IllegalArgumentException if the initial capacity is less
     *              than zero.
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

第三个构造方法: public Hashtable(int initialCapacity, float loadFactor);

    /**
     * 用指定的容量和加载系数构造一个空的hashTable
     * Constructs a new, empty hashtable with the specified initial
     * capacity and the specified load factor.
     *
     * @param      initialCapacity   the initial capacity of the hashtable.
     * @param      loadFactor        the load factor of the hashtable.
     * @exception  IllegalArgumentException  if the initial capacity is less
     *             than zero, or if the load factor is nonpositive.
     */
    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;
        //数组+单向链表作为hashTable的数据存储容器。初始化数组
        table = new Entry[initialCapacity];
        //计算扩容阀值,当count(hashTable中的所有entry,不是table数组的size) >= threshold的时候进行扩容操作。
        //扩容阀值=容量*加载系数  扩容法制最大不超过(MAX_ARRAY_SIZE + 1)
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //初始化hashSeed,在进行hashCode计算的时候需要使用
        initHashSeedAsNeeded(initialCapacity);
    }

第四个构造方法:public Hashtable(Map<? extends K, ? extends V> t)

    /**
     * Constructs a new hashtable with the same mappings as the given
     * Map.  The hashtable is created with an initial capacity sufficient to
     * hold the mappings in the given Map and a default load factor (0.75).
     *
     * @param t the map whose mappings are to be placed in this map.
     * @throws NullPointerException if the specified map is null.
     * @since   1.2
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
        //TODO 创建一个新的hashTable,这里可以肯出hashTable的加载系数一直都是0.75,不允许调用处进行设置
        //当t的count>11的时候程序取2*t.size(),为甚么取2倍?  标示:XX01
        this(Math.max(2*t.size(), 11), 0.75f);
        //把数据转存到hashTable中
        putAll(t);
    }

在这里调用  public synchronized void putAll(Map<? extends K, ? extends V> t) ;把所有数据放进hashTable中

    /**
     * 把所有的 映射从指定的map复制到hashTable中
     * 如果给定的map中的key值已经存在于hashTable中,则将会覆盖hashTable中key所对应的value(hashTable中key值不允许重复)
     * Copies all of the mappings from the specified map to this hashtable.
     * These mappings will replace any mappings that this hashtable had for any
     * of the keys currently in the specified map.
     *
     * @param t mappings to be stored in this map
     * @throws NullPointerException if the specified map is null
     * @since 1.2
     */
    public synchronized void putAll(Map<? extends K, ? extends V> t) {
        //foreach 循环map数据put到hashTable中
        for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
            put(e.getKey(), e.getValue());
    }

循环调用  public synchronized V put(K key, V value) ;进行entry复制

   /**
     * 把给定的key和value进行映射后放入hashTable,
     * key和value值都不允许为null(hashMap是允许key和value为null的)
     * Maps the specified <code>key</code> to the specified
     * <code>value</code> in this hashtable. Neither the key nor the
     * value can be <code>null</code>. <p>
     *
     *通过调用get(K key)方法可以取出value值
     * The value can be retrieved by calling the <code>get</code> method
     * with a key that is equal to the original key.
     *
     * @param      key     the hashtable key
     * @param      value   the value
     * @return     the previous value of the specified key in this hashtable,
     *             or <code>null</code> if it did not have one
     * @exception  NullPointerException  if the key or value is
     *               <code>null</code>
     * @see     Object#equals(Object)
     * @see     #get(Object)
     */
    //synchronized:看到这个关键字没?这就是hashTable线程安全的原因,加锁了,不允许两个线程同时操作此方法
    public synchronized V put(K key, V value) {
        // Make sure the value is not null 不允许value为null,否则抛出 空指针异常
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        //取hashCode
        int hash = hash(key);
        //计算table下标(hashTable的数据存储容器为数组+链表(链表是为了解决hash冲突))
        //至于为什么这样计算?我也不知道,智商是硬伤啊!
        int index = (hash & 0x7FFFFFFF) % tab.length;
        //从table取出entrust(链表头结点,如果有的话),然后循环链表找到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;
            }
        }
        //如果hashTable没有该映射则新增
        //操作次数++
        modCount++;
        //判断是否需要进行扩容,注意:threshold不是table的长度,而是capacity*loadFactor
        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.   创建一个新的节点,把他放入table
        Entry<K,V> e = tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        //数据量++
        count++;
        return null;
    }

2、其他重点方法

keySet和valueSet的迭代器使用

    /**
     * 返回key的迭代器/枚举器?该怎么叫呢?
     * Returns an enumeration of the keys in this hashtable.
     *
     * @return  an enumeration of the keys in this hashtable.
     * @see     Enumeration
     * @see     #elements()
     * @see     #keySet()
     * @see     Map
     */
    public synchronized Enumeration<K> keys() {
        return this.<K>getEnumeration(KEYS);
    }

    /**
     * 返回value的枚举器,通过这个没去器可以连续取出value的值
     * Returns an enumeration of the values in this hashtable.
     * Use the Enumeration methods on the returned object to fetch the elements
     * sequentially.
     *
     * @return  an enumeration of the values in this hashtable.
     * @see     java.util.Enumeration
     * @see     #keys()
     * @see     #values()
     * @see     Map
     */
    public synchronized Enumeration<V> elements() {
        return this.<V>getEnumeration(VALUES);
    }

    //根据type(KEY/VALUE)取出对应的枚举器
    private <T> Enumeration<T> getEnumeration(int type) {
        //如果hashTable数据为空则返回空的枚举器
        if (count == 0) {
            return Collections.emptyEnumeration();
        } else {
            return new Enumerator<>(type, false);
        }
    }

    //获取迭代器,没什么可分析的,如果不了解可以找一下迭代器的使用
    private <T> Iterator<T> getIterator(int type) {
        if (count == 0) {
            return Collections.emptyIterator();
        } else {
            return new Enumerator<>(type, true);
        }
    }

根据key取出value: public synchronized V get(Object key);

    /**
     * 取出给定的key所映射的value,如果hashTable中没有此key则返回null
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     *下面又啰嗦了一遍,这里的key相等时根据key.equals(k)来进行判断的,也就是字符串内容相等就OK
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code (key.equals(k))},
     * then this method returns {@code v}; otherwise it returns
     * {@code null}.  (There can be at most one such mapping.)
     *
     * @param key the key whose associated value is to be returned
     * @return the value to which the specified key is mapped, or
     *         {@code null} if this map contains no mapping for the key
     * @throws NullPointerException if the specified key is null
     * @see     #put(Object, Object)
     */
    public synchronized V get(Object key) {
        //还是套路:计算index,从table中取出entry节点,再循环链表找key.equals(key).
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            //判断相等的时候需要key的hashCode和内容同时相同才可
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;
            }
        }
        return null;
    }

扩容操作:当count(entry节点的数量/Key的数量) >= threshold 时进行扩容

   /**
     * 增加容量并且对hashTable进行内部重整,以便于有效的接收容纳更多的entry。
     * 当key的数量大于hashTable的阀值时会自动调用这个方法。
     * Increases the capacity of and internally reorganizes this
     * hashtable, in order to accommodate and access its entries more
     * efficiently.  This method is called automatically when the
     * number of keys in the hashtable exceeds this hashtable‘s capacity
     * and load factor.
     */
    protected void rehash() {
        //套路:先保存老数据
        int oldCapacity = table.length;
        Entry<K,V>[] oldMap = table;

        // overflow-conscious code
        //计算出新的容量:(oldCapacity*2)+1
        int newCapacity = (oldCapacity << 1) + 1;
        //如果扩容之后的容量大于容量的最大值则进行判断
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            //如果容量已经是最大值了则无法集训进行扩容,只能return了。终止操作
            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);
        //TODO  标示:XX03
        boolean rehash = initHashSeedAsNeeded(newCapacity);

        table = newMap;
        //循环table
        for (int i = oldCapacity ; i-- > 0 ;) {
            //循环链表
            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
                //注意这里,这里传递的知识引用,也就是说在堆空间中并没有复制一个新的entry,
                //只是把原来的entry的引用复制了一份(不了解的可以查找一下java 的堆栈内存数据存储)
                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];
                //把老数据放到新的table中
                newMap[index] = e;
            }
        }
    }

hashTable继承了序列化和克隆接口,在这里没有对序列化读写方法和克隆方法进行分析,这些都是固定的格式,感兴趣的朋友可以看一些这些接口的使用方法。

在这里还涉及到keySet valueSet 和entrySet,这主要是用来进行迭代使用的,对迭代器不了解的可以找一些迭代器使用的相关资料,这些都是套路,不再分析。

所有的数据操作都离不开增删改查,而这些不同方法很多步骤都是相同的,先进行数据验证,然后进行数据查找,找到之后进行删、改操作。抓住核心其实就没什么难理解的了。

-------------------------------------over--------------------------------------------------

时间: 2024-11-05 12:22:29

源码解读—HashTable的相关文章

dotNet源码解读--HashTable目录扩展的奥秘

摘要:为了探索dotnet中hashtable的目录结构及与目录扩展相关的算法,本文通过对相关源码的阅读与分析,得出如下结论,hashtable的目录是由数组组织,目录元素代表一个数据节点,不是数据桶.目录扩展是扩展当前目录长度2倍往1遍历过程中遇到的第一个素数.目录扩展触发条件:装载因子式的触发,同时考虑到"杂乱程度"需要进行重新散列.目录扩展时需要遍历原有目录中所有的元素.查询过程与探测再散列类似. 关键词:dotnet,hashmap,目录扩展方法,目录扩展触发条件 一.目录结构

jdk1.8.0_45源码解读——HashMap的实现

jdk1.8.0_45源码解读——HashMap的实现 一.HashMap概述 HashMap是基于哈希表的Map接口实现的,此实现提供所有可选的映射操作.存储的是<key,value>对的映射,允许多个null值和一个null键.但此类不保证映射的顺序,特别是它不保证该顺序恒久不变.  除了HashMap是非同步以及允许使用null外,HashMap 类与 Hashtable大致相同. 此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能.迭代col

jdk1.8.0_45源码解读——Map接口和AbstractMap抽象类的实现

jdk1.8.0_45源码解读——Map接口和AbstractMap抽象类的实现 一. Map架构 如上图:(01) Map 是映射接口,Map中存储的内容是键值对(key-value).(02) AbstractMap 是继承于Map的抽象类,它实现了Map中的大部分API.其它Map的实现类可以通过继承AbstractMap来减少重复编码.(03) SortedMap 是继承于Map的接口.SortedMap中的内容是排序的键值对,排序的方法是通过比较器(Comparator).(04) N

QCustomplot使用分享(二) 源码解读

一.头文件概述 从这篇文章开始,我们将正式的进入到QCustomPlot的实践学习中来,首先我们先来学习下QCustomPlot的类图,如果下载了QCustomPlot源码的同学可以自己去QCustomPlot的目录下documentation/qcustomplot下寻找一个名字叫做index.html的文件,将其在浏览器中打开,也是可以找到这个库的类图.如图1所示,是组成一个QCustomPlot类图的可能组成形式. 一个图表(QCustomPlot):包含一个或者多个图层.一个或多个ite

vue源码解读预热-0

vueJS的源码解读 vue源码总共包含约一万行代码量(包括注释)特别感谢作者Evan You开放的源代码,访问地址为Github 代码整体介绍与函数介绍预览 代码模块分析 代码整体思路 总体的分析 从图片中可以看出的为采用IIFE(Immediately-Invoked Function Expression)立即执行的函数表达式的形式进行的代码的编写 常见的几种插件方式: (function(,){}(,))或(function(,){})(,)或!function(){}()等等,其中必有

SpringMVC源码解读 - RequestMapping注解实现解读 - RequestCondition体系

一般我们开发时,使用最多的还是@RequestMapping注解方式. @RequestMapping(value = "/", param = "role=guest", consumes = "!application/json") public void myHtmlService() { // ... } 台前的是RequestMapping ,正经干活的却是RequestCondition,根据配置的不同条件匹配request. @Re

15、Spark Streaming源码解读之No Receivers彻底思考

在前几期文章里讲了带Receiver的Spark Streaming 应用的相关源码解读,但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Approach)的方式,No Receiver的方式的优势: 1. 更强的控制自由度 2. 语义一致性 其实No Receivers的方式更符合我们读取数据,操作数据的思路的.因为Spark 本身是一个计算框架,他底层会有数据来源,如果没有Receivers,我们直接操作数据来源,这其实是一种更自然的方式

jdk1.8.0_45源码解读——Set接口和AbstractSet抽象类的实现

jdk1.8.0_45源码解读——Set接口和AbstractSet抽象类的实现 一. Set架构 如上图: (01) Set 是继承于Collection的接口.它是一个不允许有重复元素的集合.(02) AbstractSet 是一个抽象类,它继承于AbstractCollection.AbstractCollection实现了Set中的绝大部分函数,为Set的实现类提供了便利.(03) HastSet 和 TreeSet 是Set的两个实现类.        HashSet依赖于HashMa

线程本地变量ThreadLocal源码解读

  一.ThreadLocal基础知识   原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板类并未采用线程同步机制,因为线程同步会影响并发性和系统性能,而且实现难度也不小. ThreadLocal在Spring中发挥着重要的作用.在管理request作用域的bean,事务管理,任务调度,AOP等模块中都出现了它的身影. ThreadLocal介绍: 它不是一个线程,而是线程的一个本地化