HashMap

HashMap

  • HashMap 是一个Node<K,V>数组(table) 和 链表(或者红黑树) 结构组成。
    /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     */
    transient Node<K,V>[] table; // Node数组声明
  • HashMap 的容量肯定是二的倍数。
  • capacity 表示容量,指的table数组的长度。是2的幂次方(16、32、64等)。
  • threshold 表示门限,当HashMap的size超过 threshold 时,就会触发resize()。
/**
 * Returns a power of two size for the given target capacity.
 */
static final int tableSizeFor(int cap) {} // 这个函数用于计算初始的threshold
  • 链表转换为红黑树的门限值 8。static final int TREEIFY_THRESHOLD = 8;
hash 函数。扰动函数
  • 首先调用hashCode函数生成hash值h。
  • 然后h向右移动 16位, 用h的 高 16 位 和 低 16位进行异或操作,异或的结果作为hashMap中的hash值。
  • 高16位和低16位异或的目的:增加函数的散列性,降低hash冲突的概率。 高位和低位的异或结果作为hash值,可以保留部分高位和低位的数据特征,从而提高散列性。
  • 参考链接:https://www.zhihu.com/question/20733617
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
resize函数
  • 功能:初始化或者翻倍 table的容量。同时会对红黑树 或者 链表重新散列一次。
  • 首先会计算出新的容量 capacity 和 门限值 threshold。容量的扩容是翻倍(左移一位)。
  • 如果是首次创建table 数组,那么直接返回数组对象。
  • 链表的重新散列:根据(e.hash & oldCap) == 0 将旧table中的一个链表拆分到扩容后新table的两个链表。

    对于这个判断条件的分析:oldCap 代表旧容量,肯定是2的幂次方(16、32、64等),二进制最高位为1,其余位均为0。oldCap旧容量的最高位即是扩容后(新容量newCap - 1)的最高位。

    总结,根据 hash & oldCap 最高位是否为0,来将旧链表拆分成为两个链表,然后分别放到新table中的第j 和 第 j + oldCap 位置。the elements from each bin must either stay at same index, or move with a power of two offset in the new table.
  • 红黑树的调整??
  • 代码注释:
/**
 * 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;
    // 根据oldCap,oldThr 计算 newCap,newThr。
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&   // table 的 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 * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;

    // 完成cap、threshold更新过程,开始扩容
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // new 一个新的table数组
    table = newTab;
    if (oldTab != null) { // 当前map不为空,需要处理旧的table,更新到 newTab中
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                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; // low head, low Tail。 低位链表的头尾
                    Node<K,V> hiHead = null, hiTail = null; // hight head, hight Tail。 高位链表的头尾
                    Node<K,V> next;
                    do {
                        next = e.next;
                         // oldCap的二进制最高位为1,其余位置为0.根据hash值最高位是否为0,将一个链表拆分成为两个
                        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; // 赋值到 j 位置
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead; // 赋值到 j+ oldCap
                    }
                }
            }
        }
    }
    return newTab;
}
putVal函数
  • 如果hash后对应table的位置为空,则直接赋值
  • 如果不为空,则对链表进行遍历。
  • 在找到同键值时,对value进行替换。
  • 在找不到同键值对象时,把最新的node对象添加到链表最后。如果添加对象后,链表长度超过了红黑树门限值 TREEIFY_THRESHOLD, 那么链表转换为红黑树。
  • 是否是同键值的判断条件:哈希值相等并且 【满足 == 条件 或者 equals函数相等】
e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @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)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null) // 1.p在此处完成赋值动作 2.判断此hash位置是否为 null
            tab[i] = newNode(hash, key, value, null); // 1.如果table中对应hash值位置为空,那么直接将node赋值。
        else { // hash & (n -1)位置已经存在value
            Node<K,V> e; K k;
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 判断 插入Node的 key值 是否和 p 的key相同
                e = p; // 赋值给 e对象。 e对象会在最后赋值流程更新value值。
            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) { // 1. 给 e 赋值  2.遍历到链表最后的节点
                        p.next = newNode(hash, key, value, null); // 创建新节点,添加到链表最后
                         // 如果链表数量大于红黑树门限值,转换为红黑树。
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                     // 判断 插入Node的 key值 是否和 e 的key相同,相同则退出,进入赋值流程。
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e; // 遍历下一个链表节点
                }
            }
            // 同key的Node已经存在于table中,此处进行更新value值流程
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null) // 根据onlyIfAbsent判断是否要更新value
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount; // 用于 iterator 遍历时的快速失败。
        if (++size > threshold) // map中的数据个数超过门限值,进行扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }
红黑树的性质
  • 红黑树是一种二叉查找树。二叉查找树的性质:
    • 若节点的左子树不空,则左子树上所有结点值均小于它的根结点的值
    • .若节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值
    • 任意节点的左、右子树也分别为二叉查找树。
    • 没有键值相等的节点(no duplicate nodes)。
    • 查找操作的时间复杂度 O(lgn), 最坏是链表情况O(n)
  • 红黑树的性质
    • 根节点是黑色的
    • 叶子节点是黑色的
    • 如果一个节点是红色,那么子节点必须是黑色
    • 叶子节点(nil节点)到根节点的路径上的黑色节点数目是一致的。// 注解: 每个叶子节点都是nil节点,nil节点是黑色的。
  • 红黑树的查找、插入、删除的最坏时间复杂度是O(lgn)
  • 红黑树左旋、右旋、插入、删除????
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值