ArrayList的一些常见知识点

ArrayList是基于数组实现的List类。他可以动态增长和缩减。本文就来总结一下关于ArrayList的面试题, 也是我在之前的面试经历中遇到的。

一、ArrayList的大小是如何自动增加的?

当我们向ArrayList的对象中增加一个对象的时候,Java会去检查ArrayList对象,确保底层的数组中有足够的容量来存储这个新的对象。如果容量不够增加新元素的话,那就会新建一个长度更长的数组(1.5倍的oldCapacity),旧的数组就会使用Arrays.copyOf方法被复制到新的数组中去,现有的数组引用指向了新的数组。

下面进入源码分析一下:

add方法

add方法中第一句调用了:
ensureCapacityInternal(size + 1)
注意这个size是当前数组中实际的元素个数,而ArrayList中的capacity是数组的长度。打个比方,你可以new一个长度为10的数组,那么capacity就是10,但是这个数组中你只赋值了两个位置,那么size=2。为什么这个方法要size+1呢?因为我们要添加一个新元素啊,当前有size个元素,那么必须要保证数组至少size+1大小才能容得下新添加元素。进入ensureCapacityInternal(size + 1)看看:

ensureCapacityInternal方法

调用了ensureExplicitCapacity(int minCapacity)方法,参数是calculateCapacity(Object[] elementData, int minCapacity)。这个方法就是计算一个为了添加新元素的最小容量:

下面是ensureExplicitCapacity(int minCapacity)方法:

方法名字也很好理解,就是确保显式的容量。首先增加ArrayList对象的修改次数。然后如果minCapacity-elementData.length>0。说明容量不够了,就调用grow方法去扩容。然后我们就看grow方法是如何扩容:

可以看到新的容量:int newCapacity = oldCapacity + (oldCapacity >> 1);即1.5倍的oldCapacity。然后用Arrays.copyof()方法把原数组拷贝,最后会用到native的数组拷贝方法。这里注释是overflow-conscious code,表明是考虑到溢出的代码。解释一下:

note: 我们始终要记得哦,传进来的minCapacity是size+1。

1.正常情况下,每次capacity扩容1.5倍。当capacity扩大1.5倍发生int的溢出时,就每次进行最小扩容(+1)就可以了。第一个判断 if (newCapacity - minCapacity < 0)这个就是为这个溢出准备的。

  1. 当按照1步骤不断往下走,最后也有可能达到上限值对吧。此时
    发生在第二个判断 if (newCapacity - MAX_ARRAY_SIZE > 0)时,其中MAX_ARRAY_SIZE= Integer.MAX_VALUE-8。这时候就要调用hugeCapacity方法啦:

至此添加元素之前的准备工作(验证数组大小,必要时的扩容)都做完了。

by the way,提一下几种数组赋值的效率问题:

1、System.arraycopy()方法
2、Arrays.copyOf()方法
3、for循环,手动复制
4、clone()方法(效率最低,而且是浅拷贝)

二、ArrayList or LinkedList如何选择

访问操作多,插入删除操作少选择 ArrayList。get元素和set元素的时间复杂度O(1)。插入操作默认在数组末尾添加元素,不考虑扩容是O(1)的时间复杂度,在指定位置添加元素,最坏O(n)的时间复杂度。

访问操作少,插入删除操作多选择LinkedList。

三、线程不安全

两个方面:

3.1 add操作

上面我们看add元素的源码时,有这么一句:

elementData[size++] = e;

这句不是一个原子操作,可以分为如下两步执行:

elementData[size] = e;
size++;

多线程的情况下可能会导致一个线程覆盖另一个线程的值。时序图如下:

3.2 扩容时

ArrayList 默认数组大小为 10。假设现在已经添加进去 9 个元素了,size = 9。

  1. 线程 A 执行完 add 函数中的ensureCapacityInternal(size + 1)挂起了。

  2. 线程 B 开始执行,校验数组容量发现不需要扩容。于是把 新元素E1 放在了下标为 9 的位置,且 size 自增 1。此时 size = 10。

  3. 线程 A 接着执行,尝试把 E2 放在下标为 10 的位置,因为 size = 10。但因为数组还没有扩容,最大的下标才为 9,所以会抛出数组越界异常 ArrayIndexOutOfBoundsException。

3.3 如果使用线程安全的ArrayList

① 使用synchronize关键字加锁
② 使用Collections.synchronizedList
③ CopyOnWriteArrayList

四、ArrayList对象作为方法的参数。

看如下代码:

import java.util.ArrayList;

public class TestList {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);

        System.out.println("调用方法前:");
        System.out.println(list);
        addlist(list);
        System.out.println("#############");
        System.out.println("调用方法后:");
        System.out.println(list);

    }

    public static void addlist(ArrayList<Integer> list) {
        list.add(5);
    }

}

输出结果:

把list作为实参传递给方法中,方法中修改list,则原来的list也会跟着改变。这个知识其实是Java的值传递和引用传递的知识点。如果不希望影响原来的,可以这么做。

import java.util.ArrayList;

public class TestList {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);

        System.out.println("调用方法前:");
        System.out.println(list);
        addlist(list);
        System.out.println("#############");
        System.out.println("调用方法后:");
        System.out.println(list);

    }

    public static void addlist(ArrayList<Integer> list) {
        list = new ArrayList<>(list);  //重新new一个对象。
        list.add(5);
    }

}

此时结果如下:


大数据与Java学习资源分享:https://zhuanlan.zhihu.com/p/50030125

全部评论

相关推荐

Aurora23:属于挂一半,暂时进池子了,隔一段时间没有其他组捞的话就彻底结束了
点赞 评论 收藏
分享
emmm别问我为啥上一条帖子隔了两个月我才开始投简历和拿offer,因为我懒😰简单流程如下:周一凌晨改好的简历,然后到处乱投简历;周二接到了三维家的一面通知,临时抱佛脚的背了一些八股;周三上午一面下午通知第二天hr面;周四上午hr面下午拿offer,遂收手支线:在BOSS上顺手投了几个大厂,投字节的时候不小心投城客户端了,结果过了一天HR突然把我简历要走了,还问我能不能整客户端,我直接一口答应(脏面评警告😢)结果在周三下午的时候给我打电话,说前端有空缺实习岗,问我有没有兴趣,然后就跟我约了周四下午一面😰我都没咋准备啊,咩都不会啊😭结果周四下午面完,晚上打电话通知过一面了,赶紧把二面约在下周一下午,留点缓冲时间。逆大天了,我一半的问题都不会,他居然给我过了?运气未免有点好了😥现在正在恶补计网、网安、性能优化的东西(这三大板块我是几乎一点不会,一面几乎一点答不出来,加上我又没怎么背八股,这块被干烂了😵)心得体会与经验:1.&nbsp;我giao怎么这么快就结束了,我还以为要找好久😨2.&nbsp;大厂的面试问题真的和中厂小厂很大不同,比如在三维家我能自己吹水到vue的数据劫持、Proxy代理响应式之类的他们就觉得很不错了,但是在字节你但凡敢提到一下就会追问你细节了,一追问马脚就全漏出来了3.&nbsp;有信心真的很重要,我感觉我能拿中厂offer最重要的就是吹水吹出自信来了,以至于三维家面试反问面试官有哪里还需要改进的时候,他就说很不错了解的很多😦4.&nbsp;理解很重要,我从头到尾真没背过很多八股,不过有一些知识确实是敲过代码验证过,所以面试的时候能吹水吹得出来😇想了解面经啥的可以直接评论区问我,但我可能也说不全,因为我没有记录,而且今天摆了一天感觉记忆快清空了😵下面是故事时间:我暑假刚开始的时候才开始准备八股,印象很深那个时候连什么原型、事件循环、闭包这些名词都没听过,资料也不知道怎么找,就一直零零散散的准备,感觉也只有js稍微背了一下八股,其他很多时候都是靠完全理解和手写熟悉一些机制的,但这样做效率很低,反正准备了一个多星期半个月就开摆了😭结果一摆就摆到了开学,笔记是乱七八糟的,八股是忘光光的,简历是一直没改的,实习也是一直没投过的。直到上周日晚上偶然和师兄聊天,他突然问我“你怎么还不找实习”,那天晚上才幡然醒悟,是时候做点事情了😡然后就按照上面描述的来走了。其实我感觉我从头到尾都没背特别多八股,也没怎么找刷题资料啥的,早期就是翻尚硅谷或者黑马的入门视频从头学起,中期用面试鸭看了一点点题,主要是在学js机制和敲js代码,后期才发现了w3c的面经网站,然后在那里看着学(那个时候已经懒得敲了,因为有些问题与代码感觉不像是给找实习的看的,忒细了点😂)接下来继续准备字节二面吧,虽然几乎没啥可能可以通过,但是万一有奇迹呢?😍😍😍也祝大家能够早日拿到心仪的offer
我的offer呢😡:我已经预见10天后你会发,节孝子启动了
投递三维家等公司10个岗位
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务