zoukankan      html  css  js  c++  java
  • Java源码剖析34讲学习笔记~2

    1. 数据结构

    • 1.7 数组 + 链表
    • 1.8 数据 + 链表 + 红黑树(链表大于8并且总长度大于64)

    2. 相关面试题

    • JDK 1.8 HashMap 扩容时做了哪些优化?

      • 通过高位运算 (e.hash & oldCap) 确定元素需要移动, 例如:

        key1信息如下:
        - key1.hash = 10 0000 1010
        - oldCap = 16 0001 0000
        使用 e.hash & oldCap 得到的结果高一位为 0
        当结果为 0 时表示元素在扩容时位置不会发生任何改变
        
        key2信息如下:
        - key2.hash = 10 0001 0001
        - oldCap = 16 0001 0000
        使用 e.hash & oldCap 得到的结果高一位为 1
        当结果为 1 时, 表示元素在扩容时, 位置发生变化, 新的下标位置等于原下标位置 + 原数组长度
        
    • 加载因子为什么是0.75?

      • HashMap初始容量为 16, 16 * 0.75(加载因子) = 12, 当HashMap中元素达到 12 时, 就会进行扩容

      • 加载因子为0.75的原因是容量和性能之间平衡的结果

        • 当加载因子设置比较大时, 扩容的门槛就被提高了, 扩容发生的频率比较低, 虽然占用的空间会比较小, 但此时发生Hash冲突的几率会提升, 因此需要更复杂的数据结构来存储元素, 这样对元素的操作时间就会增加, 运行效率也会因此降低

        • 而当加载因子值比较小的时候, 扩容的门槛会比较低, 因此会占用更多的空间, 此时元素的存储就比较稀疏, 发送Hash冲突的可能性就比较小, 操作性能会比较高

    • 当有哈希冲突时, HashMap是如何查找并确认元素的?

      • 当哈希冲突时需要通过判断 key 值是否相等,才能确认此元素是不是我们想要的元素
    • HashMap源码中有哪些重要的方法?

      • 查询

        public V get(Object key){
            Node<K, V> e;
            // 对key进行哈希操作
            return (e = 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, e; int n; K k;
            // 非空判断
            if((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash)] != null){
                // 判断第一个元素是否是要查询的元素
                if(first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))){
                    return first;
                }
                // 下一个节点非空判断
                if((e = first.next) != null){
                    // 如果第一节点是树结构, 则使用getTreeNode直接获取相应的数据
                    if(first instanceof TreeNode){
                        return ((TreeNode<K, V>)first).getTreeNode(hash, key);
                    }
                    do{ // 非树结构, 循环节点判断
                        // hash 相等并且key相同, 则返回此节点
                        if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
                            return e;
                        }
                    }while((e = e.next) != null);
                }
            }
               return null;
        }
        
      • 新增

        public V put(K key, V value){
            // 对key进行哈希操作
            return 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, i;
            // 哈希表为空则创建表
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            // 根据key的哈希值计算出要插入的数组索引 i
            if ((p = tab[i = (n - 1) & hash]) == null)
                // 如果table[i] 等于null, 则直接插入
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                // 如果key已经存在了, 直接覆盖value
                if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                // 如果key不存在, 则判断是否为红黑树
                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) {
                            p.next = newNode(hash, key, value, null);
                            // 链表长度大于8转换为红黑树进行处理
                            if (binCount >= TREEIFY_THRESHOLD - 1)
                                treeifyBin(tab, hash);
                            break;
                        }
                        // key已经存在直接覆盖value
                        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;
            // 超过最大容量, 扩容
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
        

    HashMap添加操作执行流程

    • 扩容

      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;
              }
              // 扩大容量为当前容量的两倍, 但不能超过MAXIMUM_CAPACITY
              else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
                  newThr = oldThr << 1;
          }
          // 当前数组没有数据, 使用初始化的值
          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
          table = newTab;
          // 原数据不为空, 将元数据复制到新的table中
          if (oldTab != null) {
              // 根据容量循环数组, 复制非空元素到新table
              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 {
                          // 链表复制, JDK 1.8 扩容优化部分
                          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;
                              }
                              // 原索引 + oldCap
                              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;
      }
      
    • HashMap是如何导致死循环的?

      • 原因:

        • JDK1.7 链表插入方式为首部倒序插入
        • JDK1.8 进行了改善, 变成了尾部正序插入
      • 案例

        /**
         * JDK1.7
         * 假设HashMap默认大小为2
         * 原本HashMap中有一个元素key(5)
         * 再使用两个线程:
         * t1添加元素key(3)
         * t2添加元素key(7)
         * 当元素key(3)和key(7)都添加到HashMap后
         * 线程t1在执行到Entry<K, V> next = e.next;时
         * 交出了CPU的使用权
         */
        void transfer(Entry[] newTable, boolean rehash){
            int newCapacity = newTable.length;
            for(Entry<K, V> e : table){
                while(null != e){
                    // 线程一执行此处
                    Entry<K, V> next = e.next;
                    if(rehash){
        				e.hash = null == e.key ? 0 : hash(e.key);
                    }
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                }
            }
        }
        
        /**
         * 当t1重新获得执行权之后
         * 先执行 newTable[i] = e 把key(3) 的 next 设置为 key(7)
         * 而下次循环时查询到 key(7) 的 next 元素为 key(3)
         * 于是就形成了 key(3) 和 key(7) 的循环引用
         * 因此导致了死循环的发生
         */
        

    JDK1.7头插死循环原因

    3. JDK1.8源码中包含属性

    // HashMap 初始化长度
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
        
    // HashMap 最大长度
    static final int MAXIMUM_CAPACITY = 1 << 30; // 1073741824
    
    // 默认的加载因子(扩容因子)
    static final int DEFAULT_LOAD_FACTOR = 0.75f;
    
    // 转换红黑树的临界值, 当链表长度大于此值时, 会把链表结构转换为红黑树结构
    static final int TREEIFY_THRESHOLD = 8;
    
    // 转换链表的临界值, 当元素小于此值时, 会将红黑树结构转换成链表结构
    static final int UNTREEIFY_THRESHOLD = 6;
    
    // 最小树容量
    static final int MIN_TREEIFY_CAPACITY = 64;
    
  • 相关阅读:
    68
    56
    Django manager 命令笔记
    Django 执行 manage 命令方式
    Django 连接 Mysql (8.0.16) 失败
    Python django 安装 mysqlclient 失败
    H.264 SODB RBSP EBSP的区别
    FFmpeg—— Bitstream Filters 作用
    MySQL 远程连接问题 (Windows Server)
    MySQL 笔记
  • 原文地址:https://www.cnblogs.com/unrecognized/p/13258513.html
Copyright © 2011-2022 走看看