编程语言
首页 > 编程语言> > HashMap 源码阅读(1)

HashMap 源码阅读(1)

作者:互联网

HashMap 源码阅读(1)

预备知识

二叉树

1. 左子树上,所有节点的值均小于或等于它的根节点的值
2. 右子树上,所有节点的值均大于或等于它的根节点的值
3. 左右子树,符合1和2

红黑树

Red-Black Tree,红黑树,一种自平衡的二叉查找树

  1. 节点是红色或黑色
  2. 根节点是黑色
  3. 所有叶子节点,都是黑色的空节点
  4. 每个红色节点的两个子节点,都是黑色
  5. 从任一节点到每个叶子节点的所有路径,都包含相同数目的黑色节点
图片来源https://zhuanlan.zhihu.com/p/31805309

插入

1)父节点是红色

插入节点为红色,导致两个红色节点,需要调整

特性4:每个红色节点的两个子节点,都是黑色

变色
旋转
2)父节点是黑色

插入节点为红色,无需调整

删除

1)删除节点是红色

无需调整

2)删除节点是黑色
删除前驱 & 变色
删除后继 & 变色

HashMap

节点

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

	// getter toString set

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

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

默认策略

  1. 默认容量:DEFAULT_INITIAL_CAPACITY = 1<<4

  2. 默认加载因子:DEFAULT_LOAD_FACTOR = 0.75f

  3. 树化阈值:TREEIFY_THRESHOLD = 8

  4. 去树化阈值:UNTREEIFY_THRESHOLD = 6

  5. 扩容后容量:2的指数幂

    下述newCap在三个地方赋值:
    	1)newCap = oldCap << 1
    	2)newCap = oldThr
    	3)newCap = DEFAULT_INITIAL_CAPACITY
    起点为DEFAULT_INITIAL_CAPACITY = 16
    
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        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);
    }
    

属性

transient Node<K,V>[] table
transient Set<Map.Entry<K,V>> entrySet
transient int size
transient int modCount
// resize阈值
int threshold
final float loadFactor

null

允许键值均为null,只允许put一个key为null的对象。

key为null的value,取最后一次put的key为null的值。

即:键为null的桶,只有一个节点。其hash为0,占用tab[0]。

resize

resize:size > threshold

说明,默认构造器并分配内存,此时Node[]为null,是在第一次put时,执行resize实现分配
  1. 扩容时新创建一个数组,将原哈希表赋值给新哈希表,有一定性能消耗:Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]if (oldTab != null)
  2. resize后,Node数组的长度改变,造成hash计算结果改变,原有节点将重新hash(key),导致位置发生改变
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;
        }
        // 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);
    }
    // 已分配却无数据,计算resize阈值
    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;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                // 桶,单节点:重hash
                if (e.next == 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;
}

crud

1. 计算给定key的hash
2. table为null,长度为0或定位桶为null
	结束
3. 检查桶中第一个节点
	是,返回;
4. 检查桶中剩余节点
	- 树:调用getTreeNode
	- 链表:插到返回,查不到返回null

增|改:modCount

1. table == null
	resize
2. 定位
3. 桶为空
	插入,结束
4. 检查桶中第一个节点
	是,结束
	否,继续5
5. 检查桶中剩余节点
	- 树:调用putTreeVal
	- 链表
		1. 不存在,插入,检查是否需要树化
		2. 存在,更新

删:modCount

1. table == null
	结束
2. 定位
3. 桶为空
	结束
4. 检查桶中第一个节点
	是,调到6
	否,继续5
5. 检查桶中剩余节点
	- 树:查找
	- 链表:查找
6. 删除,检查是否需要去树化

参考

https://zhuanlan.zhihu.com/p/31805309

清澈。初见 发布了30 篇原创文章 · 获赞 12 · 访问量 9394 私信 关注

标签:Node,newCap,HashMap,int,next,源码,阅读,null,节点
来源: https://blog.csdn.net/qq_31854267/article/details/103991856