List源码解析
本篇文章有点长,所以先列个目录
List源码解析
1、ArrayList
2、LinkedList
3、vector
4、总结
在上一篇文章中,我们对Collection接口有个一个总体的了解,这篇文章我们将会深入的学习实现Collection的一个重要子接口---List
首先,我们需要看一下List的「继承链」,它有哪些具体的实现类,
在这里,我们会主要讲解ArrayList、LinkedList和Vector
1、ArrayList
ArrayList使用非常广泛,它也是我们使用频率最高的一个集合(很多情况下不考虑场景直接使用),
- 数据结构
ArrayList的底层数据结构是一个存储Object类型的数组,我们对ArrayList的操作都是基于数组的。
1.1成员变量
❝long serialVersionUID:标识码,用于序列化和反序列化
transient object[ ] elementData:不可被序列化的数组,只能存在于内存中
int size:ArrayList中存放元素的个数,默认为0
int DEFAULT_CAPACITY:初始化的数组的容量,默认为10
object[ ] EMPTY_ELEMENTDATA:当ArrayList为空时,将该数组赋值elementData数组
object[ ] DEFAULTCAPACITY_EMPTY_ELEMENTDATA:当构造器未指明数组的长度时,默认使用该数组
final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;最大数组的容量,-8的原因是因为要去掉header Words,防止内存溢出
❞
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
1.2构造方法
❝AyyayList():构造一个初始容量为0的空列表
❞
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
❝ArrayList(int initialCapacity):以一个确定的容量构造数组列表,当初始容量>0,直接构造该容量的列表,当初始容量=0,将 EMPTY_ELEMENTDATA赋给elemData,初始容量<0,则抛出异常
❞
public ArrayList(int initialCapacity) {
//当初始容量>0,直接构造该容量的列表
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//当初始容量=0,将 EMPTY_ELEMENTDATA赋给elemData
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//初始容量<0,则抛出异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
❝ArrayList(Collection<? extends E> c):构造一个包含指定集合元素的列表,其顺序由集合的迭代器返回
❞
public ArrayList(Collection<? extends E> c) {
//将集合转为数组
elementData = c.toArray();
//判断数组的长度
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
1.3 add方法
❝add(E e):在列表的尾部增加元素e,我们具体分析下add方法的执行流程
❞
❝在添加元素e时,先调用函数ensureCapacityInternal(size + 1),目的保证数组内的容量可用,它会首先计算传入的容量与默认容量的的大小,取最大值,如果传入的容量比默认容量大,则需要扩容,扩为元容量的1.5倍,扩容后继续比较取最大值。
对下面的代码进行详细了注释,读者可按照步骤「一二三四五」的顺序理解即可
❞
public boolean add(E e) {
//步骤一
//size为添加前数组的容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//添加元素到相应位置,元素数量+1
elementData[size++] = e;
return true;
}
//步骤二
//确保内部的容量
private void ensureCapacityInternal(int minCapacity) {
//步骤三、四
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//步骤三
//计算最小需要的空间
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断传入的容量与原有容量的大小,返回两者之间最大的
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//步骤四
//判断是否需要扩容
//如果最小需要的空间比原有的elementData的内存空间要大,则扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//步骤五
//扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩容为原有容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
❝add(int index,E element):在确定的位置插入元素e,首先判断index位置是否越界,然后检查数组容量是否够用,不够则需要进行扩容,最后进行数组的复制来完成数组的添加
❞
public void add(int index, E element) {
//判断位置是否越界
rangeCheckForAdd(index);
//确保容量可用
ensureCapacityInternal(size + 1); // Increments modCount!!
//数组的复制
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
1.4 set方法
❝set(int index, E e):将指定位置的元素更新为e,首先它会先检查index是否越界,找到该位置的替代元素并替换成新元素,返回被替换的值。
❞
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
1.5 get方法
❝get(int index):获取指定位置的元素值
❞
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
1.6 remove方法
❝remove(int index):删除指定位置的的元素,将指定下标后面的的一位到数组的末尾全部元素向前移动一个单位,并且把数组最后一个元素设置为null
❞
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;
}
1.7indexOf(Object o)
❝indexOf(Object o):返回指定元素在列表中首次出现的索引位置,如果此列表不包含该元素,则返回-1
❞
public int indexOf(Object o) {
//ArrayList中的值可以为null
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
//采用equals()方法比较
if (o.equals(elementData[i]))
return i;
}
return -1;
}
1.8其它方法
❝contains(Object o):判断列表中是否包含对象o
❞
public boolean contains(Object o) {
return indexOf(o) >= 0;
❝Object[] toArray():转换成对象数组
❞
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
1.9总结
- ArrayList的底层使用数组实现的,可以进行动态扩容,遍历和访问元素时,效率很高,可以存放null值
- 插入和删除元素时效率较低,线程不安全
2、LinkedList
LinkedList是一个用「双向链表」实现的List,除此之外,它还可以作为队列和栈使用,为了接下更好的分析LinkedList,我们先回顾下双向链表的一些常用操作
- 双向链表的中间删除
S->pre->next=S->next
S->next->pre=S->pre
- 双向链表的中间插入
D->pre=B
D->next=C
C->pre=D
B->next=D
- 双向链表的末尾删除
P->pre->next=P->next
- 双向链表的末尾添加
B->next=C
C->pre=B
- 链表的遍历
PersonNode temp = headNode;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
System.out.println(temp);
}
复习了链表的基本操作后,我们来学习LinkedList的源码
2.1成员变量
❝int size:结点个数
Node first:指向头结点指针
Node last:指向尾结点的指针
❞
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
2.2构造方法
❝LinkedList():构造一个空链表
❞
public LinkedList() {
}
❝LinkedList(Collection<? extends E> c):根据指定集合c构造LinkedList,先构造一个空linkedlist,在把指定集合c中的所有元素添加到linkedlist,如果c为null,则抛出异常
❞
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
2.3link..方法
❝linkFirst(E e):在表头插入元素
❞
private void linkFirst(E e) {
//使结点f指向原来的头结点
final Node<E> f = first;
//新建结点newNode,结点的前指针指向null,后指针指向原来的头结点
final Node<E> newNode = new Node<>(null, e, f);
//头指针指向新结点
first = newNode;
//如果原来的头结点为null,更新尾指针,否则使原来的头结点f的前置指针指向新的头结点newNode
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
❝linkLast(E e):在链表尾部插入元素,分析方法和linkFirst类似
❞
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++;
}
❝linkBefore(E e,Node<E> succ):在非空节点succ前插入元素e
❞
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
2.4unlink...方法
❝unlinkFirst(Node<E> f):删除头f并返回
❞
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
❝unlinkLast(Node<E> l):删除尾结点l并返回
❞
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
❝unlink(Node<E> x):删除指定结点x
❞
E unlink(Node<E> x) {
......
}
2.5 add方法
❝add(E e):在列表尾部插入元素e
❞
public boolean add(E e) {
linkLast(e);
return true;
}
❝add(int index,E element):在指定的位置插入元素
❞
public void add(int index, E element) {
//检查是否越界
checkPositionIndex(index);
//判断index与列表的大小
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
2.6 remove方法
❝remove(Object o):移除首先出现对象o的元素
❞
public boolean remove(Object o) {
......
}
❝remove(int index):在指定位置删除元素并返回
❞
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
❝remove():移除表头元素
❞
public E remove() {
return removeFirst();
}
2.7 get方法
❝get(int index):获取指定位置(index)的元素
❞
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
2.8 set方法
❝set(int index,E element):在指定位置更新元素,返回被替换的元素
❞
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
2.9其他方法
//获取列表的第一个元素
public E getFirst() {......}
//获取列表的最后一个元素
public E getLast() {......}
//从列表中移除并返回第一个元素
public E removeFirst() {......}
//从列表中移除并返回最后一个元素
public E removeLast() {......}
//在表头插入元素
public void addFirst(E e) {
linkFirst(e);
}
//在表尾插入元素
public void addLast(E e) {
linkLast(e);
}
//列表中是否包含对象o
public boolean contains(Object o) {
return indexOf(o) != -1;
}
//列表中元素的数量
public int size() {
return size;
}
2.10总结
LinkedList的底层数据结构是用链表实现的,
「要重点理解LinkedList中的link...方法(1.3)和unlink...方法(1.4),因为我们常用的add,set,get,remove方法都是在此基础上调用得来的。」
LinkedList内的元素可重复,增添、删除效率高,查找效率低,它是线程不安全的,如果要使得线程安全,可以使用Collection中的方法
List list = Collections.synchronizedList(new LinkedList(...));
3、vector
vector的底层数据接口同ArrayList一样,都是采用数组来实现的。所以它们具有「查找快、增删慢」的特点,当添加的元素大于所给的容量时,都会进行扩容。
3.1成员变量
❝Object[] elementData:保存vector中元素的数
elementCount:vector实际元素的个数
capacityIncrement:vector自动扩容是需要增加的容量
serialVersionUID:标识符,序列化和反序列化
❞
protected Object[] elementData;
protected int elementCount;
protected int capacityIncrement;
private static final long serialVersionUID = -2767605614048989439L;
3.2构造方法
❝Vector():构造一个容量为10、自增容量为0的Vector
❞
public Vector() {
this(10);
}
❝Vector(int initialCapacity):构造一个指定容量为initialCapacity、自增容量为0的vector
❞
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
❝Vector(int initialCapacity, int capacityIncrement):构造一个指定容量为capacity、自增容量为capacityIncrement的Vector
❞
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
❝Vector(Collection<? extends E> c):构造一个Vector,该Vector包含指定集合的元素,其顺序由集合的迭代器返回
❞
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
3.3 add
❝add(E e):在vector尾端添加元素,并将其实际数量+1,如果增加后的容量大于vector本身的容量,则需要扩容
❞
❝「我们还是以add方法来讲解扩容机制」,请看下面的程序解析
❞
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
//如果至少需要的容量>数组缓冲区当前的长度,就进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容方法,保证vector至少能存储minCapacity个元素
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//首先扩容
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
//容量小于minCapacity,直接将容量增为minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
///如果扩容后的容量大于临界值,则进行大容量分配
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
//进行大容量分配
private static int hugeCapacity(int minCapacity) {
//如果minCapacity<0,抛出异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果想要的容量大于MAX_ARRAY_SIZE,则分配Integer.MAX_VALUE,否则分配MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
3.4 set方法
❝set(int index, E element):将指定位置(index)的元素替换成element,并返回被替换的元素
❞
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
3.5 get方法
❝get(int index):获取指定位置(index)的元素
❞
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
3.6 remove方法
❝remove(Object o):移除指定位置的元素
❞
public boolean remove(Object o) {
return removeElement(o);
}
其它方法
❝copyInto(Object[] anArray):将vector中的所有的元素拷贝到指定的数组anArray中去
❞
public synchronized void copyInto(Object[] anArray) {
System.arraycopy(elementData, 0, anArray, 0, elementCount);
}
❝trimToSize():将底层的数组容量调整为当前vector实际元素个数,来释放空间
❞
public synchronized void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (elementCount < oldCapacity) {
elementData = Arrays.copyOf(elementData, elementCount);
}
}
❝ensureCapacity(int minCapacity):增加vector容量,如果vector的当前容量小于至少需要的容量,就增加容量
❞
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
modCount++;
ensureCapacityHelper(minCapacity);
}
}
4、总结
- ArrayList、Vector和LinkedList特点
❝ArrayList访问速度快
Vector与ArrayList相似。但Vector的方法是线程安全的,而ArrayList的方法不是线程安全,由于线程的同步必然要影响性能,因此ArrayList的性能比Vector好
LinkedList随机访问元素慢,顺序访问快,增删元素快。
❞
- 使用的场景选择
❝对插入效率要求高,有LinlkedList
对插入效率要求不高,要求访问效率高使用ArrayList和Vector
❝线程安全Vector
线程不安全ArrayList