ArrayList底层实现源码讲解与vector区别

一、ArrayList数据结构

ArrayList的底层数据结构是一个类型为Object的数组。
数组的初始长度与创建ArrayList对象时使用的构造方法有关
注:数组“elementData”是用来存储数据、操作数据的数组
“DEFAULTCAPACITY_EMPTY_ELEMENTDATA”是默认的空数组

  1. 传入的初始长度
    在这里插入图片描述
  2. 使用无参构造函数,创建长度为0的数组,在加入第一个数据时会将数组扩容到默认值10
    在这里插入图片描述
  3. 使用传相同数据类型的数组的构造函数,长度为传参数组的长度
    在这里插入图片描述

经测试,如果要存100万数据,需要扩容28次,数据量越大,扩容次数越多,每一次的扩容代表着创建新数组对象,复制原有数据。
如果数据很大,那么有必要为集合初始化一个默认大小,防止多次扩容,但如果数据增长很慢,那么就会浪费内存了,具体怎么做,还是要看实际应用场景。这里只做初步分析。

二、ArrayList的线程安全性

由于ArrayList每次增加数据的时候需要通过add()方法,而add方法的实现是通过size计数,将数据储存在size索引下的数组里,然后再将size加一。

在这里插入图片描述

具体举例说明:在单线程运行的情况下,如果Size = 0,添加一个元素后,此元素在位置 0,而且Size=1;而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增 加 Size 的值。 那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而Size却等于 2。这就是“线程不安全”了。

如果非要在多线程的环境下使用ArrayList,就需要保证它的线程安全性,通常有两种解决办法:第一,使用synchronized关键字;第二,可以用Collections类中的静态方法synchronizedList();对ArrayList进行调用即可。

三、ArrayList扩容方式

当使用add()方法是,通过检测插入时size++的值是否大于数组的长度,大于的话通过grow()方法进行数组扩容。grow()方法中的“>>1”是将二进制数字向右移一个单位,即是将数字除2。因此oldCapacity+(oldCapacity>>1)就是扩大了1.5倍,如:原本数组长度为10,进行扩容以后数组长度为10+5=15,扩大了1.5倍。
注:“Integer.MAX_VALUE”是Integer类能取到的最大值0x7fffffff
在这里插入图片描述
在这里插入图片描述

如果扩大两倍后的值大于“Integer.MAX_VALUE - 8”则将扩容数组的长度设置为“Integer.MAX_VALUE”或“Integer.MAX_VALUE-8”。

四、常用方法

图一
图一
运行结果

add()增加

代码演示

上图中用蓝笔标识的是add()方法的使用有两种使用方式,一种直接添加数据,一种在指定位置插入数据,会替换掉原先的数据。

源码分析

  1. 直接在后面添加
    在这里插入图片描述
    先判断size++的值是否超过了数组长度,超过了进行两倍扩容,判断长度以后在size位插入数据。
  2. 在指定位置插入数据
    在这里插入图片描述
    (1)当index>size时会出现错误“IndexOutOfBoundsException("Index: “+index+”, Size: "+size)”
    在这里插入图片描述
    (2)当size++大于数组长度时进行扩容
    (3)进行拷贝操作,将index下标后面数据往后移动一个单位
    (4)对index下标下的值进行替换

contains()判断是否存在

代码演示

是判断在ArrayList数组中有没有对象与“contains()”括号中相同的对象,有则返回true,没有则返回false。

ArrayList<Course> course = new ArrayList();

        // 初始化5个对象
        for (int i = 0; i < 5; i++) {
            course.add(new Course("course " + i));
        }
        Course specialCourse = new Course("special course");
        course.add(specialCourse);

        System.out.println(course);
        // 判断一个对象是否在容器中
        // 判断标准: 是否是同一个对象,而不是name是否相同
        System.out.print("虽然一个新的对象名字也叫 course 1,但是contains的返回是:");
        System.out.println(course.contains(new Course("course 1")));
        System.out.print("而对specialHero的判断,contains的返回是:");
        System.out.println(course.contains(specialCourse));

在这里插入图片描述

源码分析

在这里插入图片描述
在这里插入图片描述
由图可知,当运行contains()是调用indexOf()方法,在indexOf()中循环判断ArrayList中的对象与对比对象进行equals对比,如果对比对象中重写了equals()方法就对比equals()中的比较值,如果没重写就对比对象的地址。

set()替换

源码分析

在这里插入图片描述
(1)对index与size进行大小比较,看是否越界
(2)对数组下标为index的值进行覆盖
(3)返回原值

get()获取指定位置的对象

代码演示

看图一中的get()方法的使用,直接在括号中放入要获取的指定位置,将会获得该位置的值。

源码分析

在这里插入图片描述
(1)先判断获取的位置是否小于size,如果大于size抛出“IndexOutOfBoundsException("Index: “+index+”, Size: "+size)”
(2)通过下标获取数组中的值

indexOf()获取对象所处的位置

源码分析

在这里插入图片描述
根据输入的值,与ArrayList中的对象进行对比,找到了一样的就返回该对象的地址,通过equals方法进行的比较,没重写对比的是对象地址,重写了就对比equals中的东西。

remove()删除

代码演示

有两种实现删除的方式,一种输入下标值进行删除;一种通过对比值进行删除,但是只会删除遇上的第一个相同值。

源码分析

在这里插入图片描述
(1)判断index是否越界
(2)获得index后面需要往前移动的个数
(3)对index后面的值往前移动一个位置
(4)将size位设置为null,并且将size-1
(5)返回删除的数值
在这里插入图片描述
在这里插入图片描述
(1)判断输入的值是否为null确定对比的方式
(2)对比找到相同的值的下标
(3)将相同值后面的数据往前移动一个单位
(4)将size位设置为null,并且将size-1
(5)返回ture/false

toArray()转换为数组

代码演示

在这里插入图片描述

addAll() 把另一个容器所有对象都加进来

代码演示

注:另一个容器必须是Collection子类。
在这里插入图片描述
在这里插入图片描述
有两种实现方式,一种是直接在ArrayList最后面加入另一个数组中的全部值,还有一个是在ArrayList指定下标后加另一个数组中的全部值。

源码分析

在这里插入图片描述
(1)使用toArray()将容器的值放入Object数组中
(2)获得数组长度
(3)判断加入数组中的之后有没有可能ArrayList中的下标越界,可能就扩容
(4)对数组的值进行拷贝,返回true/false
在这里插入图片描述
(1)使用toArray()将容器的值放入Object数组中
(2)获得数组长度
(3)判断加入数组中的之后有没有可能ArrayList中的下标越界,可能就扩容
(4)将index后面的数据移动要插入数据的个数
(4)对数组的值进行拷贝,返回true/false

与vector的对比

Vector的实现其实和ArrayList的底层实现很类似,都是封装了一个Object[],但Vector是一个比较古老的集合,JDK1.0就已经存在,建议不要使用这个集合,Vector与ArrayList的主要区别是:Vector是线程安全的,ArrayList是非线程安全的,但性能上Vector比ArrayList低。
给出以下几点总结:
1、Vector有四个不同的构造函数。 无参构造的容量默认值为10
2、扩充容量的方法ensureCapacityHelper。与ArrayList不同的是,Vector在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时,就看构造方法中传入的容量增长系数CapacityIncrement是否为0,如果不为0,就设置新的容量为 旧容量 + 容量增长量;如果为0,设置新的容量为旧的容量的2倍,如果设置后的容量还不够,则直接新的容量设置为 旧容量 + 传入参数所需要的容量 而后同样用Arrays.copyof()方法将元素拷贝到新的数组。
3、很多方法都加入了synchronized同步语句,来确保线程安全。
4、Vector在查找给定元素索引值等方法中,源码都将该元素的值分为null和不为null两种情况处理,Vector中允许元素为null
5、其他很多地方与ArrayList实现大同小异,Vector现在已经不再使用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值