List类知识点总结
LinkedList
链表集合,继承AbstractSequentialList
,实现Deque
、Cloneable
、Serializable
。
- 实现
Deque
,可以当作双端序列使用 Cloneable
:可被克隆Serializable
:可被序列化
属性
1 | transient int size = 0; // 长度 |
可见LinkedList实现的是双向链表
构造
1 | public LinkedList() { |
方法
方法大全
1 | // Queue接口的方法 |
add
1 | public boolean add(E e) { |
迭代方式
- 随机访问:list.get(i),效率超级低
- 迭代器:list.iterator(),效率最高
- foreach:for(Object i: list),效率其次
ArrayList
动态数组,继承AbstractList,实现List、RandomAccess、Cloneable、Serializable。
- Q:ArrayList继承了
AbstractList
,为什么还要实现List? - A:显式表明
ArrayList
实现了List
,AbstractList
只是为了提升List
实现的代码重用。
RandomAccess
:标记接口,表明List的某个实现支持随机访问。Cloneable
:可被克隆``Serializable
:可被序列化
不是线程安全,多线程可以选择Vector
或CopyOnWriteArrayList
。
属性
1 | // 保存数据的数组 |
Q:elementData
需要序列化,为什么还要被transient
修饰,被transient
修饰了,为什么还能被序列化?- A:
elementData
是一个缓存数组,通常会预留容量,所以elementData
中只有实际存放的元素需要被序列化,故被transient
修饰防止所有元素被序列化;ArrayList
中元素还能被序列化是因为它重写了writeObject()
方法(debug看到这个方法是被反射获取调用的)。
1 | private void writeObject(java.io.ObjectOutputStream s) |
构造
1 | // 不可变的空数组 |
- 第一个构造函数会将
elementData
初始化为空数组 - 第二个构造函数会将
elementData
初始化为指定大小的数组
细心的同学一定会发现 :以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。
方法
方法大全
1 | boolean add(E e) // 尾部添加元素 |
add/扩容机制
1 | public boolean add(E e) { |
当添加元素时,会调用如下方法,若elementData
为空数组,则将它扩展为默认长度(10
)的数组;若添加元素后elementData
长度不够,将会将elementData
扩展为原来的1.5
倍。
1 | // 默认容器大小 |
具体逻辑
如果通过元素值添加,确保容器能装下;如果通过索引添加,之前还需要数组越界检查
- 当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了
ensureCapacityInternal()
方法 ,所以 minCapacity 此时为10。此时,minCapacity - elementData.length > 0
成立,所以会进入grow(minCapacity)
方法。 - 当add第2个元素时,minCapacity 为2,此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,
minCapacity - elementData.length > 0
不成立,所以不会进入 (执行)grow(minCapacity)
方法。 - 添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。
- 直到添加第11个元素,
minCapacity
(为11)比elementData.length
(为10)要大。进入grow
方法进行扩容。
同时注意一下如果需要容量大于MAX_ARRAY_SIZE
会进入hugeCapacity()
方法:
1 | private static int hugeCapacity(int minCapacity) { |
remove
1 | public E remove(int index) { |
set
1 | public E set(int index, E element) { |
get
1 | public E get(int index) { |
System.arraycopy()和Arraya.copyOf()
联系:
看两者源代码可以发现 copyOf() 内部实际调用了 System.arraycopy()
方法
区别:
arraycopy()
需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf()
是系统自动在内部新建一个数组,并返回该数组。
ensureCapacity
ArrayList 源码中有一个
ensureCapacity
方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的
1 | /** |
最好在 add 大量元素之前用 ensureCapacity
方法,以减少增量重新分配的次数
迭代方式
- 随机访问:list.get(i),效率最高
- 迭代器:list.iterator()
- foreach:for(Object i: list)
Collections工具类
Collections 工具类常用方法:
- 排序
- 查找,替换操作
- 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)
排序操作
1 | void reverse(List list)//反转 |
查找替换操作
1 | int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的 |
同步控制
Collections提供了多个synchronizedXxx()
方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。
Collections还可以设置不可变集合,提供了如下三类方法:
1 | emptyXxx(): 返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是Set,还可以是Map。 |
Arrays
常见操作
- 排序 :
sort()
- 查找 :
binarySearch()
- 比较:
equals()
- 填充 :
fill()
- 转列表:
asList()
- 转字符串 :
toString()
- 复制:
copyOf()
方法
sort
1 | // *************排序 sort**************** |
binarySearch()
1 | // *************查找 binarySearch()**************** |