LinedList
本次对LinedList的分析是基于JDK1.8来进行的
按照目录的结构来进行分析
1.LinedList简介
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
易知LinkedList 继承了AbstractSequentialList双向链表,所以他可以用作栈,队列或双端队列;
还实现了List接口,是有序的列表;
实现了Deque,支持两端的元素插入和移除;
实现了Cloneable接口,能够使用clone()方法;
实现了Serializable接口,支持序列化操作
ArrayList在执行随机访问很快速,但是在执行插入或删除效率略低,LinkedList和ArrayList一样都基本实现的了List接口,但是作为双端队列,在插入和删除操作时比ArrayList更高效.
2.1属性
//实际的元素个数
transient int size = 0;
/**
* 头节点
*/
transient Node<E> first;
/**
* 尾节点
*/
transient Node<E> last;
LinkedList对size,头结点,尾结点进行transient关键字修饰,使其不能被序列化
2.2构造函数
/**
* 空构造函数
*/
public LinkedList() {
}
/**
* 带参数的构造函数
* @param c 放入此列表中的集合
*/
public LinkedList(Collection<? extends E> c) {
//调用无参构造函数
this();
//添加集合中所有元素
addAll(c);
}
addAll(c)中又调用另一个重载的addAll(size, c);方法
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
最终的addAll(size, c)方法如下:
/**
* 将指定集合中的所有元素插入到此列表,从指定的位置开始。
* 将当前在该位置的元素(如果有的话)和随后的元素移到右侧(增加它们的索引)。
* 新元素将按照它们由指定集合的迭代器返回的顺序出现在列表中。
*
* @param index 在哪个索引处插入指定集合中的第一个元素
* @param c 包含要添加到此列表中的元素的集合
*/
public boolean addAll(int index, Collection<? extends E> c) {
//检查传入的索引位置是否有效
checkPositionIndex(index);
//将目标集合转换保存为a对象数组
Object[] a = c.toArray();
//获取插入目标的长度
int numNew = a.length;
//如果新数组长度为0
if (numNew == 0)
//返回false
return false;
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;
//新建结点,前驱为pred,元素为e,后继为空
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null) //如果前驱为空
first = newNode;//重新赋值头结点
else //如果前驱不为空
pred.next = newNode;//赋值新结点为pred的后继
pred = newNode;//重新赋值pred结点
}
if (succ == null) {//如果succ为空
last = pred;//重新赋值尾结点
} else { //如果succ不为空
pred.next = succ;//赋值pred的后继为succ
succ.prev = pred;//赋值succ的前驱为pred
}
//链表长度加上插入的长度
size += numNew;
//结构性加1
modCount++;
return true;
}
2.3核心方法
增加方法
add(E e)
/**
* 增加一个元素到尾部,成功返回true
*
* @param e
*/
public boolean add(E e) {
linkLast(e);
return true;
}
调用linkLast(E e)方法,添加目标元素到尾结点
/**
* 添加一个元素到尾结点
*/
void linkLast(E e) {
//保存尾结点为final不可变类型
final Node<E> l = last;
//初始化结点对象,前驱为l,数据域为元素e,后继为null
final Node<E> newNode = new Node<>(l, e, null);
//重新赋值尾结点
last = newNode;
if (l == null) //尾结点为空
first = newNode;//赋值新结点为头结点
else //尾结点不为空
l.next = newNode;//尾结点的后继赋值为新结点
//结点大小加1
size++;
//结构性加1
modCount++;
}
移除方法
remove(Object o)其实就是删除索引最小的元素(如果有多个重复值的话)
/**
* 从此链表中删除第一次出现的指定元素,
* 如果不包含该元素,则不变。
*
* @param o
*/
public boolean remove(Object o) {
if (o == null) {//如果元素为空
for (Node<E> x = first; x != null; x = x.next) {//从头结点开始遍历
if (x.item == null) {//如果遍历结点的元素为空
unlink(x);//移除该结点
return true;
}
}
} else {//如果不为空
for (Node<E> x = first; x != null; x = x.next) {//从头结点开始遍历
if (o.equals(x.item)) {//如果链表中包含该元素
unlink(x);//移除该结点
return true;//返回true
}
}
}
return false;//不包含返回false
}
调用unlink(Node x)来移除结点
/**
* 移除非空结点
*/
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) {//如果prev结点为空
first = next; //重新赋值头结点
} else { //如果非空
prev.next = next; //重新赋值prev的尾结点
x.prev = null; //目标结点前驱置空
}
if (next == null) {//如果next结点为空
last = prev; //重新赋值尾结点
} else { //如果非空
next.prev = prev; //重新赋值尾结点的前驱
x.next = null; //置空目标结点后继
}
//置空目标元素
x.item = null;
//结点大小减1
size--;
//结构性加1
modCount++;
//返回移除的元素值
return element;
}
查找方法
get(int index)
/**
* 返回此列表中指定位置的元素
*
* @param index 要返回的元素的索引
* @return 该列表中指定位置的元素
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
调用node(int index)方法返回指定元素索引处的(非空)节点值。
/**
* 返回指定元素索引处的(非空)节点。
*/
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {//如果索引位置小于总长度除以2
Node<E> x = first; //保存头结点
for (int i = 0; i < index; i++)//从头结点开始遍历直到索引位置获取相关结点
x = x.next;
return x;//返回结点
} else {//如果索引位置大于总长度除以2
Node<E> x = last;//保存尾结点
for (int i = size - 1; i > index; i--)//从尾结点开始往前遍历
x = x.prev;//获取相关结点
return x;//返回结点
}
}
更新方法
set(int index, E element)
/**
* 用指定的元素替换此列表中指定位置的元素
*
* @param index 要替换的元素的索引
* @param element 要存储在指定位置的元素
* @return 之前在指定位置的元素
*/
public E set(int index, E element) {
checkElementIndex(index);//检查传入的位置是否有效
Node<E> x = node(index);//保存查询到的位置结点
E oldVal = x.item;//保存指定位置的旧值
x.item = element;//把新值赋值给该位置结点
return oldVal;
}
总结
在我们需要进行快速的从列表的端点插入或者移除元素时,LinedList只需要链接新的元素,而不必修改列表中剩余元素,我们便可以使用它,如果要执行大量的随机访问,LinedList的访问时间将随列表的大小而明显增加,不适用随机访问的场景.它不是同步的