JAVA集合:HashMap深度解析(版本对比)

栏目: Java · 发布时间: 8年前

内容简介:JAVA集合:HashMap深度解析(版本对比)

关于 JAVA 的集合框架近期也会逐渐同步跟新出来,欢迎小伙伴们多提意见

今天先为JAVA集合系列源码开一个头,也尝试着用不同的方式,不同的角度去阅读源码,去学习源码中的一些思想。HashMap作为最常使用的集合之一;JDK1.7之前,有很大的争议,一方面是数据量变大之后的查询效率问题,还有就是线程安全问题。本文将从JDK1.7和1.8两个不同版本的源码入手,去探寻一下HashMap是如何被优化的,以及线程安全问题的出现情况。

jdk1.7

HashMap在1.7中和1.6的主要区别:

  • 加入了jdk.map.althashing.threshold这个jdk的参数用来控制是否在扩容时使用String类型的新hash算法。
  • 把1.6的构造方法中对表的初始化挪到了put方法中。
  • 1.6中的tranfer方法对旧表的节点进行置null操作(存在多线程问题),1.7中去掉了。

定义

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

HashMap继承自AbstractMap,实现了Map,Cloneable,和Serializable。既然实现了Serializable接口,也就是说可以实现序列化,在下面的成员变量介绍中可以看到,table[]使用了transient来修饰的,这个对于大多数集合框架中的类来说都有这种机制。查阅了相关资料和结合网上各路大神的解释,这里总结一下:

  • 减少不必要的空值序列化

    table 以及 elementData中存储的数据的数量通常情况下是要小于数组长度的(扩容机制),这个在数据越来越多的情况下更为明显(数据变多,伴随着冲突概率变大,同时也伴随着扩容)。如果使用默认的序列化,那些没有数据的位置也会被存储,就会产生很多不必要的浪费。

  • 不同虚拟机的兼容问题

    由于不同的虚拟机对于相同hashCode产生的Code值可能是不一样的,如果使用默认的序列化,那么反序列化后,元素的位置和之前的是保持一致的,可是由于hashCode的值不一样了,那么定位到的桶的下标就会不同,这很明显不是我们想看到的

所在HashMap的序列化 并没有使用默认的序列化方法,而采用自定义的序列化方法,通过重写writeObject方法来完成。

成员变量

  • 默认初始容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
  • 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
  • 默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • 空表实例
static final Entry<?,?>[] EMPTY_TABLE = {};
  • table,一个Entry数组
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
  • size,map中key-value的总数
transient int size;
  • 阈值,当map中key-value的总数达到这个值时,进行扩容
int threshold;
  • 负载因子
final float loadFactor;
  • 被修改的次数(fial-fast机制)
transient int modCount;
  • alternative hashing(如果使用了String类型的一种新hash算法)的默认阈值
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
  • 控制是否重新hash,默认为0,后面会详细说明
transient int hashSeed = 0;
  • 内部类,VM启动之后初始化
jdk.map.althashing.threshold

JDK中的参数,默认-1,如果设置为1,则强制使用String类型的新hash算法

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"));

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

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

                if (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;
        }
    }

内部结构

JAVA集合:HashMap深度解析(版本对比)

从上图可以看出,HashMap是基于数组+链表的方式实现的。来看Entry这个内部类:

  • Entry,4个属性(key,value,next节点,hash值)
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        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();
        }

        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that's already
         * in the HashMap.
         */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }

构造函数

  • 无参构造函数,默认初始容量16,负载因子0.75
public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
  • 一个参数构造函数,默认负载因子0.75
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
  • 两个参数构造函数,设置容量和负载因子
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();
}
  • init方法,模板方法,如果有子类需要扩展可以自行实现
void init() {
}

主要方法

  • hash方法
final int hash(Object k) {
    int h = hashSeed;
    //默认0,如果不是0,并且key是String类型,才使用新的hash算法(避免碰
    //撞的优化?)
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }
    h ^= k.hashCode();
    //把高位的值移到低位参与运算,使高位值的变化会影响到hash结果
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
  • 根据hash值确定在table中的位置,length为2的倍数 HashMap的扩容是基于2的倍数来扩容的,从这里可以看出,对于indexFor方法而言,其具体实现就是通过一个计算出来的code值和数组长度-1做位运算,那么对于2^N来说,长度减一转换成二进制之后就是全一(长度16,len-1=15,二进制就是1111),所以这种设定的好处就是说,对于计算出来的code值得每一位都会影响到我们索引位置的确定,其目的就是为了能让数据更好的散列到不同的桶中。
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);
}

put方法

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {  
    //如果表没有初始化,则以阈值threshold的容量初始化表
        inflateTable(threshold);
    }
    if (key == null)   
    //如果key值为null,调用putForNullKey方法,所以hashmap可以插入key和value为null的值
        return putForNullKey(value);
    //计算key的hash值
    int hash = hash(key); 
    //计算hash值对应表的位置,即表下标
    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))) {
        //如果hash值相等并且(key值相等或者key的equals方法相等),
        //则覆盖,返回旧的value
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    //修改字数+1
    modCount++; 
    //如果没找到key没找到,则插入
    addEntry(hash, key, value, i);  
    return null;
}
  • 初始化表方法, 表容量必须是2的倍数 (roundUpToPowerOf2)
private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);

    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}
  • 获取大于或等于给定值的最小的2的倍数
private static int roundUpToPowerOf2(int number) {
    // assert number >= 0 : "number must be non-negative";
    return number >= MAXIMUM_CAPACITY
            ? MAXIMUM_CAPACITY
            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
  • highestOneBit:返回小于给定值的最大的2的倍数
public static int highestOneBit(int i) {
    // HD, Figure 3-1
    i |= (i >>  1); //其余位不管,把最高位的1覆盖到第二位,使前2位都是1
    i |= (i >>  2); //同样的,把第3、4位置1,使前4位都是1
    i |= (i >>  4); //...
    i |= (i >>  8); //...
    i |= (i >> 16); //最高位以及低位都是1
    return i - (i >>> 1);   //返回最高位为1,其余位全为0的值
}
  • initHashSeedAsNeeded方法控制transfer扩容时是否重新hash
final boolean initHashSeedAsNeeded(int capacity) {
        //hashSeed默认0,currentAltHashing为false
        boolean currentAltHashing = hashSeed != 0;
        //参照上面的Holder类的静态块,jdk.map.althashing.threshold默认-1,Holder.ALTERNATIVE_HASHING_THRESHOLD为Integer.MAX_VALUE,如果jdk.map.althashing.threshold设置了其他非负数,可以改变Holder.ALTERNATIVE_HASHING_THRESHOLD的值,如果不超过Integer.MAX_VALUE,则useAltHashing为true
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {    //改变hashSeed的值,使hashSeed!=0,rehash时String类型会使用新hash算法
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }
  • HashMap把key为null的key-value键值对放入table[0]中
private V putForNullKey(V value) {
        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++;
        addEntry(0, null, value, 0);
        return null;
    }
  • 插入新的key-value键值对
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;   //null的hash值为0
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
  • 头插法,即把新的Entry插入到table[bucketIndex]的链表头位置

    关于头插法的解释:一般情况下会默认后插入的数据被查询的频次会高一点。

void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

get方法

public V get(Object key) {
    if (key == null)
        return getForNullKey(); //如果key为null,直接去table[0]中找
    Entry<K,V> entry = getEntry(key);

    return null == entry ? null : entry.getValue();
}
private V getForNullKey() {
    if (size == 0) {
        return null;
    }
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null)
            return e.value;
    }
    return null;
}
  • getEntry方法比较简单,先找hash值在表中的位置,再循环链表查找Entry,如果存在,返回Entry,否则返回null
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;
}
  • remove方法
public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);
    return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
    if (size == 0) {
        return null;
    }
    int hash = (key == null) ? 0 : hash(key);
    int i = indexFor(hash, table.length);
    Entry<K,V> prev = table[i];    //前一个节点
    Entry<K,V> e = prev;    //当前节点

    while (e != null) {
        Entry<K,V> next = e.next;   //下一个节点
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e)  //如果相等,说明需要删除的是头节点,头节点直接等于next
                table[i] = next;
            else
                prev.next = next;   //如果不是头节点,前一个的next等于下一个节点,删除当前节点
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }

    return e;
}

resize方法,扩容(重点)

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {  //如果容量已经达到MAXIMUM_CAPACITY,不扩容
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];
    //initHashSeedAsNeeded方法决定是否重新计算String类型的hash值
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
  • transfer方法,把旧表的所有节点转移到新表中
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);
            }
            /**
              *重新计算hash值在新表中的位置(旧表中一条链表中的数据
              *最多会分成两条存在新表中,即oldTable[index]中的节点会存到
              *newTable[index]和newTable[index+oldTable.length]中)
              */
            int i = indexFor(e.hash, newCapacity);  
            //头插法插入到新表中
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

jdk1.8

1.8的HashMap相比于1.7有了很多变化

  • Entry结构变成了Node结构, hash变量加上了final声明 ,即不可以进行rehash了
  • 插入节点的方式从 头插法 变成了 尾插法
  • 引入了 红黑树
  • tableSizeFor方法、hash算法等等

定义

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

成员变量

  • 默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
  • 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
  • 默认负载因子 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • 链表转红黑树的阈值 8
static final int TREEIFY_THRESHOLD = 8;
  • 红黑树转链表的阈值 6
static final int UNTREEIFY_THRESHOLD = 6;
  • 链表转红黑树所需要的最小表容量64,即当链表的长度达到转红黑树的临界值8的时候,如果表容量小于64,此时并不会把链表转成红黑树,而会对表进行扩容操作,减小链表的长度
static final int MIN_TREEIFY_CAPACITY = 64;
  • table,Node数组
transient Node<K,V>[] table;
/**
 * Holds cached entrySet(). Note that AbstractMap fields are used
 * for keySet() and values().
 */
transient Set<Map.Entry<K,V>> entrySet;
  • 节点总数
transient int size;
  • 修改次数
transient int modCount;
  • 扩容阈值
int threshold;
  • 负载因子
final float loadFactor;

结构

  • Node结构,实现了Entry,hash值声明为final,不再可变,即1.7中的rehash操作不存在了
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

构造函数

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}
  • 参数为Map的构造方法,先计算需要的容量大小,然后调用putVal方法插入节点
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) { // pre-size
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)
            resize();
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}
  • tableSizeFor方法,给定初始化表容量值,返回表的实际初始化容量(必须是2的倍数),这个方法相比与1.7有了优化,更简洁。
static final int tableSizeFor(int cap) {
    int n = cap - 1;    //先进行-1操作,当cap已经是2的倍数时,最后+1,返回该数本身
    n |= n >>> 1;   //右移1位,再进行或操作,然后赋值给n,使最高位的1的下一位也变成1
    n |= n >>> 2;   //右移2位,使最高2位的1右移覆盖后2位的值,即最高4位均为1
    n |= n >>> 4;   //右移4位...
    n |= n >>> 8;   //右移8位...
    n |= n >>> 16;  //右移16位...
    //如果cap<=0,返回1,如果>MAXIMUM_CAPACITY,返回MAXIMUM_CAPACITY,否则,最后的n+1操作返回大于等于cap的最小的2的倍数
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

主要方法

  • hash算法进行了简化,直接把高16位移下来进行或运算
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

put方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
/**
 * Implements Map.put and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * onlyIfAbsent 如果是true,不存在才插入,存在则不改变原有的值
 * @param onlyIfAbsent if true, don't change existing value
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        //如果table为null或者length为0,调用resize扩容方法(没有单独的///初始化方法了)
        n = (tab = resize()).length;
        //i = (n - 1) & //hash]计算hash值对应表中的位置,如果链表头为null,直接插入
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //如果key存在,赋值给e,后面统一判断是否插入
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //如果是树节点,调用putTreeVal方法
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {//循环tab[i = (n - 1) & //hash]上的链表,binCount记录链表的长度,用来判断是否转化为树结//构
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {//如果key没找到,直接插入
                    p.next = newNode(hash, key, value, null);
                    // -1 for 1st,如果长度达到了8,就转化为树结构
                    if (binCount >= TREEIFY_THRESHOLD - 1) 
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
      
        if (e != null) { 
        // key存在,如果onlyIfAbsent为false,替换value,如果onlyIfAbsen//t 为true,原有值为null,也会替换,否则不变更原有值
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e); //LinkedHashMap重写使用
            return oldValue;
        }
    }
    ++modCount; //修改次数+1
    if (++size > threshold) //如果size达到了扩容的阈值,则进行扩容操作
        resize();
    afterNodeInsertion(evict);  //LinkedHashMap重写用的
    return null;
}

get方法

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
  • getNode方法很简单,(n - 1) & hash计算key值对应的table下标,找到链表,先判断头节点,然后循环查找,如果头节点是树节点,调用树节点的getTreeNode方法
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // 先判断第一个节点
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

remove方法

public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}
/**
 * Implements Map.remove and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to match if matchValue, else ignored
 * @param matchValue if true only remove if value is equal //如果是true,value也要相等
 * @param movable if false do not move other nodes while removing
 * @return the node, or null if none
 */
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {//找到对应的头节点
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))//先判断头节点
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode)  //如果是树,调用树的getTreeNode方法
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {  //循环链表
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) { //matchValue为true时,value也要相等才删除节点
            if (node instanceof TreeNode)   //树节点的删除
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p) //如果是头节点,把头节点的下个节点赋值给头节点
                tab[index] = node.next;
            else    //把当前节点的next节点赋值给上一个节点的next(删除当前节点)
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node); //空方法,LinkedHashMap重写用
            return node;
        }
    }
    return null;
}

resize方法(扩容)

/**
 * Initializes or doubles table size.  If null, allocates in
 * accord with initial capacity target held in field threshold.
 * Otherwise, because we are using power-of-two expansion, the
 * elements from each bin must either stay at same index, or move
 * with a power of two offset in the new table.
 *
 * @return the table
 */
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {   //如果旧表已经初始化过了
        if (oldCap >= MAXIMUM_CAPACITY) {   //达到上限,不再扩容
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //如果容量大于等于16,并且*2小于上限,扩容2倍,新表容量=旧表*2,新阈值=旧阈值*2
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // 初始化表,有参构造函数中把需要初始化的容量赋值给了threshold
        newCap = oldThr;
    else {               // 如果没有给定容量,默认初始化16,阈值16*0.75=12
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    /**
      * 如果旧表里有值,需要把旧表里的值重新计算放到新表里
      * hash & (oldCap*2-1)计算新表中的位置,只可能得到两种结果(把新表分成两个小表)
      * hash & (oldCap-1) 放在前面的表里 和 hash & (oldCap-1) + oldCap 放在后面的表里
      * hash & oldCap == 0 就是第一种结果, !=0 就是第二种结果
      */
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)  //头节点是null,直接赋值
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode) //树节点处理
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {    //循环链表
                        next = e.next;
                        if ((e.hash & oldCap) == 0) { //分配到前面表里的放在一个链表里
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {  //分配到后面表里的放在一个链表里
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {   //放到新表里
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

多线程安全问题

HashMap不是线程安全的

  • JDK1.7中 ,当两个线程同时进行插入操作时,同时执行到createEntry方法时,获取到了同一个头节点e,第二个线程会覆盖掉第一个线程的插入操作,使第一个线程插入的数据丢失。 JDK1.8中的尾插法 同样会有这样的问题,两个线程获取到相同的节点,然后把新键值对赋值给这个节点的next,后面的赋值操作覆盖掉前面的。
  • JDK1.7和JDK1.8中 对map进行扩容时,由于节点的next会变化,造成实际有key值,但是读操作返回null的情况。
  • 1.7中,当两个线程同时进行扩容操作时,可能会造成 链表的死循环 ,形成过程:
  1. 现在有个map:
    JAVA集合:HashMap深度解析(版本对比)
  2. 线程1进行扩容操作,执行transfer方法,赋值完节点e和next之后阻塞了。
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;
  1. 线程2进行扩容操作并完成了扩容,创建了newTable2。
    JAVA集合:HashMap深度解析(版本对比)
  2. 此时,节点e和next的连接情况如上图所示,线程1如果继续执行,执行过程如下:
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
JAVA集合:HashMap深度解析(版本对比)
e = next;
Entry<K,V> next = e.next;
JAVA集合:HashMap深度解析(版本对比)
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
```java
![](https://user-gold-cdn.xitu.io/2018/1/20/1611330db7c085de?w=668&h=444&f=png&s=149071)
```java
e = next;
Entry<K,V> next = e.next;
JAVA集合:HashMap深度解析(版本对比)
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
JAVA集合:HashMap深度解析(版本对比)
e = next;
  1. 此时链表形成了死循环。
  • 1.8中的transfer方法有了变化,不会造成死循环。

总结

  • HashMap的结构,主要方法
  • 1.7和1.8的区别
  • 关于红黑树部分后面补充

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Haskell School of Expression

The Haskell School of Expression

Paul Hudak / Cambridge University Press / 2000-01 / USD 95.00

Functional programming is a style of programming that emphasizes the use of functions (in contrast to object-oriented programming, which emphasizes the use of objects). It has become popular in recen......一起来看看 《The Haskell School of Expression》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具