1.add()
public boolean add(E e) {
//调用linkLast
linkLast(e);
//如果前面调用没有异常,返回true
return true;
}
/**
* 添加到链表尾部方法
* @param e 数据
*/
void linkLast(E e) {
//定义l用于存储当前尾部的链表,方便后面连接
final LinkedList.Node<E> l = last;
//把数据e初始化为链表节点,并且节点头连接上last
final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
//刷新尾节点为新加的节点newNode,因为尾节点始终都要保存,方便以后从尾节点的遍历和存储及移除
last = newNode;
//如果尾节点为空也就是链为空没有数据,则首节点也为新节点newNode
if (l == null)
first = newNode;
//否则代表链表有数据,则将链表的下一个节点连接上新节点newNode
else
l.next = newNode;
//添加了一个新数据,数量加1
size++;
//修改次数加1,这个就统计修改次数,无关
modCount++;
}
/**
* 链表结构node
* @param <E>
*/
private static class Node<E> {
//存储我们写入的数据
E item;
//链表的尾节点
LinkedList.Node<E> next;
//链表的头节点
LinkedList.Node<E> prev;
//链表的构造器主要是用于连接头尾节点,及存储元素
Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
/**
*
* @param index 添加的索引位置
* @param element 添加的元素
*/
public void add(int index, E element) {
//检查下标是是否越界
checkPositionIndex(index);
//如果index刚好等于链表长度也就是刚好要往最后一个里插入,则往最后插入
if (index == size)
//这个方法上面写了
linkLast(element);
//否则就是往中间和头部插入
else
//找到对应的节点,然后插入
linkBefore(element, node(index));
}
private void checkPositionIndex(int index) {
//如果越界抛出异常
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
//当下标是否大于等于0且下于等于链表的长度的时候为true,否则false
return index >= 0 && index <= size;
}
/**
* node查询方法(采用二分查找的方法),节省时间
* @param index
* @return
*/
LinkedList.Node<E> node(int index) {
//如果下标小于长度/2,从头部循环一直找,找到后返回节点
if (index < (size >> 1)) {
LinkedList.Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//如果下标大于长度/2,从尾部循环一直找,找到后返回节点
LinkedList.Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
/**
* 中间插入方法
* @param e 元素
* @param succ 对应下标的节点
*/
void linkBefore(E e, LinkedList.Node<E> succ) {
//用于存储链表的节点头部以上的链表数据
final LinkedList.Node<E> pred = succ.prev;
//把数据e初始化为链表节点,并且节点头连接上pred,节点尾连接上succ
final LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, succ);
//把头部的链表连接上该节点,此时newNode<-node<-node<-node
succ.prev = newNode;
//如果链表头部节点为空,头部节点first指向newNode,目的是刷新头部节点,方便节点以后遍历、存储及移除
if (pred == null)
first = newNode;
else
//否则,把头部链表以上的数据连接上newNode,此时node->node->node->newNode
pred.next = newNode;
//添加了一个节点,数量加1
size++;
//修改次数加1,这个就统计修改次数,无关
modCount++;
}
总结:链表是双向链表,添加时需要进行指针改变,判断该往哪里插入,每次的头尾操作都会更新first、last这两节点,方便以后遍历、存储、移除操作。代码比较容易读懂,细细看就会发现其中的奥妙。
2.addAll()
public boolean addAll(Collection<? extends E> c) {
//调用了addAll方法,size:数据长度, c:集合数据
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
//检查是否下标越界,用于往指定下标添加
checkPositionIndex(index);
//把集合转化成数组
Object[] a = c.toArray();
//获取数组的长度
int numNew = a.length;
//如果长度为0表明没有数据,返回false
if (numNew == 0)
return false;
//pred用于存储节点以上链表,succ用于存储节点以下链表
LinkedList.Node<E> pred, succ;
//如果index==size代表往最后面加数据,此时存储节点以上链表pred直接指向最后节点,succ存储节点以下链表为空
if (index == size) {
succ = null;
pred = last;
} else {
//否则存储节点以上链表pred为当前index节点的头部,存储节点以下链表succ为当前节点
succ = node(index);
pred = succ.prev;
}
//遍历数组
for (Object o : a) {
//转化成对应类并忽略检查警告
@SuppressWarnings("unchecked") E e = (E) o;
//把数据e初始化为链表节点并头部连接上节点以上链表pred
LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, null);
//节点以上链表pred为空直接让首节点为newNode
if (pred == null)
first = newNode;
else
//否则往节点以上链表pred的后面连接上数据newNode
pred.next = newNode;
//节点以上链表pred重置该节点为newNode,方便循环后面连接
pred = newNode;
}
//如果succ==null代表往链表的最后面加数据,此时尾节点last刚好等于循环完的pred
if (succ == null) {
last = pred;
} else {
//如果是往中间插入数据,此时需要把节点以上的数据pred:node->node->newNode->链表之前的节点succ
pred.next = succ;
//succ的头部要指向newNode,succ:node->node->pred
succ.prev = pred;
}
//数量加上集合的长度
size += numNew;
//修改次数,无关
modCount++;
//上面无误,返回true
return true;
}
代码比较容易读懂,细看吧。
3.get()
public E get(int index) {
//检查是否下标越界index>=0 && index<size
checkElementIndex(index);
//查询对应下标的index节点并返回.node()上面有写
return node(index).item;
}
4.remove()
public E remove() {
//调用removeFirst,不加参数默认移除前面的节点
return removeFirst();
}
public E removeFirst() {
//赋值f等于first首节点
final LinkedList.Node<E> f = first;
//如果首节点为空,抛出异常
if (f == null)
throw new NoSuchElementException();
//解链表并移除节点,返回移除的节点
return unlinkFirst(f);
}
private E unlinkFirst(LinkedList.Node<E> f) {
//保存移除节点的值
final E element = f.item;
//移除节点后的新链表等于f.next
final LinkedList.Node<E> next = f.next;
//把原来节点的值置为空
f.item = null;
//连接也置为空
f.next = null; // help GC
//让首节点为next
first = next;
//如果移除节点后没有节点了也就是next为空了,需要把尾节点也置为空
if (next == null)
last = null;
else
//否则把next链表的头连接置为空,这样next就是头节点
next.prev = null;
//数量减1
size--;
//修改数加1
modCount++;
//返回移除的节点值
return element;
}
//根据下标移除
public E remove(int index) {
//检查下标是否越界,越界抛出异常
checkElementIndex(index);
//找到节点并进行移除
return unlink(node(index));
}
E unlink(LinkedList.Node<E> x) {
//保存移除的值
final E element = x.item;
//移除节点的下一个节点
final LinkedList.Node<E> next = x.next;
//移除节点的上一个节点,这两节点连接起来就移除了对应节点
final LinkedList.Node<E> prev = x.prev;
//如果节点的上一个节点为空,代表这个节点移除之后,移除节点的下一个节点就是头节点
if (prev == null) {
first = next;
} else {
//否则移除节点的上一个节点的尾链接连接到移除节点的下一个节点
prev.next = next;
//把移除节点头链接置空,方便回收
x.prev = null;
}
//如果移除节点的下个节点为空,代表这个节点移除后,移除节点的上一个节点就是尾节点
if (next == null) {
last = prev;
} else {
//否则移除节点的下一个节点的头链接连接到移除节点的上一个节点
next.prev = prev;
//把移除节点的尾链接置空,方便回收
x.next = null;
}
//把移除节点的值置空,方便回收
x.item = null;
//数量减1
size--;
//修改数加1
modCount++;
//返回移除节点的值
return element;
}
//根据值移除
public boolean remove(Object o) {
//如果值为空
if (o == null) {
//从链表的头部节点开始找,一直循环
for (LinkedList.Node<E> x = first; x != null; x = x.next) {
//当找到节点值为空时
if (x.item == null) {
//进行移除,这个方法上面有写
unlink(x);
return true;
}
}
} else {
//从链表的头部节点开始找,一直循环
for (LinkedList.Node<E> x = first; x != null; x = x.next) {
//当找到节点值等于要移除值时
if (o.equals(x.item)) {
//进行移除,这个方法上面有写
unlink(x);
return true;
}
}
}
//如果没有找到对应的值返回false
return false;
}
代码比较简单,慢慢看!
总结一下:总体来说LinkedList代码比较简单,只要知道linkedlist是双向链表结构,脑子有这个图形,就不难读懂。其中涉及到指针的修改,解链、连接链等也是比较容易读懂,干就完了,奥利给!