ArrayList 你了解这些吗?
本文参考自:
ArrayList
是如何扩容的?
初始容量为 10,第一扩容就为 10。
以后每次的扩容都是原容量的 1.5 倍。
ArrayList
继承关系
1 |
|
实现Serializable
标记型接口
序列化:将对象转化为字节数组的过程
反序列化:将字节数组转化为对象的过程
实现Cloneable
标记型接口
克隆:将ArrayList
集合的数组clone到另一个集合
实际底层调用的是Object中的clone
方法,而clone
是一个native
方法
1 |
|
使用了Arrays#copyOf
方法进行复制
1 |
|
浅拷贝:基本数据类型可以达到完全复制,引用数据类型拷贝的是栈上的地址值,所以修改引用类型的数据,会改变原来的数据。
深拷贝:基本数据类型和引用类型都可以完全复制,引用对象在堆中创建新的对象,对原数据没有任何影响。
深拷贝的实现方式:
实现Cloneable
接口并重写clone
方法。
让其clone
方法通过序列化和反序列化的方式来生成一个原对象的深拷贝副本
实现RandomAccess
标记型接口
ArrayList
支持随机访问。
随机访问:直接
first+N
,便可以得到第N
个元素的地址,因为这些相邻元素是按顺序连续存储的。
比如普通数组就是可随机访问的。文件随机访问是指在某个文件内直接读写任何给定位置数据的能力。
通过get(i)
即可获得相应内存中存放的值。原因是因为ArrayList
存放的内容在内存中是连续的,数组直接用[]
访问,相当于直接操作内存地址,所以随机访问的效率较高。
普通的for循环是随机访问的,所以遍历ArrayList
使用普通for
循环比增强for
循环和迭代器的效率高。
而LinkedList
是一个双向链表,链表只能顺序访问,不支持随机访问,LinkedList
中的get
方法是按照顺序从列表的一端开始检查,直到找到要找的地址。所以遍历LinkedList
使用增强for
循环和迭代器的效率高,使用普通for
循环会每次都从头开始遍历,效率较差。
AbstractList
抽象类
AbstractList
虽然是抽象类,但其内部只有一个抽象方法 get
:
1 |
|
从字面上看这是获取的方法,子类必须实现它,一般是作为获取元素的用途,除此之外,如果子类要操作元素,还需要重写 add
、set
、 remove
方法,因为 AbstractList
虽然定义了这几个方法,但默认是不支持的。
ArrayList
频繁扩容导致添加性能急剧下降,如何处理?
每次扩容都会创建一个数据,将数据复制到新数组,所以在ArrayList
中有一个构造方法,参数是自定义长度,指定初始容量。
1 |
|
ArrayList
插入或删除元素一定比LinkedList
慢吗?
不一定,LinkedList
其底层调用了 Node 的方法,该方法循环也是比较复杂的。如果这个 LinkedList
上的数据很多,虽说进行了折半的 但是效率也是比较低的。
而 ArrayList
底层数组需要移动位置,复制数组。
ArrayList
是线程安全的吗?
ArrayList
不是线程安全的,效率高。
如何解决ArrayList
线程安全问题?
可以使用安全集合
Vector
。可以使用
Collections
工具类中的SyschronizedList
方法解决ArrayList
的线程安全问题。
定义为全局变量,被多个线程所共享,就要考虑线程问题。
定义为局部变量时,调用方法会在虚拟机栈处创建一个栈帧,虚拟机栈线程私有的,每一次只有一个线程执行,所以局部变量的数据是独立的,不需要考虑安全问题。
如何复制一个ArrayList
集合到另一个ArrayList
集合中?
可以使用 clone() 方法
使用其中的构造器
使用 addAll() 方法
for 循环遍历复制
已知成员变量集合存储N多用户名称,在多线程的环境下,使用迭代器在读取集合数据的同时如何保证还可以正常的写入数据到集合?
在多线程读写操作,ArrayList
会抛出并发异常,所以在进行读写数据时,使用读写的操作时,使用CopyOnWriteArrayList
这个读写分离的集合。
ArrayList
和LinkedList
区别?
ArrayList
基于动态数组的数据结构
ArrayList
支持随机访问查询快,增删慢,但并不一定比
LinkedList
慢
LinkedList
(双向链表)
基于链表的数据结构
对于顺序操作,
LinkedList
不一定比ArrayList
慢查询慢,增删快