目录
LinkedList简介
LinkedList数据结构
LinkedList源码解析
LinkedList和ArrayList的比较
public class LinkedList
ps:LinkedList是非同步的
LinkedList构造函数
// 默认构造函数
LinkedList()
// 创建一个LinkedList,保护Collection中的全部元素。
LinkedList(Collection extends E> collection)
AbstractSequentialList简介:
public abstract class AbstractSequentialList
其中的抽象方法为: public abstract ListIterator
而且AbstractSequentialList实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些函数,如果我们想创建一个AbstractSequentialList的实现类,只需要重写并提供 listIterator() 和 size() 方法的实现即可。如果要实现不可修改的列表,只需要修改ListIterator接口的hasNext、next、hasPrevious、previous 和 index 方法即可。
LinkedList本质是双向链表。
LinkedList有一个内部类
private static class Node {//内部类,定义了一个结点的内部结构,包含前驱,后继,和该节点的元素
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2两个构造函数
public LinkedList() {//无参构造,创建一个空的链表
}
public LinkedList(Collection extends E> c) {//包含“集合”的构造函数:创建一个包含“集合”的LinkedList
this();
addAll(c);
}
LinkedList是通过双向链表来实现的,所以它的顺序访问效率会很高,随机访问效率会很低。
类的成员变量
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
{
// 实际元素个数
transient int size = 0;
// 头结点
transient Node first;
// 尾结点
transient Node last;
} //将成员变量定义为transient,意味着这些变量不会被序列化
add方法
public boolean add(E e) {//双向链表
// 添加到末尾
linkLast(e);
return true;
}
void linkLast(E e) {
final Node l = last;//首先将尾结点保存到l中,属性是final
final Node newNode = new Node<>(l, e, null);//生成一个新节点,前驱为l,后继为null
last = newNode;//将新创建的结点设为尾结点
if (l == null)//如果原来的尾结点为空,那么新生成的结点为头结点,否则将原来结点的后继指向新节点
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
addAll方法有两个重载函数
public boolean addAll(int index, Collection extends E> c) {//从某一位置开始将集合中的元素添加进去
checkPositionIndex(index);//检查索引值是否合法
Object[] a = c.toArray();//将集合转化为数组
int numNew = a.length;//获取几个中元素的个数
if (numNew == 0)//如果集合中元素个数为零,那么添加失败
return false;
Node pred, succ;
if (index == size) {//如果插入位置为链表末尾,则后继为null,前驱为尾结点
succ = null;
pred = last;
} else {
succ = node(index);//如果插入位置为其他位置,那么该位置上的原始结点为新调整的后继结点
pred = succ.prev;//原始结点的前驱结点为新调整的前驱结点
}
for (Object o : a) {//遍历数组
@SuppressWarnings("unchecked") E e = (E) o;
Node newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;//表示在第一个元素之前插入(索引为0的结点),新节点为头结点
else
pred.next = newNode;//否则将新节点插入到前驱结点的下一个结点
pred = newNode;//每一次都将前驱结点设置为一次循环操作的新节点
}
if (succ == null) {//如果此时后继结点为空,说明到了最后一个结点,将该结点的前驱设为尾结点
last = pred;
} else {//否则将每一个结点的前驱和后继关联起来
pred.next = succ;
succ.prev = pred;
}
size += numNew;//修改实际元素个数
modCount++;
return true;
}
LinkedList的遍历方式
由此可见,遍历LinkedList时,使用removeFist()或removeLast()效率最高。但用它们遍历时,会删除原始数据;若单纯只读取,而不删除,应该使用第3种遍历方式。
无论如何,千万不要通过随机访问去遍历LinkedList!
首先LinkedList在指定位置插入元素远远快于ArrayList,源码比较
LinkedList的插入动作:
public void add(int index, E element) {
//首先检查插入位置是否合理
checkPositionIndex(index);
//如果插入位置等于集合大小,则在集合后面追加
if (index == size)
linkLast(element);
else//否则就在指定位置之前插入,即在节点index之前插入元素element
linkBefore(element, node(index));
}
//由于LinkedList原理是双向链表,所以有前驱结点和后继节点
void linkBefore(E e, Node succ) {
// assert succ != null;
//首先获得succ的前驱结点存到pred中
final Node pred = succ.prev;
//新建一个结点,其前驱结点是pred,后继节点是succ
final Node newNode = new Node<>(pred, e, succ);
//将succ的前驱结点执行新生成的节点
succ.prev = newNode;
//如果pred为null,那么就将头结点赋值为新生成的节点
if (pred == null)
first = newNode;
else//否则将pred的后继节点指向新生成的节点
pred.next = newNode;
size++;
modCount++;
}
//node寻找中有一个位置判断,如果插入位置在前半段则从0索引开始找,如果插入位置在后半段则从size-1索引开始找
Node node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
从中,我们可以看出:通过add(int index, E element)向LinkedList插入元素时。先是在双向链表中找到要插入节点的位置index;找到之后,再插入一个新节点。
双向链表查找index位置的节点时,有一个加速动作:若index < 双向链表长度的1/2,则从前向后查找; 否则,从后向前查找。
ArrayList的插入动作:
public void add(int index, E element) {
//评估插入元素位置是否合理
rangeCheckForAdd(index);
//确保数组的容量有当前元素个数加1那么大
ensureCapacityInternal(size + 1); // Increments modCount!!
//数组复制
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;//index索引的元素为插入元素
size++;
}
通过源码我们可以了解到,在指定位置插入元素时,LinkedList是通过二分法查找元素的位置,从而将指定元素插入到索引之前,然后修改指针即可;而ArrayList则是通过数组的赋值来插入元素,如果数组元素过多,那么耗时会很长,所以不如LinkedList速度快。
然而在随机访问时LinkedList的速度却不如ArrayList快,原因是LinkedList这时还是要通过二分法来查找元素的索引,如果元素个数较多,则耗时较长;而ArrayList直接通过索引值就可以找到指定位置的元素。