jdk1.7中的HashMap

jdk1.7底层使用的数组+链表实现

HashMap类继承了AbstractHashMap类,实现类Map<K,V>接口,Cloneable,Serializable接口


public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    

   /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认初始容量2^4

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量2^30

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认负载因子0.75f,当构造方法未指定负载因子时

    /**
     * An empty table instance to share when the table is not inflated.
     */
    static final Entry<?,?>[] EMPTY_TABLE = {
    
    };

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;//当前key-vlue键值对的数目

    /**
     * The next size value at which to resize (capacity * load factor).
     * @serial
     */
    // If table == EMPTY_TABLE then this is the initial capacity at which the
    // table will be created when inflated.
    int threshold;//要调整大小的下一个大小值(容量*负载系数),如果table==EMPTY_table,则这时table将在扩容时侯被创建



    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;//负载因子

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;//HashMap被修改的次数

    /**
     * The default threshold of map capacity above which alternative hashing is
     * used for String keys. Alternative hashing reduces the incidence of
     * collisions due to weak hash code calculation for String keys.
     * <p/>
     * This value may be overridden by defining the system property
     * {@code jdk.map.althashing.threshold}. A property value of {@code 1}
     * forces alternative hashing to be used at all times whereas
     * {@code -1} value ensures that alternative hashing is never used.
     */
    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;    
    transient int hashSeed = 0;//hash种子


以下为Entry对象,存储key-value

    static class Entry<K,V> implements Map.Entry<K,V> {
    
    
        final K key//key
        V value;//value
        Entry<K,V> next;//下一个结点
        int hash;//hash值

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
    
    //创建一个Entry对象(key,value,hash),指明下一个结点next
            value = v;
            next = n;
            key = k;
            hash = h;
        }

然后看构造方法

    public HashMap(int initialCapacity, float loadFactor) {
    
    
        if (initialCapacity < 0)//如果初始容量小于0,抛出不合法的初始容量异常
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)//如果初始容量大于最大容量,设置最大容量为初始容量,最大容量为2^30
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))//负载因子<=0,或者负载一直不是合法浮点数,抛出不合法负载因子异常,因子负载因子要大于0
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;//设置
        threshold = initialCapacity;
        init();
    }
        /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
    
    
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
    
    
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);//默认初始容量未16,默认负载因子为0.75f
    }

然后看put方法

    public V put(K key, V value) {
    
    
        if (table == EMPTY_TABLE) {
    
    //table为EMPTY_TABLE({}),也就是刚开始new HashMap<>()时候
            inflateTable(threshold);//为数组分配空间
        }
        if (key == null)//put null key时候
            return putForNullKey(value);//put null key方法
        int hash = hash(key);//计算hash值
        int i = indexFor(hash, table.length);//根据hash值和数组长度计算出一个下标
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    
    //遍历以table[i]为头结点的链表
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    
    //如果要当前结点的hash值等于hash且当前结点的key等于key
                V oldValue = e.value;//记录当前节点value
                e.value = value;//设置当前结点value为value
                e.recordAccess(this);
                return oldValue;//返回旧value
            }
        }

       //如果当前要插入的key不存在
        modCount++;
        addEntry(hash, key, value, i);//添加新的结点
        return null;
    }

然后看inflateTable方法

    private void inflateTable(int toSize) {
    
    
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);//capacity的值为>=toSize的2的n次幂,比如toSize为13,则capacity为16,toSize为17,capactiy为32

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//计算threasholld为容量*负载因子
        table = new Entry[capacity];//创建容量为capactity的数组
        initHashSeedAsNeeded(capacity);//为当前容量数组初始化hash种子
    }

查看initHashSeedAsNeeded方法

    final boolean initHashSeedAsNeeded(int capacity) {
    
    
        boolean currentAltHashing = hashSeed != 0;//初始化时候hashSeed为0,所以currentAltHashing=false
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);//isBooteded默认返回false(如果jvm不进行赋值),
        boolean switching = currentAltHashing ^ useAltHashing;//false^false=false
        if (switching) {
    
    
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }

查看Holder.ALTERNATIVE_HASHING_THRESHOLD部分

  private static class Holder {
    
    

        /**
         * Table capacity above which to switch to use alternative hashing.
         */
        static final int ALTERNATIVE_HASHING_THRESHOLD;

        static {
    
    
            String altThreshold = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    "jdk.map.althashing.threshold"));//根据配置的jvm参数jdk.map.althashing.threshold得到一个altThreshold,doPrivileged底层为native方法

            int threshold;
            try {
    
    
                threshold = (null != altThreshold)
                        ? Integer.parseInt(altThreshold)
                        : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;//计算threshold

                // disable alternative hashing if -1
                if (threshold == -1) {
    
    //threshold=-1
                    threshold = Integer.MAX_VALUE;///MAX_VALUE为0x7fffffff
                }

                if (threshold < 0) {
    
    //threshold <0
                    throw new IllegalArgumentException("value must be positive integer.");
                }
            } catch(IllegalArgumentException failed) {
    
    
                throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
            }

            ALTERNATIVE_HASHING_THRESHOLD = threshold;
        }
    }

因此通过设定一定的jvm参数可以使得useAltHashing的值为false,从而switching为true,从而改变hashSeed的目的

看一下putForNullKey方法

    private V putForNullKey(V value) {
    
    
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    
    //遍历table[0]为头节点的链表
            if (e.key == null) {
    
    //找到key为null的结点,把结点的value更改为当前value
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);//如果不存在null key,那么在添加一个新结点,hash为0,下标为0,key为null,value为value
        return null;
    }

然后看一下hash方法

    final int hash(Object k) {
    
    //给定一个key计算出一个hash值
        int h = hashSeed;//hash种子,默认为0
        if (0 != h && k instanceof String) {
    
    
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();//hashcode()为Object类的方法,为native方法

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);//使得高位参加运算,增加hashcode的散列型
    }

然后提高hash方法得到了一个hash值,
然后看indexFor方法

    static int indexFor(int h, int length) {
    
    
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);//得到数组元素下标,这里巧妙运用了数组长度为2的n次幂,比如数组长度为默认长度16,15二进制后五位为1111,假设计算出的hash值二进制表示为10110010,两者相与为00000010,相与后的值始终在0-15之间也就是 0-数组的长度-1,当然也可以做取余运算,但是&运算的速度要远远大于取余
    }

这样旧得到了数组的下标i,然后下面就判断该key是否在以table[i]为头结点存在,如果存在替换为新value,如果不存在就在链表添加新的元素

然后看addEntry的代码

    void addEntry(int hash, K key, V value, int bucketIndex) {
    
    
        if ((size >= threshold) && (null != table[bucketIndex])) {
    
    //当前table长度>=threadshold且当前数组元素不为null
            resize(2 * table.length);//扩容为原来两倍
            hash = (null != key) ? hash(key) : 0;//重新计算hash
            bucketIndex = indexFor(hash, table.length);//重新计算数组下标
        }

        createEntry(hash, key, value, bucketIndex);//创建一个Entry对象
    }

然后看一下resize方法,

    void resize(int newCapacity) {
    
    
        Entry[] oldTable = table;//记录旧table
        int oldCapacity = oldTable.length;//记录旧table长度
        if (oldCapacity == MAXIMUM_CAPACITY) {
    
    //如果旧table长度=2^30
            threshold = Integer.MAX_VALUE;//threadshold为MAX_VALUE(0x7fffffff)
            return;
        }

        Entry[] newTable = new Entry[newCapacity];//new一个新Entry数组
        transfer(newTable, initHashSeedAsNeeded(newCapacity));//将旧Entry数组的元素移到新数组
        table = newTable;//将newTable的值赋值给旧table
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);//重新计算threadshold
    }

然后看一下transfer方法

    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable, boolean rehash) {
    
    
        int newCapacity = newTable.length;//计算新Entry数组长度
        for (Entry<K,V> e : table) {
    
    //遍历旧 table数组
            while(null != e) {
    
    //当当前结点不为null
                Entry<K,V> next = e.next;//得到当前结点
                if (rehash) {
    
    //得到hash值
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);//重新计算数组下标
                //采用头插法插入
                e.next = newTable[i];//当前结点下一个结点为newTable[i]
                newTable[i] = e;//当前newTable[i]为当前结点
                e = next;//e指向下一个结点
            }
        }
    }

进入createEntry方法

    void createEntry(int hash, K key, V value, int bucketIndex) {
    
    
        Entry<K,V> e = table[bucketIndex];//记录当前数组元素 table[bucketIndex]
        table[bucketIndex] = new Entry<>(hash, key, value, e);//要插入结点的下一个结点为当前结点,也就是头插法
        size++;//key-value的数量加1
    }

然后看一下get方法

    public V get(Object key) {
    
    
        if (key == null)//key为null时候
            return getForNullKey();//获取null key的value
        Entry<K,V> entry = getEntry(key);//指向getEntry方法

        return null == entry ? null : entry.getValue();
    }

进入getForNullKey方法


    private V getForNullKey() {
    
    
        if (size == 0) {
    
    //如果当前key-value数量为0
            return null;
        }
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    
    
            if (e.key == null)//查找到key为null的元素
                return e.value;//返回其value
        }
        return null;
    }

进入getEntry方法

    final Entry<K,V> getEntry(Object key) {
    
    
        if (size == 0) {
    
    //如果当前key-value 数量为0
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);//根据key计算出一个hash值
        for (Entry<K,V> e = table[indexFor(hash, table.length)];//计算出一个数组下标i,遍历table[i]为头节点的链表
             e != null;
             e = e.next) {
    
    
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))//如果当前结点hash等于要查找的hash且当前结点key与要查找key相等
                return e;//返回当前结点
        }
        return null;
    }

然后看一下remove方法

    public V remove(Object key) {
    
    
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);//返回删除的元素
    }

进入removeEntryForKey方法

    final Entry<K,V> removeEntryForKey(Object key) {
    
    
        if (size == 0) {
    
    //如果当前key-value 数量为0
            return null;
        }
        int hash = (key == null) ? 0 : hash(key);//根据key计算出一个hash值
        int i = indexFor(hash, table.length);//根据hash值和table长度计算出一个下标
        Entry<K,V> prev = table[i];//记录原数组
        Entry<K,V> e = prev;//记录要遍历的table[i]链表

        while (e != null) {
    
    //遍历链表
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
    
    //如果当前结点key==k要删除的key
                modCount++;
                size--;//key-value 数量-1
                if (prev == e)//如果要删除的结点==第一个结点
                    table[i] = next;//数组table[i]变为e的下一个结点
      
                else
                    prev.next = next;//比如有1 2 3,意思就是1直接指向3
                e.recordRemoval(this);
                return e;
            }
            prev = e;//当前prv为e
            e = next;//当前e为next
        }

        return e;//返回删除的结点
    }

猜你喜欢

转载自blog.csdn.net/rj2017211811/article/details/109167714
今日推荐