HashMap1.8源码梳理

本文涵盖内容

  1. HashMap 方法源码解析(构造方法、增删改查)
  2. HashMap特点、适用场景
  3. 常见面试题

特点,适用场景

  1. 线程不安全的
  2. 存储结构是 数组 + 链表 + 红黑树(jdk1.8)
  3. 允许key、value为null
  4. 无序

源码解析

1. 构造方法
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;
	// 初始化阀值 ,添加的Entry超过该阀值 ,就进行扩容操作
    this.threshold = tableSizeFor(initialCapacity);
}
2.1 增put(K key, V value)
public V put(K key, V value) {
	// hash(key) 详见2.2
    return this.putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K, V>[] tab;
    Node<K, V> p;
    int n;
    int i;
    if ((tab = this.table) == null || (n = tab.length) == 0) {
		// 如果第一次进行put,进行扩容获取初始化table操作,详见2.3
        n = (tab = this.resize()).length;
    }
    if ((p = tab[i = (n - 1) & hash]) == null) {
		// 如果该下标下没有数据择直接进行存储
        tab[i] = this.newNode(hash, key, value, null);
    } else {
        Node<K, V> e;
        K k;
		// 如果i下标的Entry和要存储的entry相同就覆盖
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) {
            e = p;
        } else if (p instanceof TreeNode) {
			// 红黑树存储
            e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
        } else {
			// 遍历链表
            for (int binCount = 0; ; ++binCount) {
				// 无限循环到链表尾部
                if ((e = p.next) == null) {
					// 把node 插入到链表尾部
                    p.next = this.newNode(hash, key, value, null);
					// 如果链表节点数量>8个就改用红黑树结构
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                    {
                        this.treeifyBin(tab, hash);
                    }
                    break;
                }
				// 如果在链表中找到相同的Entry就覆盖
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) {
                    break;
                }
                p = e;
            }
        }
		// 执行覆盖操作 , 讲新的value赋值给同key的Entry
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null) {
                e.value = value;
            }
            this.afterNodeAccess(e);
            return oldValue;
        }
    }
    ++this.modCount;
	// 如果数量超过了阀值,就扩容
    if (++this.size > this.threshold) {
        this.resize();
    }
    this.afterNodeInsertion(evict);
    return null;
}
2.2 hash(key)
 static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2.3 resize()
final Node<K, V>[] resize() {
    Node<K, V>[] oldTab = this.table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = this.threshold;
    int newCap;
    int newThr = 0;
	// if 当前数组长度>0进行扩容 else 使用默认容量值 初始化新数组 
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            this.threshold = Integer.MAX_VALUE;
            return oldTab;
		// oldcap * 2 扩容
        } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                oldCap >= DEFAULT_INITIAL_CAPACITY) {
            newThr = oldThr << 1; // double threshold
        }
    } else if (oldThr > 0) // initial capacity was placed in threshold
    {
        newCap = oldThr;
    } else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float) newCap * this.loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
                (int) ft : Integer.MAX_VALUE);
    }
    this.threshold = newThr;
	// 初始化长度为newcap的新数组
    @SuppressWarnings({"rawtypes", "unchecked"}) Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
    this.table = newTab;
	// if 初始化不进入代码段 else 进入代码段进行扩容操作
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K, V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
				// 如果该下标只有一个Entry设置该值即可
                if (e.next == null) {
                    newTab[e.hash & (newCap - 1)] = e;
                } else if (e instanceof TreeNode) {
					// 如果链表长度超过8个会是一个红黑树node
                    ((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;
						// 根据e.hash & oldCap) == 0 为标志把数组桶中的链表分成两个链表
					   	// ==0 存储在原有下标newtable[j]下 !=0 存储在newtable[j+oldtable.length]下
                        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;
}
3. 删除 remove(Object key)
 public V remove(Object key) {
    Node<K, V> e;
    return (e = this.removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
}

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;
    int index;
	// 只有当前数组!=null && table.length>0 && p = 数组index下标中第一个Entry != null 才能进行删除操作
    if ((tab = this.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) {
			// 如果存在链表 , 检测next是否是红黑树node 赋值
            if (p instanceof TreeNode) {
                node = ((TreeNode<K, V>) p).getTreeNode(hash, key);
            } else {
			// 如果是普通单向链表 ,遍历链表找到该node 赋值
                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)))) {
            if (node instanceof TreeNode) {
				// 红黑树删除
                ((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
            } else if (node == p) {
				// 链表头部删除
                tab[index] = node.next;
            } else {
				// 链表删除
                p.next = node.next;
            }
            ++this.modCount;
            --this.size;
            this.afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}
4.1 查get(Object key)
public V get(Object key) {
    Node<K, V> e;
    return (e = this.getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K, V> getNode(int hash, Object key) {
    Node<K, V>[] tab;
    Node<K, V> first;
    Node<K, V> e;
    int n;
    K k;
    if ((tab = this.table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {

		// 检测数组下标对应数据是否是所需数据
        if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k)))) {
            return first;
        }
        if ((e = first.next) != null) {
			// 如果是红黑树node 进行红黑树获取过程
            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;
}
4.2 4种遍历查询方式
	Iterator i = map.entrySet().iterator();
	while (i.hasNext()) {
		Entry<String, String> e = (Entry<String, String>) i.next();
		System.out.println(e.getKey() + "\t" + e.getValue());
	}

	Set<String> set = map.keySet();
	for (String key : set) {
		System.out.println(key + "\t" + map.get(key));
	}

	for (Map.Entry<String, String> e : map.entrySet()) {
		System.out.println(e.getKey() + "\t" + e.getValue());
	}

	for (String v : map.values()) {
		System.out.println("value= " + v);
	}

与HashTable区别

  1. HashTable 线程安全,put get使用synchronized来进行同步
  2. 默认长度是11
  3. 数组扩容是原有数组长度 * 2 +1 ,扩容后遍历数组重新hash并计算index重新存储
  4. 不允许value为null

与ConcurrentHashMap区别

  1. ConcurrentHashMap线程安全 , key和value都不允许为null

常见面试题

1. HashMap put流程解析
  1. 求key的hash值
  2. 如果table == null 或者table.length == 0 说明还没有初始化table , 进行table初始化操作
  3. 根据table.length-1 & hash 得到数组下标index
  4. 如果数组下标index中数据==null table[index] = node
  5. 如果和下标inde中链表/红黑树中第一个值相同覆盖
  6. 如果node 是TreeNode(红黑树类型)就让红黑树管理
  7. 如果是链表 , 就循环把该node插入到表位 , 在循环过程中如果存在相同的node就覆盖,根据循环的count , 如果大于8个就把链表转化成红黑树来处理
  8. size++ 如果size>threshold 进行扩容
2. 扩容为2的幂数的原因 :
  1. 如果length不为2的幂,比如15。那么length-1的2进制就会变成1110。在h为随机数的情况下,和1110做&操作。尾数永远为0。那么0001、1001、1101等尾数为1的位置就永远不可能被entry占用。这样会造成浪费,不随机等问题。 length-1 二进制中为1的位数越多,那么分布就平均。

  2. 减少哈希碰撞,避免生成链表 , 降低查询效率

参考

3. 在Jdk1.8 之后HashMap做了什么优化
  1. jdk1.8之后 , 当链表长度达到8个之后转成红黑树存储,以提升它的查询、插入效率
  2. 在数组扩容后数据转移优化:在1.8之前是需要遍历每个链表中的数据重新通过hash值&新数组长度取出下标 , 在1.8之后 会把原先数组下标中的链表根据hash & oldtable.length == 0 分成两个链表 , ==0的链表还是存在原先下标j下 , !=0的讲存储在newtable[j+oldtable]
4. hash方法中为什么 (h = key.hashCode()) ^ (h >>> 16) 这样设计

由于hash和(length-1)进行&运算,length绝大多数情况小于2的16次方。所以始终是hashcode 的低16位参与运算。

要是高16位也参与运算,会让得到的下标更加散列。

所以这样高16位是用不到的,如何让高16也参与运算呢。所以才有hash(Object key)方法。让他的hashCode()和自己的高16位^运算。所以(h >>> 16)得到他的高16位与hashCode()进行^运算。

重点来了,为什么用^:因为&和|都会使得结果偏向0或者1 ,并不是均匀的概念,所以用^。

这就是为什么有hash(Object key)的原因。

参考

5. HashMap 是怎么扩容的

在size > threshold的时候进行resize扩容操作 。
源码注释.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值