ArrayList 介绍
ArrayList是一种线性数据结构,它的底层是用数组实现的,是动态数组。与Java中的数组相比,它的容量能动态增长。源代码里有解释。当创建一个数组的时候,就必须确定它的大小,系统会在内存中开辟一块连续的空间,用来保存数组,因此数组容量固定且无法动态改变。ArrayList在保留数组可以快速查找的优势的基础上,弥补了数组在创建后,要往数组添加元素的弊端。好了不说太多,咱们的目标是源码实现。往下看。。。
底层实现:
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。 ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。 ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。 ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
![](https://i-blog.csdnimg.cn/blog_migrate/86d8873e243768c98365876d14fbf6ae.png)
直接上源码 下图一步一步解析
![](https://i-blog.csdnimg.cn/blog_migrate/2eeb9b9de811104432cfdb0b14d56d96.png)
我们只需记住该默认容量的大小为 10 即可
![](https://i-blog.csdnimg.cn/blog_migrate/6594099a9e0a87aa5ee308862f6618cc.png)
上图是共享的空数组实例用于默认大小的空实例。我们将其与EMPTY_ELEMENTDATA区分开来,以便知道添加第一个元素时要膨胀多少。
数组有下标的概念,默认大小是10 的情况下,它的下标就是 0 - 9.
![](https://i-blog.csdnimg.cn/blog_migrate/ee92fdb70777b8cb12533c94c0e71b20.png)
这里定义的动态就是ArrayList的优势,所以它才会在有数组优点的基础上,又是可变大小。
![](https://i-blog.csdnimg.cn/blog_migrate/0d9c4155d5de7e3bda5530f166916c65.png)
每添加一个元素,这里的size 就会++, +1 操作; size 就代表当前的元素数量。
![](https://i-blog.csdnimg.cn/blog_migrate/ec0b51e23c732789f7dc13d16a22a77d.png)
这两个为一部分 ,没截图完整。
minCapacity—期望的最小容量,这里才发现表示的是期望容量!!!
上述注释的意思为增加容量,以确保它至少能容纳由最小容量参数指定的元素数量。
这个函数就表示了为什么扩容机制为1.5倍,再接着看
![](https://i-blog.csdnimg.cn/blog_migrate/3247dfd72746d0596119f9b460669d44.png)
这个函数表示了巨大的容量 传入了一个你期望的容量,经过判断 如果容量为 0 就抛出内存异常
接着又是三目运算符,条件为如果期望容量大于Array的最大值 这个表达式为true 就返回Integer 的最大值 否则返回Array长度的最大值。
到现在就解析完成了ArrayList 的底层如何实现 啊 好累啊 如需让我补充 敬请评论 互相交流学习
之后源码的方法 ,也就是封装的方法,增加删除等等。
接下来呢 大家看看我总结了ArrayList 的面试题
1. DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA都是空数组,两者有何区别?
答:两个是用来共享给空数组的,主要用来起区分作用。
无参构造器构造的空数组会用DEFAULTCAPACITY_EMPTY_ELEMENTDATA给elementData赋值,有参构造器构造的空数组会用EMPTY_ELEMENTDATA给elementData赋值。
争对不同的构造器创建的ArrayList,在扩容策略上稍有不同。在扩容时,会判断elementData是由无参构造器创建还是有参构造器创建,从而选择对应策略进行扩容。
2. ArrayList是如何扩容的? (答案在博客内部)
3. 如果要在ArrayList中添加1w条数据,如何提高效率?(高频)
答:如果确定要添加1w条数据,可以在初始化时就指定ArrayList开辟1w的容量。这样以来,虽然一开始开辟空间可能会稍微耗费一些资源,但是规避了之后ArrayList进行多次扩容的操作。
4. 由于ArrayList实现了fail-fast机制,那么在遍历ArrayList时,使用类提供的remove方法真的就不能移除元素吗?
在fail-fast的定义中,强调了fail-fast只是有可能被触发,在知道fail-fast具体的实现原理后,有两个思路:
- 保证迭代器版本号不变。
- 不让匹配版本号的方法执行。
第一种方式:首先迭代器版本号的存在意味着使用迭代器来进行遍历,保证迭代器版本号不变意味着在迭代器遍历的过程中不能进行类提供的remove进行移除操作。所以可以先将目标元素的引用赋值给其他变量,等迭代器遍历结束后,在单独使用类提供的remove方法进行删除。
接着看第二种方式:不让匹配版本号的方法执行,意味着不能使用迭代器进行遍历。所以可以使用数组遍,遍历的同时就可以使用类提供的remove方法进行删除。
但是以上两种方法都不是正常的删除操作,只能说是钻fail-fast机制存在的漏洞。
5. ArrayList是线程安全的吗?如果不是,如何做到线程安全?
在讨论ArrayList的线程安全时,一般都会和Vector一起讨论。ArrayList是线程不安全的。即使有fail-fast机制的存在,也只是尽量保证ArrayList的线程安全。
Vector类是JDK1.0给出的类,也实现了动态数组,和ArrayList很相似,但是两者又是不同的。主要的不同点在于:
- Vector是线程安全的。
- Vector的扩容策略。
Vector 为什么线程安全但是 使用时不安全, 因为底层的增删改查 都加了锁 但是我如果一边修改这个值,但是另外一个线程可以进入完成删除操作。 所有还是会报错
面试题暂时就是这些,之后还会一直更新。 谢谢大家
大家再见!!!