List表示有序的集合(元素可以重复),根据索引来寻找元素,放入其中的元素的存储顺序和放入顺序是一致的。
ArrayList
0.继承自AbstractList,拥有通用的方法如Iterator迭代器。实现List接口。
1.底层是transient Object[] elementData
数组。可以看到默认大小是10。
2.不同的初始化方式,有一点区别。
1 | // 未指定,默认是10。构造的数组大小为0,要等到放入第一个元素时才会扩容成10个。 |
3.数组扩容
默认初始化的内部数组大小是10,当放入第11个元素时会进行第一次扩容:newCapacity = oldCapacity + oldCapacity >> 1
,也就是变成原来的1.5倍。(所以默认情况下,放入第11个元素时,扩容成15个;放入第16个元素时,扩容成22个;放入第23个元素时,扩容成33个。)
数组扩容的操作是进行数组的复制,所以扩容消耗资源,应该尽量先指明所需的容量,来减少扩容操作。对于已经存在的ArrayList,在放入大量元素前,可以手动进行扩容:ArrayList.ensureCapacity(capacity)
来指定内部数组的大小。
4.有一个骚操作,把ArrayList转化成对象数组:
1 | // 调用toArray方法,传入对象数组接收,返回Object[]数组 |
5.ArrayList的add(int index, Object obj)
和remove(int index) / remove(Object obj)
都会进行数组的复制,所以ArrayList适合随机访问get(int index)
,不适合随机增加、删除操作多的场景。
ArrayList总结
1.初始化时应该指明容量,避免多次的扩容操作。
2.使用场景应该是随机查找比较多而随机增加、删除操作比较少的场景。
LinkedList
1.继承自AbstractSequentialList,拥有通用的方法如iterator。实现List接口。实现Queue接口,拥有队列的特性;实现Deque接口,拥有双端队列的特性。
2.LinkedList内部的节点。拥有前后指针,实现的是双端队列的性质。
内部的私有属性,存储了链表的节点个数以及保存了链表的头尾指针。
3.因为LinkedList实现了Queue接口、Deque接口,所以它既能作为队列也能作为堆栈来使用。又因为实现了List接口,所以又是有序的Collection。这意味着它有3种数据结构的作用,我们应该在正确的场景下正确语义化使用,这表示我们要使用合适的接口来声明LinedList。(因为双端队列Deque接口提供了传统Stack的操作方法声明,所以它实际上是4种数据结构)
4.对于LinkedList的随机访问操作,它内部有一个优化。如果index<(size/2),就从头部开始查找;否则从尾部开始查找。
LinkedList总结
1.使用场景应该是增加、删除操作多的情况,而随机访问操作少的情况。
2.LinkedList有3种数据结构的身份,我们应该在正确的场景下进行正确的接口声明,并且使用与之对应的语义化方法来操作LinkedList。
Vector
vector作为古老的集合类,是jdk1.2之后才改为实现List接口的。它与ArrayList的性质很像(2者的继承图是一样的),但是其内部方法都使用了synchronized修饰来保证线程安全,这使得对Vector的操作在多线程下变成了串行操作。Vector已经不推荐使用了,单线程下我们选用ArrayList,多线程下我们选用CopyOnWriteArray。
1.默认初始化大小为10,扩容操作时,如果构造时指明了增大的容量,则增加;否则默认变成原来的2倍。
Stack
Stack作为Vector的子类,可用于实现堆栈。但是不建议使用,而是使用LinkedList来替代堆栈。