zoukankan      html  css  js  c++  java
  • Map集合基础02-HashMap

    数据结构(和ConcurrentHashMap类似)

    • 存储数据的基础结构时Node的数组;

    • 节点中保存的是当前节点的hash,主键Key,对应值value,链表的next;

     transient Node<K,V>[] table;
    
     static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;
    }
    

    基础操作

    put方法

    • 方法和concurrentHashMap类似,其中涉及到红黑树的部分,可以查看另一篇红黑树的详细代码执行过程
       /**
        * 1.如果没初始化先进行初始化;
        * 2.hash对应的位置没有数据,则新增节点
        * 3.判断hash,key是否相同,相同则赋值临时值。否则,判断是树状结构则从红黑树中更新值,并返回替换的节点或者返回null;
        * 4.判断如果是链表则遍历链表,如果在链表中则返回替换的节点  
        * 5.完成上面这些后,节点计数+1,判断是否要进行重新调整数组大小
        */     
        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)//hash对应的数组位置为null,则新增Node节点
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))//如果key相等
                    e = p;//将当前节点赋值临时节点e
                else if (p instanceof TreeNode)//如果p节点是红黑树。那么在红黑树中插入节点
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    /**
                     * 遍历链表,查找节点,会出现以下几种情况。
                     * 1.逐个遍历,判断节点key是否和新插入数据hash,key相等
                     * 2.如果没有,则新增节点,并判断是否要生成红黑树;
                     *  生成红黑树的条件:数组最小长度要为64,并且链表个数超过8个时  
                     */     
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;//赋值下一个节点查看
                    }
                }
                if (e != null) { //如果能查找到节点,则将旧值换新值
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;//数量+1
            // 判断是否需要重新设置大小
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);//钩子方法,用于子类实现
            return null;
        }
    /**
     * 1.确定新的数组大小
     * 2.进行数据节点重排
     */
    final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;//原始数组长度
            int oldThr = threshold;
            int newCap, newThr = 0;
             /**
              * 1.原始长度大于0,当大于等于最大容量,返回现有数组;
              * 2.新长度的大小为旧长度两倍且小于最大容量,并且旧长度大于最小初始长度时,新长度为为旧长度的2倍
              */           
            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) //是否超过临界值
                newCap = oldThr;
            else {               //如果初始是0,则设置为默认
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
           //新的临界值为0时
            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;
                        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 { // 链状结构进行重排
                            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;//返回新的数组
        }
    
    

    remove方法

    • 查找出要移除的节点,统一进行移除操作
        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, index;
            if ((tab = 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) {
                    if (p instanceof TreeNode)
                        node = ((TreeNode<K,V>)p).getTreeNode(hash, key);//获取树结构中的节点
                    else {//获取链表中节点
                        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)//数组结构的话,替换成节点的next链节点(无则为null)
                        tab[index] = node.next;
                    else //链表的话,更改节点关联
                        p.next = node.next;
                    ++modCount;
                    --size;
                    afterNodeRemoval(node);//钩子方法
                    return node;
                }
            }
            return null;
        }
    
    

    get方法

    • 根据key查找节点
    final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
             /**
              * 1.判断第一个节点,如果一致的就返回,如果不一样再看是不是树状结构或者链表结构
              * 2.树状结构的话,通过二分法进行查找;
              * 3.链表结构的话,逐个遍历进行查找;
              * 4.如果没有查找到,返回null;   
              */
                if (first.hash == hash && 
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                if ((e = first.next) != null) {
                    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;
        }
    

    问题:

    1. 为啥默认的平衡因子时0.75?

    有位大佬博文分析的很清楚,有兴趣可以看一下:https://www.cnblogs.com/aspirant/p/11470928.html

    总结一下就是:提高空间利用率和 减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小,
    

    2.HashMap和ConcurrentHashMap的区别?

    • 值插入时,ConcurrentHashMap是通过CAS插入新的数组节点,和通过Synchronized替换原节点,链表节点或树节点。

    • ConcurrentHashMap在进行插入,删除操作的时候会判断容器是否在扩容。

    由上面这两点可以看出ConcurrentHashMap相对于HashMap线程是安全的。

    • HashMap的优势在于:插入速度比较快;但是遇到多线程的时候,很容易出现链路闭环;

    3.为什么HashMap数组长度一定是2的次幂?

    • 获取位置更均衡

      • 插入值的位置tab[i = (n - 1) & hash],n-1之后各个位置刚好是1;

      • hash和(n-1)高位与,则为0,与(n-1)低位获取的数字是不变的。可以获得固定的存储位置。

    • 扩容定位更方便

      • n变成2倍,(n-1)向前进1;

      • n变成2倍,hash对应位为0时,存放位置不变

    具体如下图:

  • 相关阅读:
    vim复制
    嵌入式Linux学习(二)
    (Java实现) 洛谷 P1042 乒乓球
    (Java实现) 洛谷 P1042 乒乓球
    (Java实现) 洛谷 P1071 潜伏者
    (Java实现) 洛谷 P1071 潜伏者
    (Java实现) 洛谷 P1025 数的划分
    (Java实现)洛谷 P1093 奖学金
    (Java实现)洛谷 P1093 奖学金
    Java实现 洛谷 P1064 金明的预算方案
  • 原文地址:https://www.cnblogs.com/perferect/p/13703805.html
Copyright © 2011-2022 走看看