ArrayList和LinkedList的源码分析
前言:日常Android开发中经常用到List集合,一般来说的用ArrayList的情况比较多,但是也许只是用也没有对源码的内容进行太多的分析,今天主要说一下ArrayList与LinkedList的区别.
Collection的子接口以及实现类
常用的子接口是LiST,实现类分类
List接口的实现类有ArrayList、LinkedList、Vector
Set接口常用实现类 HashSet、TreeSet
ArrayList
ArrayList通过名字也能知道,内部是通过数组实现的,接下来主要分析一下其创建,add(),remove(),clear()方法,方便更好的理解与记忆
构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
可以看到初始的创建都是对elementData字段的赋值,说白了就是创建一个空的Object[]
,接下来的所有操作都是围绕这个数组进行操作的.第一个构造方法是赋值一个空的Object[]
,第二个是创建指定大小的数组,第三个是对数组的copy
,以第二个为例可以看到首先对初始输入数值大小进行判断,如果数值大于0,那么就创建一个指定大小的数组,如果等于0就创建一个空数组,如果是其他的情况就抛异常.
Add( )
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
同样Add()
方法也是有两个,对于两个方法基本的四度相同,都是先对size值进行操作,然后赋值新的元素,里面都有ensureCapacityInternal()
方法,接下来看该方法的源码
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
该方法首先对要操作的数组进行判断,如果该数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,那么先对初始值进行赋值,然后走ensureExplicitCapacity()
方法,该方法再进行一次判断之后走grow()
方法,重点来了在grow()
方法中所有的操作都是围绕minCapacity
进行操作的,这里面首先获取以前数组的长度,然后扩容为原来的1.5倍,并与Integer.VALUE
值进行比较,最终结果是调用Arrays.copyOf()
方法对原数组进行拷贝,并扩容数组,至此一个新的元素已经加入到数组中,同时数组的长度也增加了.
Remove( )
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) 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;
}
同add()
本质上都是调用了System.arraycop()
对数组进行操作,生成一个新的数组
clear( )
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
对数组中的所有值赋空,方便GC的回收
size( )
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
public int size() {
return size;
}
直接返回size
值,该值在添加删除等操作的时候会对其修改
小知识点–i,i–,–i是先减后赋值,i–是先赋值后减
LinkedList
LinkedList内部实现的数据接口是双向链表,所有的增删查改操作都是围绕该链表进行操作的
构造方法
在构造方法之前首先说一下链表,链表分为单链表和双向链表,在LinedList中采用的是双向链表,所以基本数据结构为前后两个头结点加一个数据.LinkedList有一个私有静态类的链表,具体看下面源码,然后重要的是记住内部的两个头结点first,last
/*两个头结点,用于只是链表的头和尾*/
/**
* 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;
/*私有的节点类*/
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;
}
}
/*连个构造方法*/
public LinkedList() {
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
我们可以看到第二个构造方法同样调用了第一个构造方法,所以就那第二个构造方法来说,它走了addAll()
方法,接下来看该方法
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
//集合转换数组
Object[] a = c.toArray();
int numNew = a.length;
//判断长度如果为0,就不需要进行相关操作了
if (numNew == 0)
return false;
//两个node节点,用于node节点里面的前后两个节点
//NODE: prev(上一个节点的引用)|E e(当前数据源)|next(下一个节点的引用)
//判断如果添加位置与当前的size相等那么就认为可以添加,添加位置位于链表最后的位置,pred代 //表需要添加节点的
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
第二个构造方法中说白了所有的操作就是对node节点的next,prev进行赋值,因为是链式结构所以需要持有前后两个节点的引用
add( )
public boolean add(E e) {
linkLast(e);
return true;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
add()
方法同样有两个,第一个基本与第二个相同,所以说一下第二个,首先调用 checkPositionIndex()
方法,目的是判断是否数组越界,如果index与size值相同说明是添加在链表的最后端,接下来看一下linkLast()
和linkBefore()
node()
源码
void linkLast(E e) {
final Node<E> l = last;
//创建新的node节点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//如果为空的话,说明当前还没有数据,那么该节点作为first节点
if (l == null)
first = newNode;
else/*否则将last尾节点的next引用指向新的node节点,也就是说添加成功之后,新的节点成为尾节点点,同时size+1*/
l.next = newNode;
size++;
modCount++;
}
/*该方法用于中间插入数据的,基本思路就是找到链表的具体位置,将前节点的next指向加入的节点,将加入节点pre指向前节点,将插入节点的next指向下一个节点,将下一个节点的pre指向插入节点*/
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++;
}
获取指定位置的节点数据,因为是链表结构所以内部采用了折半查找
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
remove( )
remove()基本思路就是链表的移除思路,试想一下链表是如何移除的,假设要移除的节点为A,具体数据结构如下 PRE–|–A–|–NEXT,移除的过程为将A的next赋值给PRE的next,A的pre赋值给B的pre,然后将f置空,方便GC,具体看源码吧
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;
}
clear( )
基本思路从头节点开始挨个修改引用,不断置空类似
remove()
,源码也是一目了然
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
size( )
public int size() {
return size;
}
总结
到了总结的时候了好开心啊,实际情况根据实际场景选择不同的数据结构,如有错误,望请指正(^▽^)
Name | ArrayList | LinkedList |
---|---|---|
内部数据结构 | 数组 | 双向链表 |
读取数据 | 快于LinkedList(因为数组查询快) | 慢于ArrayList(因为链表读取数据慢) |
添加或删除数据 | 慢 | 快 |