List接口
经典口诀: 有序、可重复、有索引
List接口有多个实现类,但是最常用的实现类一般为 ArrayList、LinkedList、Vector
ArrayList
ArrayList的注意事项:
-
ArrayList可以加入null,甚至可以加入多个null
-
List<String> list =new ArrayList<>(); list.add(null);
-
-
ArrayList是由数组来实现数组存储的 -
ArrayList基本等同于Vector,除了ArrayList是线程不安全的(正因为它是线程不安全的,所以它的执行效率高)。在多线程的情况下,不建议使用ArrayList- 查看源码,
ArrayList的源码并没有加上synchronized关键字 -
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
- 查看源码,
ArrayList底层结构和源码分析
序列化
-
ArrayList中维护了一个Object类型的数组elementData。-
transient Object[] elementData;// transient修饰,表示该属性并不会被序列化 -
但是事实上
ArrayList实现了序列化接口,ArrayList实际上是可以被序列化的。由于
ArrayList的数组是基于动态扩增的,所以并不是所有被分配的内存空间都存储了数据。如果采用外部序列化方法实现数组的序列化,会序列化整个数组。ArrayList为了避免这些没有存储数据的内存空间被序列化,内部提供了两个私有方法writeObject以及readObject来自我完成序列化和反序列化。从而在序列化和反序列化数组时节省了空间和时间。因此使用transient修饰数组,是防止对象数组被其他外部方法序列化
私有方法
writeObject以及readObject源码如下:/** * Save the state of the <tt>ArrayList</tt> instance to a stream (that * is, serialize it). * * @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) in the proper order. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } /** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity int capacity = calculateCapacity(elementData, size); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity); ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
-
添加元素、扩容
当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加元素时,则扩容elementData为10,如果需要再次扩容,则扩容elementData为1.5倍
源码为:
- 如果使用的是指定大小的构造器,则初始
elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
如果形参大于0 ,那么之后扩容就会直接扩容为1.5倍,其他源码就是2中的源码。
删除元素
ArrayList删除元素public E remove(int index)
ArrayList每次删除元素,实际上都要进行一次元素的挪动重组,删除元素所在的下标越靠前,那么数组重组的开销就越大
源码如下:
public E remove(int index) {
//判断被删除下标是否合规
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
//数组重组
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
Vector
Vector也是List接口的实现类。
- Vector底层也是一个对象数组,
protected Object[] elementData; - Vector是线程同步的,即线程安全,Vector类的操作方法都带有
synchronized - 在开发中,如果需要线程同步安全时,考虑使用Vector
Vector和ArrayList的比较
| 底层结构 | 版本 | 线程安全(同步)效率 | 扩容倍数 | |
|---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 线程不安全,效率较高 | 如果是有参构造器,那么每次扩容都是原容量的1.5倍。 如果是无参构造器进行创建,那么容量一开始为0,第一次扩容,容量为10,后面每次扩容都扩充为1.5倍 |
Vector | 可变数组Object[] | jdk1.0 | 线程安全,效率不高 | 如果是无参构造器,容量默认为10,满后,就按照2倍进行扩容。 如果有参构造器,指定大小,则每次按照2倍进行扩容。 |
LinkedList
- LinkList底层实现了双向量表和双端队列的特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
LinkedLis
LinkedList底层维护了一个双向链表LinkedList中维护了两个属性first和last分别指向首节点和尾节点- 每个节点(Node对象)里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
LinkedList底层结构和源码分析
LinkedList的成员变量:
first和last均为Node结构,分别指向链表头和链表尾。
Node结构中,每个节点都有一个prev指针指向前一个节点,next指针指向下一个节点。item表示当前节点中存储的值
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
//--------------------
//Node结构源码为
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
LinkedList添加元素
通过源码可以看到,相较于ArrayList的add方法中还要判断容量是否足够,如果不够还要扩容,此外如果ArrayList插入元素是插入到中间位置的话,还需要数组整合开销,LinkedList插入或者移除操作就简洁明了很多。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
ArrayList、Vector、LinkedList三种比较
首先三者都是List接口的实现类,共性是 有序、可重复、有索引。但是三者之间也有所差异
- Vector是Java操起提供的线程安全的动态数组,如果不需要线程安全的话,那么不建议使用Vector,因为同步是有额外开销。Vector内部是适用对象数组来保存数据,会根据需要自动增加容量。(初始创建时,如果没有指定容量,初始容量就是10,后续扩容的话均会扩容到原来的2倍)。
- ArrayList是线程不安全的,正是由于是线程不安全的,所以性能会好很多。同Vector,ArrayList也是对象数组来保存数据,只不过ArrayList如果创建时使用的是无参构造器,那么初始容量是0,一旦加入数据,容量就会扩充为10,如果后续还需要扩容,那么就会扩充到原来的1.5倍。
- LinkedList是Java提供的双向链表,它并不涉及到所谓的扩容,它不是线程安全的。