hashMap表面源码分析

hashMap底层主要使用了数组+单向链表的数据接口(jdk1.8当链表过长的时候使用了树的结构)

初始化:赋值数组的长度16,和加载因子值0.75f,并没有生成数组。

// 提供四个初始化方法
public HashMap(int initialCapacity, float loadFactor)
public HashMap(int initialCapacity)
public HashMap()
public HashMap(Map map)

static final Entry<,>[] EMPTY_TABLE = {};

transient Entry<,>[] table = (Entry<,V>[]) EMPTY_TABLE;  // 内部数组,初始化在第一次put时

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;  // 静态数组长度
static final float DEFAULT_LOAD_FACTOR = 0.75f;  // 静态加载因子

int threshold;     // 数组长度默认为16,在第一次put后变为12
final float loadFactor;     // 加载因子0.75f

public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

// 默认:initialCapacity=16      loadFactor=0.75
public HashMap(int initialCapacity, float loadFactor) {
    // 是否小于0
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                          initialCapacity);
    // 是否大于最大值 1<<30 = 2^30
    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;
    // 模板方法,实际为null,留给子类实现空间
    init();
}

// put操作数组为null时初始化     toSize 16
private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    // 选取一个2的整数幂次数。 例如:15 —》16  17 —》32
    int capacity = roundUpToPowerOf2(toSize);
    // threshold = 12
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}

get操作

public V get(Object key) {
    // 取table[0]链表,对key==null的时候进行特殊处理,把entry放在0处
    if (key == null)
        return getForNullKey();
    // key非空时
    Entry<K,V> entry = getEntry(key);

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

private V getForNullKey() {
    // 边界控制,当数组长度为0时,返回null
    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;
}

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

    // 计算hash值
    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;
}

final int hash(Object k) {
    int h = hashSeed;
    // 对String类,进行特殊处理
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

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) {
        inflateTable(threshold);
    }
    // key为null放在table[0]位置
    if (key == null)
        return putForNullKey(value);
    // 计算key的hash值
    int hash = hash(key);
    // 通过与运算计算在表中的位置
    int i = indexFor(hash, table.length);
    // 判断在链表中是否存在,并不是添加到链表中。如果存在替换并返回老值
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        // 1先比较hash值,2比较key值
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            // 模板模式,子类扩展
            e.recordAccess(this);
            return oldValue;
        }
    }
    // modCount值加1,不能在迭代器中使用list.remove方法,会导致modCount值不一致,抛出异常
    modCount++;
    // 添加entry。1.链表存在,添加在头处 2.链表不存在,新建节点
    addEntry(hash, key, value, i);
    return null;
}

void addEntry(int hash, K key, V value, int bucketIndex) {
    // 超出负载因子乘积就扩容2倍
    if ((size >= threshold) && (null != table[bucketIndex])) {
        // 重hash赋值
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    // 创建新entry
    createEntry(hash, key, value, bucketIndex);
}

1,初始化时都做了什么事,什么时候才创建数组?
初始化时默认设置了threshold为16和loadFactor为0.75f。
在第一次put的时候才真正的创建数组长度16,并赋值threshold为12。当实际存储的链表个数超过12时开始扩容。

2,加载因子的作用?
加载因子是hashMap时间和空间的比值。加载因子越大,扩容越慢,链表长度越长,查询慢。
加载因子越小,扩容越快,链表越短,查询快。
所以没有特殊要求还是使用默认值0.75较好。

2,为什么数组大小和扩容时要控制在2的整数倍?

// Find a power of 2 >= toSize     初始化时
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;
}

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

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

// 寻找在表中的位置
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的整数倍,扩容时也是扩容2倍,而不像ArrayList扩容时为1.5倍。
其原因在于数组长度为2的倍数时,散列会平均,效率变高 h & (length -1)会比去模效率高,length为奇数时,
length - 1末尾为0,h & 末尾为0的数时,最后一位永远是0,hash只能散列到偶数的位置,浪费空间。

3,hashMap插入时,是头插还是尾插?
头插。在插入的时候,新建了entry,把next结点指向头结点,如果是尾插的话,需要循环到尾位置,多操作。
同理可知,在扩容的时候会导致链表反转。

public V put(K key, V value) {
    ..
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    ..

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

void createEntry(int hash, K key, V value, int bucketIndex) {
    // i位置的链表head
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}
// hash  key  value  e(i位置的链表head)
Entry(int h, K k, V v, Entry<K,V> n) {
    value = v;
    next = n;     // 把链表头结点赋给当前的next
    key = k;
    hash = h;
}

4,hashMap多线程使用时发生死循环,为什么?

可通过源码分析。

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            // 1、记录next节点
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            // 2、当前节点头插到新数组
            e.next = newTable[i];
            newTable[i] = e;
            // 3、将老数组向下遍历
            e = next;
        }
    }
}

正常扩容过程
1、记录next节点
2、将当前节点头插进新数组
3、将老数组向下遍历
这里写图片描述

并发行程死循环:当一个线程1已经完成扩容,线程2扩容到2节点时
正常扩容步骤:1、线程2记录2的next。因为线程1已完成扩容,把链表倒置,2的next节点为1。
2、将2节点头插。新数组 2->1
3、将1赋值给当前节点。由此形成死循环

这里写图片描述

参考来自:http://ifeve.com/hashmap-infinite-loop/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值