首页 > 其他分享 >Debug ( 自行跟踪集合全总结 )

Debug ( 自行跟踪集合全总结 )

时间:2023-05-11 22:44:54浏览次数:35  
标签:elementData put 构造 自行 集合 添加 key Debug null

Debug

  • 自行回顾跟随源码所得理解
  • 能力有限,有问题还请求指正

List

ArrayList

  • 维护的是一个 Object 类型的数组 elementData
  • 使用的几乎都是 ArrayList 内部类

无参构造器

  • 起初构造器给一个空 Object 数组:this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

第一次添加

  • add 方法进入,:

  • add() 内每次执行 ensureCapacityInternal(size + 1); ( size:0 —> 1,是已存储个数,就是后面的 minCapacity )

    • ensureCapacityInternal(int minCapacity) 内:

      ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

      • calculateCapacity() 判断是否 elementData[] 是否为空,空则默认分配 DEFAULT_CAPACITY = 10 返回 ( minCapacity:1 —> 10 )

      • ensureExplicitCapacity(int minCapacity) :修改加一,确定是否扩容:

        minCapacity - elementData.length > 0grow() 扩容 ( 第一次时 length 还为零 )

    • 真正扩容 —— grow()

      • 新容量为旧的 1.5 倍 ( int newCapacity = oldCapacity + (oldCapacity >> 1); ),因为第一次零,所以需要修正将初始 10 赋给新 newCapacity ( 只有第一次要修正 )
      • ( 如果超出了包装类某上限会触发 hugeCapacity(),一般不会 )
      • elementData = Arrays.copyOf(elementData, newCapacity); copyOf() 将旧值复制进新扩容后的数组,完成扩容操作
  • add() 内 elementData[size++] = e; 新值添加,size++ 移动到下一位,返回 true 完成添加

第二次添加

  • add() 执行 ensureCapacityInternal(size + 1);
    • calculateCapacity() 只有在首次添加 elementData 为空时才有作用
    • ensureExplicitCapacity() 修改加一,扩容否
  • add() 内 elementData[size++] = e; 依此类推

有参构造器

  • 构造器:this.elementData = new Object[initialCapacity];
  • 步骤似无参,只是第一次不需要再默认分配零,也不用 grow 扩容
  • 直到首次 minCapacity - elementData.length > 0 要存入的、所需的最小容量大于数组现有容量,就 grow() 扩容 1.5 倍 ( 扩容后 copyOf 新 elementData )

Vector

  • 维护的是一个 Object 类型的数组 elementData

无参构造器

  • 直接 this(10); ——> 调用 ( 单 ) 有参构造器 public Vector(int initialCapacity) 的方法:this(initialCapacity, 0); 再调用 ——> ( 双 ) 有参构造 public Vector(int initialCapacity, int capacityIncrement),initialCapacity:10,capacityIncrement:0,返回
    • 其中 capacityIncrement 是在 grow 扩容的时候才会用到的,自定义的扩容大小:int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);

第一次添加

  • add() 方法:

    • 修改加一

    • ensureCapacityHelper(minCapacity) 判断是否需要扩容

      if (minCapacity - elementData.length > 0)grow(minCapacity)

      • 扩容时判断:无参的话就是扩容为旧容量两倍的大小
      • 其余似 ArrayList,也是 Arrays.copyOf()
    • elementData[elementCount++] = e; 添加新值,返回 true

第二次添加

  • 依此类推

有参构造器

  • 参照无参时执行构造器方法的代码部分
  • 添加似无参,仅在扩容部分不同

LinkedList

  • 维护的是一个双向链表
  • 两个属性 first 和 last,分别指向首节点和尾节点
  • 存储是用 Node 节点:item、next、prev

无参构造器

  • 只有无参构造器

添加

  • add() 添加

    • 调用方法 linkLast(e);

      • 方法里的 last 是每上一次添加的链尾 Node 节点,第一次添加时 last 为 null

      • l = last

      • newNode = 新值 ( prev,item,next ) 定下 prev、值

        final Node<E> newNode = new Node<>(l, e, null);

      • last = newNode

      • l 空则定链首 first = newNode ( 第一次才走此语句,即首尾都是第一次添加的 ) —— 只有第一次添加才执行

      • 否则就 l.next = newNode,定下 next,双向链表完成 —— 只有第一次添加不执行

      • size、modCount 皆加一


Set

HashSet

  • 底层实际为 HashMap ( 只有开头构造器和 add() 是 HashSet 的内部类 )
  • HashMap 底层为:数组 + 链表 + 红黑树

无参构造器

  • 直接 map = new HashMap<>();,所以实际是就 HashMap
  • new 后无参的 HashMap 定下了临界因子 loadFactor = 0.75
  • 多个 Node 存进 table ( 有 entrySet )

添加

  • add() 调用 map 的 put() 方法,PRESENT 空 Object 占位 ( HashSet 只存一组对象 )

    return map.put(e, PRESENT)==null;:只有返回 null 才添加成功

    • put() 里返回 putVal(),其中先计算 key 的哈希值

      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);:计算返回给 hash(key),一起进入 putVal() 内

      • 另起分析核心代码 putVal()
  • 表空否

    • 空的话就扩容:
    • resize() 给 16 大小,临界为 0.75 * 16 = 12,返给 table
    • n = 扩容后长度
  • 继续执行,计算索引处空否 ( p = 存在 table 的索引 )

    • 为空的话就直接放进去 newNode
  • 索引处不为空的话,辅助用:Node e

    1. 索引处 hash 等 && 同对象或同值否

      • true 就 e = p
    2. 红黑树否

      • e = ...
    3. 与索引处链首不等且不是树,索引处 next 开始死循环链表

      1. ( 链表内无同 k ) e = p.next 为 null ( 链尾 ) — 判断时更新 e

        p = newNode,此时 e == null

        if 单链大于 8,treeifyBin() ( 树化条件一达成 )

        break

      2. 链表内有同 e.hash 等 && 与 e 同对象或同值否,break

      3. p = e,每次循环把被比较的值存进 e

    4. e 空否 ( 都要经过,上述1. 2. 3. 最多只执行其一 )

      • 存 e.value ( 1. 是索引处链首值 3. 是旧值 )

      • ( 同 k ) 替换 v 为新值

      • return,后面不再执行

  • 修改成功 + 1

  • ++size > 临界值否

    • true,大了就 resize() 再扩容
  • 返回 null ( 表示添加成功 )


LinkedHashSet

  • 底层是一个 LinkedHashMap ( HashMap 的子类 )
  • 维护了一个数组 + 双向链表 ( 跨表索引连接 )
  • newNode(hash, key, value, null);

无参构造器

  • LinkedHashSet里 的 super(16, .75f, true); —— HashSet 构造器,new LinkedHashMap 调 LinkedHashMap 构造器
  • LinkedHashMap里 的 super(initialCapacity, loadFactor); —— HashMap 构造器

添加

  • HashSet 的 add() 返回 map 的 put()

    return map.put(e, PRESENT)==null;

    • put() 里返回 putVal(),其中先计算 key 的哈希值
    • 后面都是同 HashSet 分析部分,参照文章上面所述
  • 但是不同在 newNode 时此处会先存 Entry 中:( HashSet 里 newNode 只是单纯调用了 Node 构造器 ) LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);,然后调用 linkNodeLast(p);

    • last = tail,第一次的话就全 null
      • 不是第一次的话,就是将上一个添加的值放 last 里
    • tail = p 把新值放链尾
    • last 空的话就表示第一次添加
      • head = p 定开头
    • 否则就表示非第一次
      • p.before = last
      • last.after = p
      • 上一个添加的值和新添加的值建立前后联系指向
  • 即:在决定好新值 newNode 存放处前就已经和上一个添加的值双链表连接上了,便是完成了跨表索引的连接

    • head 开头,tail 结尾
    • before 链内指向上一个值,after 链内指向后一个

TreeSet

  • 参照最后的 TreeMap
  • 因为 TreeSet 底层就是 TreeMap

有参构造器

  • 构造器里 this(new TreeMap<>(comparator));
  • 在 TreeMap 的构造器里 this.comparator = comparator;
  • add() 调用了 map 的 put(),用了占位 PRESENT,return m.put(e, PRESENT)==null;
    • put() 方法具体与本文最后的 TreeMap 部分一致
    • 返回 null 便是添加成功

Map

都是需要具体输入 k - v 元素

HashMap

无参构造器

  • 存储同 HashSet

  • 构造器仅定下了临界因子 loadFactor = 0.75

添加

  • 具体详见 HashSet 从调用 map 的 put() 方法
  • 只不过 Map 在 put() 方法时携参都是具体值 key、value,不会再用空 Object 占 value 的位置了,剩下的添加思路皆一致

Hashtable

  • 底层就是 Entry 数组

无参构造器

  • this(11, 0.75f);
  • 重载的有参构造器里分 table 给 11 大小,并临界算得 8
  • put() 内
    • 判断 value 不能为空,否则报错
    • Entry<?,?> tab[] = table;
    • int hash = key.hashCode(); 得 hash,然后再算出应在表的索引值 index
    • Entry<K,V> entry = (Entry<K,V>)tab[index]; 取出索引所在值 Entry
    • 同 key 就替换 value 值
    • addEntry()
      • 修改 +1
      • 若是已达临界值,便扩容 rehash();,机制是扩为旧容量的两倍加一
      • 没达临界就存新值到计算哈希值所得到的 table 表索引处
      • count++
    • 返回 null 完成存放

Properties

  • 继承了 Hashtable
  • 扩容和 put 添加都是 Hashtable 的内部类,过程一致

TreeMap

无参构造器:

  • comparator = null;
    • TreeMap 内定义 private final Comparator<? super K> comparator;
  • k 空就报错,Comparable<? super K> k = (Comparable<? super K>) key;
  • cmp = k.compareTo(t.key);
  • 无参默认 ASCII

有参使用匿名内部类:

  • 在重写匿名内部类的方法时可以自行决定排序规则

  • public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }

  • 第一次 put 添加

    • 新值存 root ( private transient Entry<K,V> root —— 之后也都是 Entry 形式存放 k-v )
    • compare(key,key):没有接收,对后面没影响,只是检测是不是为空
    • 直接添加
  • 第二次 put 添加

    • 接收匿名内部类方法 Comparator<? super K> cpr = comparator;
    • t = root ( 查找开始的根部,第二次的话就是第一次存的值 )
    • cpr != null,即有参重写过的话继续 ( 无参构造器的话就默认方式 ASCII 存新值 )
    • do
      • parent = t
      • cpr.compare 得知值放 parent 左 / 右 ( 只是定下 )
    • while,t == null 退出,即找到可以放进新值的空处
    • e 存新值
    • parent 左 / 右 = e ( 真正存放进去 )
    • fixAfterInsertion(e); 树化更新 root
    • 大小、修改次数 ++
    • 返回 null
  • 第三次 put 添加

    • ...... ( 前面一致 )
    • cpr.compare 得知值放 parent 左 / 右 ( 只是定下 )
    • 若是 parent 左 / 右 != null,无法存进
      • 循环 parent = 左 / 右非空有值的 Entry
      • compare 得知值放 parent 左 / 右
      • 直到应放位置为空了,退出循环
    • e 存新值
    • parent 左 / 右 = e ( 真正存放进去 )
    • ...... ( 后面也都一致 )
  • 依此类推,但注意,若是存放值 key 是 “ z、a、o ”,这种情况,会在真正存放完新值 o 时经过 fixAfterInsertion(e); 方法 ( 红黑树 ) 将 root 改为 o ( 处中间 ),即 o.left 为 a,o.right 为 z

    • 断 a - z 为 a - o - z

标签:elementData,put,构造,自行,集合,添加,key,Debug,null
From: https://www.cnblogs.com/zhu-ya-zhu/p/17392464.html

相关文章

  • 微信小程序开发工具怎样支持xdebug调试
     在做PHP项目时候用xdebug进行调试,如果使用浏览器我一般直接XdebugHelper浏览器插件。配合PHPSTORM进行调试。 微信小程序并不支持cookies,因此需要另想办法,可以在微信小程序的request里加上cookies头,如下代码所示:wx.request({url:'',header:{'Cooki......
  • Mac ssh "debug1: SSH2_MSG_KEXINIT sent"
    http://techbackground.blogspot.com/2013/06/path-mtu-discovery-and-gre.html简单解释就是IPV4报头与GRE报头结构不同,导致GRE数据包最大内容载荷只有1454,默认mtu如果是1500的话,就会有46字节的内容无法处理导致错误。WIFI可以直接在网络设置里修改,调整为手动,设置MTU为1454:......
  • Windows的Mysql5.7社区版的安装详细操作,从无到有,安装配置一条龙服务。(压缩包自行安装,
    换了一个电脑,所有软件、环境都得重新来安装一次,安装到Mysql的时候,发现网上有两种安装方式,一种是Mysql的压缩包安装方式,这种方式直接到官网下载Mysql的压缩包,解压之后做些配置就可以了,另一种是Mysql的Installer一站式的安装,这种方法步骤相对来说少点,但是要先安装个Installer在......
  • N4-P4部分debug
    ERROR1:a=sum.read(1)+bERROR2:a=a+1ERROR3:Thetargetlimitsthenumberofactionsaccessingasingleregisterto4.ERROR4:ConditionsinanactionmustbesimplecomparisonsofanactiondataparameterTrymovingthetestoutoftheactionan......
  • python基础学习-集合
    """集合:无序,不允许重复,不支持下标索引,允许修改#字面量{元素1,元素2,元素3}#定义变量变量名称={元素1,元素2,元素3}#定义空集合变量名称=set()方法:1.添加新元素集合.add(元素)2.移除元素集合.remove(元素)3.随机取出元素element=集合.pop()4.清空......
  • 集合的并发修改异常
    情景一:ArrayList<Integer>arrayList=newArrayList<>();for(inti=0;i<10_000;i++){ arrayList.add(newRandom().nextInt(100_000_000));}/**开启多个线程,每个线程都执行迭代器*/for(inti=0;i<20;i++){ newThread(()->{ Iterator<......
  • 用chatgpt ui 实现 对于时间段集合中的每个时间段,检查它是否与要检查的时间段重叠或者
    以下是一个C#实现,用于确定一个时间段是否与另一个时间段集合重叠或交叉,如果有重叠或交叉则返回false。算法:传递要检查的时间段和时间段集合作为参数。对于时间段集合中的每个时间段,检查它是否与要检查的时间段重叠或者有交叉。如果有重叠或交叉,则返回false表示它们不应该......
  • 解决GoLand 无法debug的问题
    gitclonehttps://github.com/go-delve/delve.gitcddelve/cmd/dlv/gobuildgoinstall(dlv版本:DelveDebuggerVersion:1.7.2)弄完后,就有一个dlv的可执行文件了,需要放到你的$GOPATH/bin里去(有一篇文章提到,需要在$GOPATH/bin下面再建一个macarm文件夹,然后把这个dlv可执行文件再......
  • 使用IDEA远程Debug调试(详细)
    一:前言记得刚工作那会写代码,遇到线上奇怪问题时,就会在可能出现问题的地方大量打log,然后重新打包部署,再对打印的log进行分析;往往这一套流程下来,基本上1个小时左右的时间就这么白白浪费,但要log打的不合理,那么就嘿嘿了,我们要不停的修改代码打log、不停的打包部署。这是何等的浪......
  • 使用 shell 脚本自动申请进京证 (六环外) —— debug 过程
    问题现象用shell脚本写了一个自动办理六环外进京证的工具《使用shell脚本自动申请进京证(六环外)》,然而运行这个脚本总是返回以下错误信息:{"msg":"目前办理业务人数较多,请稍后再试。","code":500}咨询woodheader/jjz项目的作者,了解到问题就是出在请求头或参数......