• HashMap源码分析jdk1.6


    HashMap数组每个元素的初始值为NULL 

    1、定义

    public interface Map<K,V> {
        int size();
        boolean isEmpty();
        boolean containsKey(Object key);
        boolean containsValue(Object value);
        V get(Object key);
        V put(K key, V value);
        V remove(Object key);
        void putAll(Map<? extends K, ? extends V> m);
        void clear();
        Set<K> keySet();
        Collection<V> values();
        Set<Map.Entry<K, V>> entrySet();
        interface Entry<K,V> {
           V getValue();
           V setValue(V value);
            boolean equals(Object o);
            int hashCode();
        }
        boolean equals(Object o);
        int hashCode();
    }

    hash是“散列”:hash就是通过散列算法,将一个任意长度关键字转换为一个固定长度的散列值,但是有一点要指出的是,不同的关键字可能会散列出相同的散列值

    2、HashMap类

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

    3.底层存储

    // 默认初始容量为16,必须为2的n次幂
        static final int DEFAULT_INITIAL_CAPACITY = 16;
    
        // 最大容量为2的30次方
        static final int MAXIMUM_CAPACITY = 1 << 30;
    
        // 默认加载因子为0.75f
        static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
        // Entry数组,长度必须为2的n次幂
        transient Entry[] table;
    
        // 已存储元素的数量
        transient int size ;
    
        // 下次扩容的临界值,size>=threshold就会扩容,threshold等于capacity*load factor
        int threshold;
    
        // 加载因子
        final float loadFactor ;

    Entry数组

    static class Entry<K,V> implements Map.Entry<K,V> {
            final K key ; 
            V value;
            Entry<K,V> next; // 指向下一个节点
            final int hash;
    
            Entry( int h, K k, V v, Entry<K,V> n) {
                value = v;
                next = n;
                key = k;
                hash = h;
            }
    
            public final K getKey() {
                return key ;
            }
    
            public final V getValue() {
                return value ;
            }
    
            public final V setValue(V newValue) {
               V oldValue = value;
                value = newValue;
                return oldValue;
            }
    
            public final boolean equals(Object o) {
                if (!(o instanceof Map.Entry))
                    return false;
                Map.Entry e = (Map.Entry)o;
                Object k1 = getKey();
                Object k2 = e.getKey();
                if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                    Object v1 = getValue();
                    Object v2 = e.getValue();
                    if (v1 == v2 || (v1 != null && v1.equals(v2)))
                        return true;
                }
                return false;
            }
    
            public final int hashCode() {
                return (key ==null   ? 0 : key.hashCode()) ^
                       ( value==null ? 0 : value.hashCode());
            }
    
            public final String toString() {
                return getKey() + "=" + getValue();
            }
    
            // 当向HashMap中添加元素的时候调用这个方法,这里没有实现是供子类回调用
            void recordAccess(HashMap<K,V> m) {
            }
    
            // 当从HashMap中删除元素的时候调动这个方法 ,这里没有实现是供子类回调用
            void recordRemoval(HashMap<K,V> m) {
            }
    }

    HashMap采用将相同的散列值存储到一个链表中,也就是说在一个链表中的元素他们的散列值绝对是相同的

    4.构造方法

    private void putAllForCreate(Map<? extends K, ? extends V> m) {
          for(Iterator<?extendsMap.Entry<?extendsK, ?extendsV>> i = m.entrySet().iterator(); i.hasNext(); ) {
                Map.Entry<? extends K, ? extends V> e = i.next();
                putForCreate(e.getKey(), e.getValue());
            }
        }
    
        /**
         * This method is used instead of put by constructors and
         * pseudoconstructors (clone, readObject).  It does not resize the table,
         * check for comodification, etc.  It calls createEntry rather than
         * addEntry.
         */
        private void putForCreate(K key, V value) {
            int hash = (key == null) ? 0 : hash(key.hashCode());
            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 != null && key.equals(k)))) {
                    e. value = value;
                    return;
                }
            }
    
            createEntry(hash, key, value, i);
        }
       
       void createEntry(int hash, K key, V value, int bucketIndex) {
           Entry<K,V> e = table[bucketIndex];
            table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
            size++;
    }

    5、增加

    public V put(K key, V value) {
            // 如果key为null,调用putForNullKey方法进行存储
            if (key == null)
                return putForNullKey(value);
            // 使用key的hashCode计算key对应的hash值
            int hash = hash(key.hashCode());
            // 通过key的hash值查找在数组中的index位置
            int i = indexFor(hash, table.length );
            // 取出数组index位置的链表,遍历链表找查看是有已经存在相同的key
            for (Entry<K,V> e = table [i]; e != null; e = e. next) {
                Object k;
                // 通过对比hash值、key判断是否已经存在相同的key
                if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                    // 如果存在,取出当前key对应的value,供返回
                    V oldValue = e. value;
                    // 用新value替换之旧的value
                    e. value = value;
                    e.recordAccess( this);
                    // 返回旧value,退出方法
                    return oldValue;
                }
            }
    
            // 如果不存在相同的key
            // 修改版本+1
            modCount++;
            // 在数组i位置处添加一个新的链表节点
            addEntry(hash, key, value, i);
            // 没有相同key的情况,返回null
            return null;
        }
    
        private V putForNullKey(V value) {
            // 取出数组第1个位置(下标等于0)的节点,如果存在则覆盖不存在则新增,和上面的put一样不多讲,
            for (Entry<K,V> e = table [0]; e != null; e = e. next) {
                if (e.key == null) {
                    V oldValue = e. value;
                    e. value = value;
                    e.recordAccess( this);
                    return oldValue;
                }
            }
            modCount++;
            // 如果key等于null,则hash值等于0
            addEntry(0, null, value, 0);
            return null;
    }

    hash函数

     length = 2^n  , m & (length-1) 相当于 m % length

    更加符合,Hash算法均匀分布的原则

    /**
         * Applies a supplemental hash function to a given hashCode, which
         * defends against poor quality hash functions.  This is critical
         * because HashMap uses power -of- two length hash tables, that
         * otherwise encounter collisions for hashCodes that do not differ
         * in lower bits. Note: Null keys always map to hash 0, thus index 0.
         */
        static int hash(int h) {
            // 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);
        }
    
        /**
         * Returns index for hash code h.
         */
        static int indexFor(int h, int length) {
            return h & (length-1);
    }
    addEntry函数
    /**
         * 增加一个k-v,hash组成的节点在数组内,同时可能会进行数组扩容。
         */
        void addEntry( int hash, K key, V value, int bucketIndex) {
            // 下面两行行代码的逻辑是,创建一个新节点放到单向链表的头部,旧节点向后移
            // 取出索引bucketIndex位置处的链表节点,如果节点不存在那就是null,也就是说当数组该位置处还不曾存放过节点的时候,这个地方就是null,
           Entry<K,V> e = table[bucketIndex];
           // 创建一个节点,并放置在数组的bucketIndex索引位置处,并让新的节点的next指向原来的节点
            table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
           // 如果当前HashMap中的元素已经到达了临界值,则将容量扩大2倍,并将size计数+1
            if (size ++ >= threshold)
                resize(2 * table.length );
    }
    
    

    新节点一直插入在最前端,新节点始终是单向列表的头节点。

    /**
         * 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.
         *
         * @param newCapacity the new capacity, MUST be a power of two;
         *        must be greater than current capacity unless current
         *        capacity is MAXIMUM_CAPACITY (in which case value
         *        is irrelevant).
         */
        void resize( int newCapacity) {
            // 当前数组
            Entry[] oldTable = table;
            // 当前数组容量
            int oldCapacity = oldTable.length ;
            // 如果当前数组已经是默认最大容量MAXIMUM_CAPACITY ,则将临界值改为Integer.MAX_VALUE 返回
            if (oldCapacity == MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return;
            }
    
            // 使用新的容量创建一个新的链表数组
            Entry[] newTable = new Entry[newCapacity];
            // 将当前数组中的元素都移动到新数组中
            transfer(newTable);
            // 将当前数组指向新创建的数组
            table = newTable;
            // 重新计算临界值
            threshold = (int)(newCapacity * loadFactor);
        }
    
        /**
         * Transfers all entries from current table to newTable.
         */
        void transfer(Entry[] newTable) {
            // 当前数组
            Entry[] src = table;
            // 新数组长度
            int newCapacity = newTable.length ;
            // 遍历当前数组的元素,重新计算每个元素所在数组位置
            for (int j = 0; j < src. length; j++) {
                // 取出数组中的链表第一个节点
                Entry<K,V> e = src[j];
                if (e != null) {
                    // 将旧链表位置置空
                    src[j] = null;
                    // 循环链表,挨个将每个节点插入到新的数组位置中
                    do {
                        // 取出链表中的当前节点的下一个节点
                        Entry<K,V> next = e. next;
                        // 重新计算该链表在数组中的索引位置
                        int i = indexFor(e. hash, newCapacity);
                        // 将下一个节点指向newTable[i]
                        e. next = newTable[i];
                        // 将当前节点放置在newTable[i]位置
                        newTable[i] = e;
                        // 下一次循环
                        e = next;
                    } while (e != null);
                }
            }
    }

    transfer方法中,由于数组的容量已经变大,也就导致hash算法indexFor已经发生变化,原先在一个链表中的元素,在新的hash下可能会产生不同的散列值,so所有元素都要重新计算后安顿一番

    hashmap的resize非常的低效

    6、删除

    /**
         * 根据key删除元素
         */
        public V remove(Object key) {
            Entry<K,V> e = removeEntryForKey(key);
            return (e == null ? null : e. value);
        }
    
        /**
         * 根据key删除链表节点
         */
        final Entry<K,V> removeEntryForKey(Object key) {
            // 计算key的hash值
            int hash = (key == null) ? 0 : hash(key.hashCode());
            // 根据hash值计算key在数组的索引位置
            int i = indexFor(hash, table.length );
            // 找到该索引出的第一个节点
            Entry<K,V> prev = table[i];
            Entry<K,V> e = prev;
    
            // 遍历链表(从链表第一个节点开始next),找出相同的key,
            while (e != null) {
                Entry<K,V> next = e. next;
                Object k;
                // 如果hash值和key都相等,则认为相等
                if (e.hash == hash &&
                    ((k = e. key) == key || (key != null && key.equals(k)))) {
                    // 修改版本+1
                    modCount++;
                    // 计数器减1
                    size--;
                    // 如果第一个就是要删除的节点(第一个节点没有上一个节点,所以要分开判断)
                    if (prev == e)
                        // 则将下一个节点放到table[i]位置(要删除的节点被覆盖)
                        table[i] = next;
                    else
                     // 否则将上一个节点的next指向当要删除节点下一个(要删除节点被忽略,没有指向了)
                        prev. next = next;
                    e.recordRemoval( this);
                    // 返回删除的节点内容
                    return e;
                }
                // 保存当前节点为下次循环的上一个节点
                prev = e;
                // 下次循环
                e = next;
            }
    
            return e;
    }

    8、查找

    public V get(Object key) {
            // 如果key等于null,则调通getForNullKey方法
            if (key == null)
                return getForNullKey();
            // 计算key对应的hash值
            int hash = hash(key.hashCode());
            // 通过hash值找到key对应数组的索引位置,遍历该数组位置的链表
            for (Entry<K,V> e = table [indexFor (hash, table .length)];
                 e != null;
                 e = e. next) {
                Object k;
                // 如果hash值和key都相等,则认为相等
                if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                    // 返回value
                    return e.value ;
            }
            return null;
        }
    
        private V getForNullKey() {
            // 遍历数组第一个位置处的链表
            for (Entry<K,V> e = table [0]; e != null; e = e. next) {
                if (e.key == null)
                    return e.value ;
            }
            return null;
    }

    9、是否包含

    代码和get几乎一样

    10、容量

    /**
         * Returns the number of key -value mappings in this map.
         *
         * @return the number of key- value mappings in this map
         */
        public int size() {
            return size ;
        }
    
        /**
         * Returns <tt>true</tt> if this map contains no key -value mappings.
         *
         * @return <tt> true</tt> if this map contains no key -value mappings
         */
        public boolean isEmpty() {
            return size == 0;
    }
  • 相关阅读:
    "动作面板"组件:<action-sheet> —— 快应用组件库H-UI
    "浮动弹层"组件:<float-layout> —— 快应用组件库H-UI
    "幕帘"组件:<curtain> —— 快应用组件库H-UI
    "轻提示"组件:<toast> —— 快应用组件库H-UI
    "模态框"组件:<modal> —— 快应用组件库H-UI
    "同一行代码片段"组件:<code> —— 快应用组件库H-UI
    "电脑程序输出"组件:<samp> —— 快应用组件库H-UI
    "多行代码"组件:<pre> —— 快应用组件库H-UI
    "按键提示"组件:<kbd> —— 快应用组件库H-UI
    "变量赋值"组件:<var> —— 快应用组件库H-UI
  • 原文地址:https://www.cnblogs.com/L-a-u-r-a/p/8520002.html
Copyright © 2020-2023  润新知