回顾集合体系中LinkedList
的源码实现,查看的源码为Android系统中的java源码实现,和JDK中的实现可能有一定区别。
成员变量
//存储的元素数量
transient int size = 0;
//队首元素节点
transient Node<E> first;
//队尾元素节点
transient Node<E> last;
构造方法
//无参数构造
public LinkedList() {
}
//传入集合的构造器
LinkedList(Collection<? extends E> c) {
this();
addAll(c);//最终调用了addAll方法,后面分析
}
元素节点Node
使用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;
}
}
add方法
//添加指定元素到队尾
public boolean add(E e) {
linkLast(e);//调用linkLast方法添加
return true;
}
//将指定元素链接到队尾
void linkLast(E e) {
final Node<E> l = last;//当前的队尾元素
//将当前的队尾last对象和要添加的元素e封装成newNode,使其
//newNode的prev成员变量持有last对象,这样完成向前的链接
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;//然后将newNode赋值给last,完成队尾更新
if (l == null)//如果last对象是null,说明还没有任何元素添加进来
first = newNode;//直接将newNode赋值给first,也就是队首节点
else
l.next = newNode;//不是null,就把last的next的引用指向newNode,完成向后的链接
size++;//size自增
modCount++;//修改标识自增
}
在指定位置插入元素:
//在指定的索引插入元素
public void add(int index, E element) {
checkPositionIndex(index);//检查索引是否合法
if (index == size)//如果index和size相同,那么直接插入队尾
linkLast(element);
else//如果index和size不相同,那么调用linkBefore插入
linkBefore(element, node(index));
}
//取出指定索引存储的Node信息
Node<E> node(int index) {
// assert isElementIndex(index);
//如果index小于size/2,那就从0遍历到index-1,返回x
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//如果index大于size/2,那就从size-1向前遍历到index+1,返回x
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//在指定的节点前插入给定的元素
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;//先取出节点的前一个节点对象
//然后将前后节点和当前元素封装成newNode,建立双向链接
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;//然后把newNode赋值给被插入对象的prev引用,建立向前引用
if (pred == null)//如果pred对象为null,说明到了队首,赋值给first
first = newNode;
else//如果不为null,那么将pred的next节点赋值为newNode,完成向后引用
pred.next = newNode;
size++;//size自增
modCount++;//修改标识自增
}
在队尾添加集合的方法:
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//在指定的索引插入一个集合
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);//检查索引合法性
//转换为数组判断长度是否合法,为0直接返回false
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//定义向前和向后的引用
Node<E> pred, succ;
if (index == size) {//如果index==size说明是要插入到队尾
succ = null;//所以向后的引用就没有了
pred = last;//向前的引用就是当前的last
} else {//不等的话,就先取出index对应的节点
succ = node(index);//向后引用直接使用该节点
pred = succ.prev;//向前的引用赋值该节点的prev引用
}
for (Object o : a) {//循环插入的数组
@SuppressWarnings("unchecked") E e = (E) o;
//封装newNode对象,完成向前引用
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)//如果pred是null,说明到了队首
first = newNode;//将newNode直接赋值给first
else//如果pred不为Null,那么将newNode赋值给pred.next,完成向后链接
pred.next = newNode;
pred = newNode;//然后将newNode赋值给pred,继续下一轮链接,直到完成
}
//succ为null,说明是插入队尾的,将最后的pred赋值给last
if (succ == null) {
last = pred;
} else {//不为Null
pred.next = succ;//pred的向后引用指向succ
succ.prev = pred;//succ的向前引用指向pred
}
size += numNew;//size增长
modCount++;//修改标识自增
return true;
}
push方法
push方法是将指定元素添加到队首
public void push(E e) {
addFirst(e);
}
public void addFirst(E e) {
linkFirst(e);
}
//最终调用该方法将元素添加到队首
private void linkFirst(E e) {
final Node<E> f = first;//取队首节点封装到newNode
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;//将newNode赋值到first队首
if (f == null)//如果f也就是原来的队首是Null,说明没有之前没有任何元素
last = newNode;//这样将newNode也赋值给last
else//否则就把原来队首的向前引用指向newNode
f.prev = newNode;
size++;//size自增
modCount++;//修改标识自增
}
get方法
获取知道索引的元素的方法:
public E get(int index) {
checkElementIndex(index);//检测索引是否合法
return node(index).item;//调用了node方法,取出节点包含的元素
}
获取队首元素方法:
public E getFirst() {
final Node<E> f = first;
if (f == null)//为null就抛出异常
throw new NoSuchElementException();
return f.item;//返回的就是first节点包含的元素
}
获取队尾元素方法:
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;//返回的就是last节点包含的元素
}
set方法
public E set(int index, E element) {
checkElementIndex(index);//检测索引合法性
Node<E> x = node(index);//取出index指向的node
E oldVal = x.item;//取出node包含的元素
x.item = element;//直接赋值新元素
return oldVal;//返回旧元素
}
remove方法
移除指定索引的元素的方法:
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));//先调用node方法取出对应的节点,然后调用unlink方法
}
E unlink(Node<E> x) {
// assert x != null;
//取出待移除的node包含的元素,和前后的引用节点
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {//如果向前引用prev为Null,说明移除的是队首元素
first = next;//将向后的引用next赋值给first
} else {//不为Null,就将prev的向后引用赋值next,完成向后的链接
prev.next = next;
x.prev = null;//原始的prev置Null
}
if (next == null) {//如果next为null,说明移除的是队尾
last = prev;//就将prev赋值给last
} else {//不是队尾就把next的向前引用赋值为prev,完成向前的链接
next.prev = prev;
x.next = null;//原始的next赋值为Null
}
x.item = null;//然后把原始的元素置null
size--;//size自减
modCount++;//修改标识自增
return element;//返回旧元素
}
pop方法
该方法就是移除队首元素,并返回移除的元素:
public E pop() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//最终调用该方法来移除队首元素
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;//取出包含的旧元素
final Node<E> next = f.next;//取出队首的向后引用的next节点
f.item = null;//将原始元素置null
f.next = null; // help GC next也置null,GC负责回收
first = next;//然后队首first置为next
if (next == null)//如果next是null说明之前只包含一个元素
last = null;//那么移除后last也置Null
else//否则,将新队首向前的引用置null
next.prev = null;
size--;//size自减
modCount++;//修改标识自增
return element;//返回旧元素
}
size方法
public int size() {
return size;//直接返回size
}
isEmpty方法
该方法在抽象类AbstractCollection
中定义:
public boolean isEmpty() {
return size() == 0;//最终也是判断的size
}
indexOf方法
找出指定元素出现的第一个索引方法:
public int indexOf(Object o) {
int index = 0;
if (o == null) {//如果o为Null,从前往后循环,找出第一个为Null的元素,返回其索引
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {//如果o不为Null,从前往后循环,找出第一个和O相同的元素,返回其索引
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;//找不到返回-1
}
lastIndexOf方法
找出指定元素出现的最后出现的索引方法:
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {//如果o为Null,从后往前循环,找出第一个为Null的元素,返回其索引
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {//如果o不为Null,从后往前循环,找出第一个和O相同的元素,返回其索引
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;//找不到返回-1
}
contain方法
public boolean contains(Object o) {
return indexOf(o) != -1;//其实也是调用的indexOf方法
}
clear方法
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
//就是从前往后遍历,将所有的引用置为Null
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;//最后将队首和队尾置null
size = 0;//size为0
modCount++;//修改标识自增
}
总结
- 由于
LinkedList
为双链表设计,ArrayList
本质为数组实现,因此比较ArrayList
主要区别:
转自 温布利往事:Java集合之LinkedList源码分析
ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间,就存储密度来说,ArrayList是优于LinkedList的。
总之,当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能,当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
- 由于
LinkedList
实现了Deque
双端队列接口,和List
接口,因此存在功能相同的方法; - 实现了
Cloneable
和Serializable
接口;