• [手撕源码]ArrayList与顺序表分析


    文章重点总结我在学习中遇到的不易理解的点,以及在面试过程中常遇到的点和题目思路分享.

    👉文章默认大家拥有ArrayList和LinkedList的基础知识

    基础回归:
    ⁜ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表

    一.分析ArrayList的源代码

    1.ArrayList的构造

    image-20220821225454458

    通过分析ArrayList类下一共有三种构造方法

    image-20220821230012665无参构造方法
    image-20220821230029375指定顺序表初始容量
    image-20220821230120859利用其他 Collection 构建 ArrayList,例如自定义comparator比较方法作为实参进行传参

    2.ArrayList的遍历

    迭代器遍历方式介绍:

    List<Integer> list = new ArrayList<>();
    list.add(1); list.add(2);
    list.add(3);
    list.add(4);
    list.add(5);
    Iterator<Integer> it = list.listIterator();
    while(it.hasNext()) {
        System.out.print(it.next() + " ");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    迭代器工作示意图:

    1661096025312 00_00_00-00_00_30

    3.面试高频题:ArrayList的扩容机制

    下面将通过分析java底下的源代码进行分析:

    image-20220821233851931

    简单解释上述编号代码的代码含义:

    ②代表ArrayList的默认容量大小
    ③④均为空数组,区别具体在哪慢慢分析
    ⑤为ArrayList类在使用过程中调用的数组
    ⑥ArrayList有效元素个数

    这里我们以无参构造方法来具体分析其扩容机制
    image-20220821234217160

    通过无参构造方法创建ArrayList时,默认将elementData数组引用DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组

    继续往后看,分析源码是如何添加元素的

    image-20220821234605711

    将带添加元素传入进来后,首先调用了ensureCapacityInternal函数,并且把size+1也就是将目前元素添加进数组后的长度作为实参传过去,我们猜测目的是判断是否超出当前长度是否需要扩容,那我们顺着往后分析;

    image-20220821234830144

    通过这个函数又调用了ensureExplicitCapacity函数并且在其内嵌套了一个calculateCapacity函数,同时还将elementData数组,当前元素个数传入了进去我们继续分析calculateCapacity函数

    image-20220821235018272

    刚刚在创建ArrayList时虽然引用了一个空数组,但是通过calculateCapacity中的if判断,如果elementData未开辟空间,则返回DEFAULT_CAPACITY, minCapacity的最大值,此时我们的最大值是DEFAULT_CAPACITY也就是默认数组大小10;我们回到calculateCapacity的函数调用者ensureExplicitCapacity函数

    image-20220821235406283

    将calculateCapacity函数返回值与当前数组长度判断,如果大于0则调用grow函数,也就是我们说的扩容函数!接下来就具体看看grow函数到底卖的是什么葫芦~

    image-20220821235556135

    首先oldCapacity存储了当前数组的长度,newCapacity:(扩容的大小/创建ArrayList的大小)
    newCapacity这一行的操作是一个1.5倍扩容(这里解释一下oldCapacity >> 1正好就是原大小的0.5倍),

    if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
    
    • 1
    • 2

    如果新容量大小小于我们要实现/添加的元素大小,则newCapacity = minCapacity,

    if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
    
    • 1
    • 2

    如果newCapacity大小大于了image-20220822000438772

    则调用hugeCapacity

    image-20220822000506606

     if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
    
    • 1
    • 2

    判断minCapacity是否<0,如果小于0,则创建/扩容失败,抛出异常

    判断minCapacity是否大于整数最大值,大于则返回整形最大值作为数组大小.

     elementData = Arrays.copyOf(elementData, newCapacity);
    
    • 1

    最后,我们就实现好了一个ArrayList类

    结论

    1.检测是否需要扩容,需要则一路走下来调用我们的grow函数

    2.计算扩容需要的大小:

    • 初步预估按照1.5倍大小扩容
    • 如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容
    • 真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
    1. 使用copyOf进行扩容

    思考

    最后的最后,几个问题对我们的顺序表进行一个简单的提问

    1.顺序表的中间/头部的插入,时间复杂度是多少?为什么?

    2.增容对于资源的开销在哪些方面?

    3.这样的增容方式合理么?如何解决?

  • 相关阅读:
    flink重温笔记(九):Flink 高级 API 开发——flink 四大基石之WaterMark(Time为核心)
    驱动开发:内核监控进程与线程回调
    实时互动还有哪些可能?RTE 2022 创新编程挑战赛正式开启
    『UniApp』uni-app-打包成App
    面试突击48:死锁的排查工具有哪些?
    网络安全(黑客)自学
    C++中返回类型与return语句
    JavaScript判断字符串是否为数字类型:Number.isInteger、isNaN、正则表达式比较
    使用VS 不调试的情况下启动诊断工具功能 生成程序分析报告
    Unity ab包加载文本 puerts 自定义loader
  • 原文地址:https://blog.csdn.net/qq_36935038/article/details/126457788