hashmap源码学习 jdk1.7.0_80

前言

jdk1.7的HashMap源码学习笔记,分析主要写在注释里,写的比较啰嗦,大佬们看看就好

存储结构

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    int hash;
    ……
}

总体来说就是数组+链表

构造函数

有四个,但最终都是调的这个

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();  //空方法,作用是为子类提供扩展的可能性,实际上LinkedHashMap中覆盖了这个方法
}

第一个参数是初始容量,第二个是加载因子,threshold是一个阈值,map长度达到这个值后就会扩容,但在此时threshold中暂存的是初始容量

看下其中Float.isNaN这个方法:

static public boolean isNaN(float v) {
    return (v != v);
}

这里的方法参数类型为基本类型float,当传入例如 0.0f / 0.0f的值时,jvm在处理浮点数运算时,不会抛出任何运行时异常,当一个操作产生溢出时,将会使用NaN值来表示,NaN是唯一与自己不相等的值,这里要区别于Float类型(比较的是引用)

另外看一下参数类型为map的构造方法,类似于clone,后面会分析

public HashMap(Map<? extends K, ? extends V> m) {
    //保证初始容量*加载因子要大于map长度
    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                  DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
    inflateTable(threshold);

    putAllForCreate(m);
}

扩容方法

一、空map扩容

//扩充容量,threshold从初始容量修改为阈值,实例化table,初始化hashseed
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);
}

//取大于等于number且最接近的2^n
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;
}
/**
 * 取二进制形式左边的最高一位后面全部补零的值
 * 如果是0,返回0;如果是负数,则返回-2^31;如果是正数,返回小于 * 等于i且最接近的2^n)
 */
public static int highestOneBit(int i) {
    // HD, Figure 3-1
    i |= (i >>  1);  //最高1位右边1位变为1
    i |= (i >>  2);  //最高2位右边2位变为1
    i |= (i >>  4);  //最高4位右边4位变为1
    i |= (i >>  8);  //最高8位右边8位变为1
    i |= (i >> 16);  //最高16位右边16位变为1
    return i - (i >>> 1);  //去掉最高位右边的所有1
}

//初始化hashseed,默认为0,异或用的很巧妙
final boolean initHashSeedAsNeeded(int capacity) {
    boolean currentAltHashing = hashSeed != 0;
    boolean useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    boolean switching = currentAltHashing ^ useAltHashing;
    if (switching) {
        hashSeed = useAltHashing
            ? sun.misc.Hashing.randomHashSeed(this)
            : 0;
    }
    return switching;
}

二、非空map扩容

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

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;
            //扩容后达到阈值(非threshold)则重新计算hash
            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;
        }
    }
}

hash()方法

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

先看寻找数组下标的方法,length为2^n时,h & (length-1)就是h对length取模,位运算比模运算效率高得多,而且可以很好的解决负数的问题。这也是hashmap容量一定要是2的幂次方的原因。

X % 2^n = X & (2^n – 1)
假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 = 7 ,即0111。
此时X & (2^3 – 1) 就相当于取X的2进制的最后三位数。
从2进制角度来看,X / 8相当于 X >> 3,即把X右移3位,此时得到了X / 8的商,而被移掉的部分(后三位),则是X % 8,也就是余数。
final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // 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);
}

这段代码是为了对key的hashCode进行扰动计算,防止不同hashCode的高位不同但低位相同导致的hash冲突。简单点说,就是为了把高位的特征和低位的特征组合起来,降低哈希冲突的概率,也就是说,尽量做到任何一位的变化都能对最终得到的结果产生影响。具体原理这里不做讨论。

插入方法

需要结合前面非空map扩容方法阅读

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);
    //判断key是否已存在,有需要时可以重写K类型的equals方法
    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;
        }
    }
    /** 
     * 修改次数,与线程安全相关,只在迭代器中使用,
     * 在使用迭代器的过程中有其他线程修改了map,将抛出异常
     */
    addEntry(hash, key, value, i);
    modCount++; 
    return null;
}

//key为null时hash为0,所以存到talbe[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;
}

void addEntry(int hash, K key, V value, int bucketIndex) {
    //长度达到阈值且数组当前位置已有值时扩容成2倍
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

//同样是头插法,Entry构造函数里实现
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++;
}

clone方法和拷贝构造函数都会调用以下方法,都为浅拷贝

private void putAllForCreate(Map<? extends K, ? extends V> m) {
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        putForCreate(e.getKey(), e.getValue());
}

private void putForCreate(K key, V value) {
    int hash = null == key ? 0 : hash(key);
    int i = indexFor(hash, table.length);

    /**
     * Look for preexisting entry for key.  This will never happen for
     * clone or deserialize.  It will only happen for construction if the
     * input Map is a sorted map whose ordering is inconsistent w/ equals.
     */
    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);
}

查找方法

查找相比插入来说比较简单

public V get(Object key) {
    if (key == null)
        return getForNullKey(); //和put类似,从table[0]中查找
    Entry<K,V> entry = getEntry(key);

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

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

    int hash = (key == null) ? 0 : hash(key);
    //根据hash找到数组下标并遍历链表
    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;
}

删除方法

这个一般用的不多

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)
                table[i] = next;
            else
                prev.next = next;
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }

    return e;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值