我真服了!面试问完ArrayList,只涨薪5k,拉倒吧!!!

前言

ArrayList猜想应该所有Java的小伙伴都用过,如果还有小伙伴没用过,请三联,你放学给我留下来我给你补习补习。本文是集合类讲解的第一篇,选择了一个相对比较简单、大家又比较熟悉的ArrayList开篇。集合是Java中非常重要而且基础的内容,因为任何数据必不可少的就是该数据是如何存储的,集合的作用就是以一定的方式组织、存储数据。

憋扯犊子了,说说学习集合我要注意哪几点吧!

对于集合,我认为关注的点主要有以下四点

  • 是否允许空
  • 是否允许重复数据
  • 是否有序,有序的意思是读取数据的顺序和存放数据的顺序是否一致
  • 是否线程安全

可以说下ArrayList怎么用的吗?

首先看一下他的新增元素的方法add(),非常简单,代码如下:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);

看着是挺简单的,那就说下新增在底层是怎么实现的吧!

…这么突然的吗,直接肝到原理了,好吧,来来来

add方法的源码来看一下:

image.png

第2行的ensureCapacity方法是扩容用的,占时先不看。底层实际上在调用add方法的时候只是给elementData的某个位置添加了一个数据而已,用一张图表示的话是这样的:【领取资料】

图片

这里需要提醒一下,elementData中存储的应该是堆内存中元素的引用,而不是实际的元素,花Gie这么画图主要是为了小伙伴们理解,只要知道这个问题就好了。

狗剩子:那ensureCapacity这个扩容方法是什么原理呢?

哟呼,狗子今天当了面试官,还知道追问了。那我们先看一下,构造ArrayList的时候,默认的底层数组大小是10:

image.png

既然固定了大小,那底层数组的大小不够了怎么办?狗子都知道那就是扩容,这也就是为什么一直说ArrayList的底层是基于动态数组实现的原因,动态数组的意思就是指底层的数组大小并不是固定的,而是根据添加的元素大小进行一个判断,不够的话就动态扩容,扩容的代码就在ensureCapacity里面:

 private void grow(int minCapacity) {
     //1\. 获取数组长度
     int oldCapacity = elementData.length;

     //oldCapacity >> 1 相当于除以2
     //2\. 新数组容量=原数组容量 * 1.5。
     int newCapacity = oldCapacity + (oldCapacity >> 1);

//vx小助手:xiehuangbao1123 领取资料面试真题
     //3\. 如果新的数组容量小于传入的参数要求的最小容量minCapacity,那么新的数组容量以传入的容量参数为准。
     if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;

     //4\. 判断新的数组容量newCapacity是否大于数组能容纳的最大元素个数 MAX_ARRAY_SIZE
     if (newCapacity - MAX_ARRAY_SIZE > 0)
         //5.
         newCapacity = hugeCapacity(minCapacity);

     //6\. 将扩容前数组放进新的扩容后的数组
     elementData = Arrays.copyOf(elementData, newCapacity);
 }

其中第 5 步hugeCapacity(minCapacity)用于判断传入的参数minCapacity是否大于MAX_ARRAY_SIZE,如果minCapacity大于MAX_ARRAY_SIZE,那么newCapacity等于Integer.MAX_VALUE,否者newCapacity等于MAX_ARRAY_SIZE

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
        
//vx小助手:xiehuangbao1123 领取资料面试真题
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

感觉很简单的扩容方式,那为什么要使用这种方式扩容呢?

大佬们定的,我能回答出来怎么扩容的还不够强么,还问为啥,你是十万个为什么嘛。

回答对了工资加五百【领取资料】

这样呀,那这个就好办了。

我们可以想:

1、如果一次性扩容扩得太大,必然造成内存空间的浪费

2、如果一次性扩容扩得不够,那么下一次扩容的操作必然比较快地会到来,这会降低程序运行效率,要知道扩容还是比较耗费性能的一个操作

所以扩容扩多少,是JDK开发人员在时间、空间上做的一个权衡,提供出来的一个比较合理的数值。最后调用到的是Arrays的copyOf方法,将元素组里面的内容复制到新的数组里面去:

image.png

用一张图来表示就是这样的:

带图示的就很棒,给你一个么么哒,除了顺序添加元素,肯定有按照下标插入的咯

你可真是个机灵鬼,ArrayList的插入操作调用的也是add方法,比如:

List<String> list = new ArrayList<>();
list.add("111");
list.add("222");
list.add("333");
list.add("444");
list.add("555");
list.add("666");
list.add("777");
list.add("888");
list.add(2,"000");
System.out.println(list);

有一个地方不要搞错了,第10行的add方法的意思是,往第几个下标插入数据,像第10行就是在下标为2的位置插入数据000(注意ArrayList下标从0开始,即list.get(0)的值是111)。看一下运行结果也证明了这一点:

[111, 222, 000, 333, 444, 555, 666, 777, 888]

还是看一下插入的时候做了什么:【领取资料】

​ 可以看到插入的时候,先用ensureCapacity方法进行判断是否扩容按照指定位置,然后利用System.arraycopy方法从指定位置开始的所有元素做一个整体的复制,向后移动一个位置,然后指定位置的元素设置为需要插入的元素,完成了一次插入的操作。用图表示这个过程是这样的:

:哟~不错哦,那再说说删除元素吧!

ArrayList支持以下两种删除方式:

  • 按照下标删除,即list.remove(1)

  • 按照元素删除,使用方式为list.remove(“111”),如果有多个等值的元素111,也只是会删除匹配的第一个元素

从代码来看,这两种删除方法在ArrayList的实现原理差不多,都是调用的下面一段代码:

简单概括其实做的事情只有两件:

  1. 把指定元素后面位置的所有元素,利用System.arraycopy方法整体向前移动一个位置

  2. 最后一个位置的元素指定为null,这样让gc可以去回收它

比方现在操作这一段代码:

List<String> list = new ArrayList<>();
list.add("111");
list.add("222");
list.add("333");
list.add("444");
list.add("555");
list.add("666");
list.add("777");
list.add("888");
list.remove("3333");

用图表示是为:

那你总结一下ArrayList特点呗!

ArrayList应该是小白到大佬都非常常用的集合类,它是一个以数组形式实现的集合,花Gie这里用一张表格先来看一下ArrayList里面有哪些基本的元素:【领取资料】

元 素作 用
private transient Object[] elementData;ArrayList是基于数组的一个实现,elementData就是底层的数组
private int size;ArrayList里面元素的个数,这里要注意一下,size是按照调用add、remove方法的次数进行自增或者自减的,所以add了一个null进入ArrayList,size也会加1

ArrayList属性一览:

属 性结 论
ArrayL ist是否允许空允许
ArrayList是否允许重复数据允许
ArrayList是否有序有序
ArrayList是否线程安全非线程安全

说了那么多,那ArrayList有啥优缺点呀

任何事物都不是完美无瑕的,我们要结合场景合理使用,ArrayList的优点如下:

  1. ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快
  2. ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已

不过ArrayList的缺点也十分明显:

  1. 删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

  2. 插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

因此,简单总结就是:ArrayList比较适合顺序添加、随机访问的场景

拓展部分

这里有个疑问困扰了我好久了呢,为什么ArrayList的elementData是用transient修饰的?

这个差点超出了我的认知,还好昨晚看过秘籍宝典。

我们看一下ArrayList的定义:

ArrayList实现了Serializable接口,也就是说ArrayList是可以被序列化的,而用transient修饰elementData意味着我不希望elementData数组被序列化。

这一万个草泥马奔腾而过,不能慌?因为序列化ArrayList的时候,elementData未必是满的,比方说elementData有10的大小,但是我只用了其中的3个,那么是否有必要序列化整个elementData呢?显然没有这个必要,因此ArrayList中重写了writeObject方法:
【领取资料】
image.png

每次序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然后遍历elementData,只序列化那些有的元素,这样就会有两点好处:

  1. 加快了序列化的速度

  2. 减小了序列化之后的文件大小

总结

ArrayList的重要性大家应该都懂,这里也就不啰嗦了。我们在读源码的时候,其实有很多可以值得我们借鉴,比如elementData使用transient来修饰,学习中需要多思考,把学习到的技术和思想运用到自己实际开发中,学以致用,才能不断强大。

最后,感谢大家的观看,谢谢大家的支持,能三连关注收藏就最好啦,祝各位面试必过,升职加薪,早日升职加薪!



  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 25
    评论
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛战士从不脱下面具

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值