zoukankan      html  css  js  c++  java
  • HashMap原理-1.7

    之所以分两篇文章记录,因为之前一直看的1.7的源码,而且网上很多的都是关于1.7的,今天在1.8上打开源码一看,居然懵了。    没想到1.8的实现变化这么大。所有特地拿一篇文章来记录下。

    本章只介绍1.7的情况

    1.HashMap存储结构

    哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。当下标有冲突的时候,就需要解决冲突,目前解决冲突的方法有:

    1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
    2. 再哈希法
    3. 链地址法
    4. 建立一个公共溢出区

    Java中的hashmap的解决办法就是采用的链地址法。

    源码分析

    数据结构

    /**
    * The table, resized as necessary. Length MUST Always be a power of two.,长度必须为2的指数次,为什么?其中非常重要的原因就是为了hash的平均分布 
    */
    transient Entry<K,V>[] table; //定义table数组,类型为Entry
    static class Entry<K,V> implements Map.Entry<K,V> {  //entry,即链表结构,存放key,value,next节点
    final K key;
    V value;
    Entry<K,V> next;
    int hash;

    2.HashMap put方法

    打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value); //空值的put,放到index0的位置
        int hash = hash(key);
        int i = indexFor(hash, table.length); //获取Hash值对应的数组index
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  //获取数组index的entry,如果不为空则寻找下一个节点,直到到entry为null
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //如果key相同,则替换key的value,或者
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
    
        modCount++;
        addEntry(hash, key, value, i); //添加元素
        return null;
    }
    
    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {  //如果超过装载因子的最大值,则需要对数组进行扩容
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);  //获取hash对应的新的index
        }
    
        createEntry(hash, key, value, bucketIndex); //创建entry
    }
    
    /**
     * Like addEntry except that this version is used when creating entries
     * as part of Map construction or "pseudo-construction" (cloning,
     * deserialization).  This version needn't worry about resizing the table.
     *
     * Subclass overrides this to alter the behavior of HashMap(Map),
     * clone, and readObject.
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];  //将当前数组index的节点保存
        table[bucketIndex] = new Entry<>(hash, key, value, e);  //数组index的值更新为新节点,之前的节点变为新节点的next节点;
        size++;
    }
    

      

    3.HashMap get方法

        public V get(Object key) {
            if (key == null)
                return getForNullKey();  //null值获取
            Entry<K,V> entry = getEntry(key);
    
            return null == entry ? null : entry.getValue();
        }
        final Entry<K,V> getEntry(Object key) {
            int hash = (key == null) ? 0 : hash(key); //求key的hash值
            for (Entry<K,V> e = table[indexFor(hash, table.length)]; //在数组中查找hash值对应的下标
                 e != null;
                 e = e.next) {//如果entry不为null的情况下,继续遍历链表的下一个节点,直到查找到key相同,且hash值相同的,返回搜索结果
                Object k;
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            }
            return null;
        }
    
    
    
    /**
         * Offloaded version of get() to look up null keys.  Null keys map
         * to index 0.  This null case is split out into separate methods
         * for the sake of performance in the two most commonly used
         * operations (get and put), but incorporated with conditionals in
         * others.
         */
        private V getForNullKey() {
            for (Entry<K,V> e = table[0]; e != null; e = e.next) {
                if (e.key == null)
                    return e.value;
            }
            return null;
        }

    4.HashMap扩容

    /**
    * Rehashes the contents of this map into a new array with a
    * larger capacity. This method is called automatically when the
    * number of keys in this map reaches its threshold.
    *
    * If current capacity is MAXIMUM_CAPACITY, this method does not
    * resize the map, but sets threshold to Integer.MAX_VALUE.
    * This has the effect of preventing future calls.
    **/
    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];
            boolean oldAltHashing = useAltHashing;
            useAltHashing |= sun.misc.VM.isBooted() &&
                    (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
            boolean rehash = oldAltHashing ^ useAltHashing;
            transfer(newTable, rehash); //扩容,将原来的数据添加到新的数组中
            table = newTable;
            threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
        }
    
        /**
         * Transfers all entries from current table to newTable.
         */
        void transfer(Entry[] newTable, boolean rehash) {
            int newCapacity = newTable.length;
            for (Entry<K,V> e : table) {
                while(null != e) {
                    Entry<K,V> next = e.next;
                    if (rehash) {
                        e.hash = null == e.key ? 0 : hash(e.key);
                    }
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                }
            }
        }
    

      

    5.HashMap缺点

      a.不支持多线程,即非线程安全;

  • 相关阅读:
    Linnia学习记录
    漫漫考研路
    ENS的学习记录
    KnockoutJS 3.X API 第四章 数据绑定(4) 控制流with绑定
    KnockoutJS 3.X API 第四章 数据绑定(3) 控制流if绑定和ifnot绑定
    KnockoutJS 3.X API 第四章 数据绑定(2) 控制流foreach绑定
    KnockoutJS 3.X API 第四章 数据绑定(1) 文本及样式绑定
    KnockoutJS 3.X API 第三章 计算监控属性(5) 参考手册
    KnockoutJS 3.X API 第三章 计算监控属性(4)Pure computed observables
    KnockoutJS 3.X API 第三章 计算监控属性(3) KO如何实现依赖追踪
  • 原文地址:https://www.cnblogs.com/dpains/p/7162223.html
Copyright © 2011-2022 走看看