Java集合-LinkedList
前言
首先看下图LinkedList在集合框架中的位置
上帝视角
LinkedList实现方式是一个双向链表,链表的每一个节点都保存着当前节点的值,以及节点的前驱动、后继。因此LinkedList对于数据的删除和添加性能都不错,顺序访问效率较高,随机访问性能稍差。
- LinkedList继承AbstractSequentialList其拥有add()、get()、remove()、迭代等常用功能
- 实现List具有size() isEmpty() contains(), toArray()等功能
- 实现Deque Deque接口继承Queue接口,在Deque中定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值null 或 false,具体取决于操作
- 实现Cloneable和Serializable其具有clone()和序列换的功能
- 因为LinkedList实现Deque接口,所以其可以作为FIFO(先进先出)的队列,也可以作为LIFO(后进先出)的栈,
- LinkedList是双向链表,所以其不用扩容,也没有长度限制
LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
源码分析
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
//当前集合长度
transient int size = 0;
//第一个节点的指针
transient Node<E> first;
//最后一个节点的指针
transient Node<E> last;
//LinkedList只提供两个构造方法
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//当属于初始化调用时,将当前size=0赋值为inedx,其实就是在size后面添加数据
//整个添加过程分为两种情况,第一种是插入在中间,还一种就是插入在最后面,
//首先要明白的是双向链表中,每一个节点都有一个当前节点值,以及一个前驱和一个后继
//如果链表为1234,插入的数据为QAZ,擦入index为3,那么接下来流程就是,将QAZ生成为一个一个节点,在生成Q的时候,要将2的后继设置为Q,Q的前驱设置为2;再将QAZ相连,将最后Z的后继设置为3,将3的前驱修改为Z,这样就完成了链接
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//pred为要设置的前驱
Node<E> pred, succ;
//index == size是要将数据添加到最后面,不等于就说明这段数据想擦在中间或者最前面
if (index == size) {
succ = null;
pred = last;
} else {
//如果要插入在位置3,获得这个节点,目前节点3前驱是2,后继为4
succ = node(index);
//获取这个数据的前驱, 也就是2,目前节点2的前驱是1,后继是3
pred = succ.prev;
}
//比如添加进来集合为QAZ,
//接下来的for循环就是将集合里面的数据取出来,生成Node节点。要添加进来集合中第一个节点Q它的前驱就是2,节点2的后继要改变成Q,那么数据就链起来了。
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//将节点2设置为当前节点的前驱
Node<E> newNode = new Node<>(pred, e, null);
//这里判断的情况是当数据中一个数据也没有的时候,因为上 pred = last 这个last尾节点为空
if (pred == null)
first = newNode;
else
//这里就是将节点2的后继设置为刚刚生成的节点,也就是为上一个节点设置后继
pred.next = newNode;
//将刚生成的节点设置为下一个节点的前驱
pred = newNode;
}
//到这里节点2和新添加的节点已经连起来了,新添加的节点最后一个节点还为空,原先断开的节点3的前驱还指向2,下面就是再进行修改节点
//当要添加在最后面的时候succ==null,那么添加进来的集合最后一个元素就是last元素
if (succ == null) {
last = pred;
} else {
//这里就是设置添加进来的集合最后一个元素的后继,Z的后继就是节点3
pred.next = succ;
// 将节点3的前驱2,改为集合的最后一个元素Z,就是重新给断开后右边的那个元素设置前驱
succ.prev = pred;
}
//size+上集合数量
size += numNew;
modCount++;
return true;
}
//这里根据下标获取一个元素的时候,判断当前inedx是处于集合的前半段还是后半段,前半段就从前向后遍历,后半段就从后向前遍历
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;
}
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//判断index的合法性
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
//节点数据模型
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(E e)
元素默认添加到末尾,主要是创建一个节点,将节点设置为last,然后将原先last的后继指向新创建的节点
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++;
}
E get(int index)
根据index获取元素的node(int index) 方法我们在上面add()方法中讲过,请看上面
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
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;
}
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
remove(int index)
删除的是第一个元素,此元素就没有前驱;删除的是最后一个元素,此元素就没有后继;
public E remove(int index) {
//检测inedx合法性
checkElementIndex(index);
//首先获取节点,然后传入unlink();
return unlink(node(index));
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
//获取这个元素的后继
final Node<E> next = x.next;
//获取这个元素的前驱
final Node<E> prev = x.prev;
//如果删除的是第一个元素,那么要删除元素的后继就是以后就是第一个元素
if (prev == null) {
first = next;
} else {
//如有元素ABC,要删除的是B
//如果是中间元素,那么将删除元素前一个元素A的后继指向B的后继C,也就是A和C相关联
prev.next = next;
x.prev = null;
}
//如果删除的元素是最后一个元素C,那么C的前驱B将是最后一个元素
if (next == null) {
last = prev;
} else {
//因为是一个双向链表,上面是将A与C相关联,这里是将C与A相关联
//如果删除的是B,那么B的前驱是A,将A赋值给C的前驱
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
addFirst(E e)
要判断添加的元素是第一个元素,如果是第一个元素,那么first和last都是它
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
//f == null说明这个原先没有first,要添加的元素是第一个元素
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
addLast(E e)
要判断添加的元素是第一个元素,如果是第一个元素,那么first和last都是它,添加到Last只不过是将之前最后一个元素的next指向当前元素
public void addLast(E e) {
linkLast(e);
}
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++;
}
E 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;
f.item = null;
f.next = null; // 置空帮助JVM回收
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
E removeLast()
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(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;
}
遍历LinkedList
LinkedList随机遍历是最慢的,建议使用逐个遍历
- 通过迭代器遍历。即通过Iterator去遍历。
for(Iterator iter = list.iterator(); iter.hasNext();)
iter.next();
2.通过快速随机访问遍历LinkedList
int size = list.size();
for (int i=0; i<size; i++) {
list.get(i);
}
3.通过另外一种for循环来遍历LinkedList
for (Integer integ:list)
;
4.通过pollFirst()来遍历LinkedList
while(list.pollFirst() != null)
;
5.通过pollLast()来遍历LinkedList
while(list.pollLast() != null)
;
6.通过removeFirst()来遍历LinkedList
try {
while(list.removeFirst() != null)
;
} catch (NoSuchElementException e) {
}
7.通过removeLast()来遍历LinkedList
try {
while(list.removeLast() != null)
;
} catch (NoSuchElementException e) {
}
对于遍历的效率,emoveFist()或removeLast()效率最高,但是会删除掉元素。如果只是取值使用第3种是最快的