• Java集合源码分析--HashMap


    转载自 http://www.cnblogs.com/zhangyinhua/p/7698642.html#_label0

    一,关于HashMap API定义

    1、哈希表基于map接口的实现,这个实现提供了map所有的操作,并且提供了key和value可以为null,(HashMap和HashTable大致上是一样的除了hashmap是异步的和允许key和value为null),
    这个类不确定map中元素的位置,特别要提的是,这个类也不确定元素的位置随着时间会不会保持不变。
    2.
    HashMap的实例有两个参数影响性能,初始化容量(initialCapacity)和loadFactor加载因子。

    二,HashMap 的属性

    
    
    HashMap的实例有两个参数影响其性能。

      初始容量:哈希表中桶的数量  加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度

      当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的实际容量)时,则对该哈希表进行rehash操作,将哈希表扩充至两倍的桶数。

    //Java中默认初始容量为16,加载因子为0.75。        
    HashMap hm=new HashMap(); for(int i=0;i<17;i++){ hm.put(i,i); } System.out.println(hm.size());//17 System.out.println(hm.table.length);//32

     1)loadFactor加载因子

        定义:loadFactor译为装载因子。装载因子用来衡量HashMap满的程度,是控制数组存放数据的疏密程度。计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。

        loadFactor越趋近于1,那么数组中存放的数据(entry)也就越多,但我们在通过key拿到我们的value时,是先通过key的hashcode值,找到对应数组中的位置,

    如果该位置中有很多元素,则需要通过equals来依次比较链表中的元素,拿到我们的value值,这样花费的性能就很高,

        loadFactor越趋近于0那么数组中存放的数据也就越稀,分散的太开,浪费很多空间,这样也不好,

    所以在hashMap中loadFactor的初始值就是0.75,一般情况下不需要更改它。

      2)桶

        数组中每一个位置上都放有一个桶,每个桶里就是装一个链表,链表中可以有很多个元素(entry),这就是桶的意思。也就相当于把元素都放在桶中。

      3)capacity

        capacity译为容量代表的数组的容量,也就是数组的长度,同时也是HashMap中桶的个数。默认值是16。

      4)size的含义

        size就是在该HashMap的实例中实际存储的元素的个数

      5)threshold的作用

        threshold = capacity * loadFactor,当Size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是衡量数组是否需要扩增的一个标准

        注意这里说的是考虑,因为实际上要扩增数组,除了这个size>=threshold条件外,还需要另外一个条件。

        什么时候会扩增数组的大小?在put一个元素时先size>=threshold并且还要在对应数组位置上有元素,这才能扩增数组。

    二、HashMap的源码分析

    2.1、HashMap的层次关系与继承结构

      1)HashMap继承结构

        

      上面就继承了一个abstractMap,也就是用来减轻实现Map接口的编写负担。

      2)实现接口

        

        Map<K,V>:在AbstractMap抽象类中已经实现过的接口,这里又实现,实际上是多余的。但每个集合都有这样的错误,也没过大影响

        Cloneable:能够使用Clone()方法,在HashMap中,实现的是浅层次拷贝,即对拷贝对象的改变会影响被拷贝的对象。

        Serializable:能够使之序列化,即可以将HashMap对象保存至本地,之后可以恢复状态。

    2.2、HashMap类的属性

    public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
        // 序列号
        private static final long serialVersionUID = 362498820763181265L;    
        // 默认的初始容量是16
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   
        // 最大容量
        static final int MAXIMUM_CAPACITY = 1 << 30; 
        // 默认的填充因子
        static final float DEFAULT_LOAD_FACTOR = 0.75f;
        // 当桶(bucket)上的结点数大于这个值时会转成红黑树
        static final int TREEIFY_THRESHOLD = 8; 
        // 当桶(bucket)上的结点数小于这个值时树转链表
        static final int UNTREEIFY_THRESHOLD = 6;
        // 桶中结构转化为红黑树对应的table的最小大小
        static final int MIN_TREEIFY_CAPACITY = 64;
        // 存储元素的数组,总是2的幂次倍
        transient Node<k,v>[] table; 
        // 存放具体元素的集
        transient Set<map.entry<k,v>> entrySet;
        // 存放元素的个数,注意这个不等于数组的长度。
        transient int size;
        // 每次扩容和更改map结构的计数器
        transient int modCount;   
        // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
        int threshold;
        // 填充因子
        final float loadFactor;
    }

    2.3、HashMap的构造方法

      有四个构造方法,构造方法的作用就是记录一下16这个数给threshold(这个数值最终会当作第一次数组的长度。)和初始化加载因子。注意,hashMap中table数组一开始就已经是个没有长度的数组了。

      构造方法中,并没有初始化数组的大小,数组在一开始就已经被创建了,构造方法只做两件事情,一个是初始化加载因子,另一个是用threshold记录下数组初始化的大小。注意是记录。

      

    三、HashMap源码分析(二)

    3.1、put方法

       /* 
         * 1. 通过key的hash值确定table下标
         * 2. 查找table下标,如果key存在则更新对应的value
         * 3. 如果key不存在则调用addEntry()方法
         */  
        public V put(K key, V value) {
            if (table == EMPTY_TABLE) {
                inflateTable(threshold);
            }

            if (key == null)
                return putForNullKey(value);

            int hash = hash(key);
            int i = indexFor(hash, table.length);
            for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                Object k;
             // 哈希码相同并且对象相同时
                if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                     // 新值替换旧值,并返回旧值  
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }
            // key不存在时,加入新元素  
            modCount++;
            addEntry(hash, key, value, i);
            return null;
        }

    3.2、get方法

    final Entry<K,V> getEntry(Object key) {
            if (size == 0) {
                return null;
            }

            int hash = (key == null) ? 0 : hash(key);
            for (Entry<K,V> e = table[indexFor(hash, table.length)];
                 e != null;
                 e = e.next) {
                Object k;
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            }
            return null;
        }

    3.3、resize方法

     /**
         * 扩展到指定的大小 
         */
        void resize(int newCapacity) {
            Entry[] oldTable = table;
            int oldCapacity = oldTable.length;
            if (oldCapacity == MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return;
            }
    
            Entry[] newTable = new Entry[newCapacity];
            transfer(newTable, initHashSeedAsNeeded(newCapacity));
            table = newTable;
            threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
        }

    四、总结

    4.1、关于数组扩容

      从putVal源代码中我们可以知道,当插入一个元素的时候size就加1,若size大于threshold的时候,就会进行扩容。假设我们的capacity大小为32,loadFator为0.75,则threshold为24 = 32 * 0.75,

      此时,插入了25个元素,并且插入的这25个元素都在同一个桶中,桶中的数据结构为红黑树,则还有31个桶是空的,也会进行扩容处理,其实,此时,还有31个桶是空的,好像似乎不需要进行扩容处理,

      但是是需要扩容处理的,因为此时我们的capacity大小可能不适当。我们前面知道,扩容处理会遍历所有的元素,时间复杂度很高;前面我们还知道,经过一次扩容处理后,元素会更加均匀的分布在各个桶中,

      会提升访问效率。所以,说尽量避免进行扩容处理,也就意味着,遍历元素所带来的坏处大于元素在桶中均匀分布所带来的好处。 

    4.2、总结

      1)要知道hashMap在JDK1.8以前是一个链表散列这样一个数据结构,而在JDK1.8以后是一个数组加链表加红黑树的数据结构。

      2)通过源码的学习,hashMap是一个能快速通过key获取到value值得一个集合,原因是内部使用的是hash查找值得方法。

  • 相关阅读:
    关于高等代数的证明题
    关于微小摄动法的专题讨论
    关于数学分析的数学竞赛题
    关于高等代数的数学竞赛题
    关于幂等阵与幂幺阵的专题讨论
    关于幂零阵与秩1阵的专题讨论
    关于可交换阵与数量阵的专题讨论
    关于等价标准形的专题讨论
    css制作圣诞树
    Integer.parseInt() 错误
  • 原文地址:https://www.cnblogs.com/mcahkf/p/8891441.html
Copyright © 2020-2023  润新知