首先还是从构造函数开始
/**
* Constructs an empty list.
*/
public LinkedList() {
}
是一个空的
然后我们从add看
public boolean add(E e) {
linkLast(e);
return true;
}
定位到linkLast
void linkLast(E e) {
//这里last 和 first 的定义是
//transient Node<E> last;
//transient Node<E> first;
final Node<E> l = last;
//构建一个新的点,上一个节点执行当前链表的last,下一个节点设置为null
final Node<E> newNode = new Node<>(l, e, null);
//将表示当前链表最后一个节点的last指针指向新的节点
last = newNode;
//判断新建的节点是不是整个链表的第一个节点
if (l == null)
//是的话 first指针指向新节点
first = newNode;
else
//不是的话 让以前的最后一个节点的下一个节点指针,指向最新、最后的节点
l.next = newNode;
//改变链表里数据的个数
size++;
//增加操作次数,迭代的时候用的着
modCount++;
}
//观察发现这个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;
}
}
现在看一下迭代器部分
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
//拿到当前操作次数,相当于版本号
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
//查看有没有下一个节点
public boolean hasNext() {
//看下一个节点,是否大于总的长度
return nextIndex < size;
}
public E next() {
//判断迭代的链表有没有被修改
checkForComodification();
//如果已经没有下一个节点了抛出异常
if (!hasNext())
throw new NoSuchElementException();
//这里可以看出next是指向下一个节点的。lastReturned是最后一个返回的,也就是这次迭代要返回给调用者的
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
//在看一下比较重要的删除
public void remove() {
//先检查链表有没有改变
checkForComodification();
//remove要在next方法后调用,所以这里的lastReturned表示正在访问的节点
//如果没有就不能移除
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
//unlink的代码就不贴了,就是很简单的双向链表删除节点
//只要把前后两节点连接在一起就行了
unlink(lastReturned);
//这里在移动一下 其他指标,具体为什么画一下图就明白说说反而说不清
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
//将删除的点null 垃圾回收
lastReturned = null;
expectedModCount++;
}
}
刚才迭代器的构造函数中有一个node方法我们看下
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;
}
}
现在看一下remove方法
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;
}
}
}
return false;
}
可以看出 ,首先是判断了一下是不是删除一个null节点,
然后从头开始找,这里的删除的话也没有利用到双向链表的优点
当然还有一个方法是这样的
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
根据节点的编号,那就能利用到双向链表的优点很快的找到删除
之后get和set方法 也是一样很简单
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
checkElementIndex 的作用是判断你要要找的点的index是不是大于0小于size
之后的话就是通过node函数去找,上面有node函数的介绍
总结一下
linkedlist 底层是一个双向链表,查点的时候会先判断点在前半段还是后半段,他增加删除节点的时候能充分利用链表的特性不需要移动元素,但是查询的时候需要遍历,没有arraylist快