LinkedList特点总结
- LinkedList实现List接口,使用链表实现
- 元素可以是null
- 因其使用链表实现,查询需要遍历O(n)时间复杂度,插入时不再需要复制移动元素O(1)时间复杂度
注 LinkedList是由一个双向链表来维护的,对于增删改查元素理解最清晰地理解就是画一张图
源码分析
考虑到之前是直接拷贝jdk源码在源码上通过注释的方式进行解读,这样看起来会比较杂乱,所以这次采用分块解读的方式
1. 节点类
因为采用链表的方式实现,所以需要先定义一个结点类,源码中使用静态内部类的方式定义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;
}
}
可以看到定义的是一个双向链表的Node类,next指向其下一个Node,prev指向其上一个Node
2. LinkedList的成员变量
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
size是当前List中的元素个数,first和last都是Node类型的变量,分别指向链表的第一个节点和最后一个节点,这三个变量都是transient,说明不希望进行序列化
3. 构造方法
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
第一个构造方法创建了一个空的LinkedList,第二个构造方法是通过一个Collection接口的实现类对象进行初始化,首先调用第一个构造方法创建一个空的LinkedList然后将参数指向的Collection中的元素添加到LinkedList中,元素的顺序是Collection的iterator返回的顺序
4. 增加元素
源码中提供了几种增加元素的方法
public boolean add(E e) {
linkLast(e);
return true;
}
首先看add方法,add方法中调用了linkLast方法,可以看出来是在list的末尾添上一个Node,这个方法与源码中的addLast方法效果是相同的,只是add方法是有返回的,凡是返回比较鸡肋
public void addLast(E e) {
linkLast(e);
}
顺藤摸瓜,我们来看一个linkLast方法
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++;
}
- 首先通过last指针找到list的最后一个node,然后创建一个新的node,这个newNode的next指向null,prev指向链表的最后一个节点
- 将链表的last指向创建的newNode
- 判断之前找到的last node 如果该node是null说明原来list中没有元素,将创建的newNode赋值给first表示新node是最后一个元素也是第一个元素,如果l不是null,表示原来的list中有元素,此时将l的next指向newNode
最后需要将size++ 表示list元素多了一个
除了在list的尾巴上添加元素,jdk源码中还提供了在链表头上添加元素的方法
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; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; }
addFirst会直接调用linkFirst完成插入操作,和上面linkLast方法其实是相仿的
首先找到first指向的元素,有可能是一个null
- 创建一个新的node,其prev指向null,因为newNode是链表的第一个元素
- first指向newNode
- 如果f指向null,表示链表原先是没有元素的,那么newNode就是当前唯一的元素,last也要指向newNode,如果f不是null,表示原先链表中就有元素,需要将f指向的(此时是链表的第二个元素)的prev指向newNode
LinkedList同时也提供了指定index插入元素
public void add(int index, E element) {
checkPositionIndex(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) {
return index >= 0 && index <= size;
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
- 首先进行index的检查,保证index在[0,size]之间,如果是非法的index就抛出一个异常
- 如果index==size 就是在链表的最后进行添加
- 否则调用linkBefore方法,完成元素添加
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;
}
}
node(int index)这个方法返回在链表index位置上的node,返回的node是一个不为null的node,// assert isElementIndex(index);被注释掉是因为前面已经进行index == size 判断就不再这边进行判断了,此外需要注意的是这边使用了一个小技巧
- 如果index属于链表的前面一半 index < (size >> 1) 就从first指向的元素进行遍历
- 如果index属于链表的后面一半元素 就从last指向的元素进行遍历
- 返回node
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
linkBefore方法是最终完成插入操作的方法,同样succ!=null也被注释掉了,因为上面node方法返回的是一个不为null的node,linkBefore其实是一个双向链表的插入操作
- 首先找到succ这个node prev指针指向的node,即succ左手边的一个node
- 创建一个新的node,这个newNode prev指向pred,next指向succ
- 因为jdk中在add(index,e)中仅对index==size的情况做了特殊处理,并没有对index==0的时候做出特殊处理,即在第一个位置进行插入,此时pred会是null,当pred == null时表示index == 0在第一个位置进行插入了,pred右手边指针需要指向newNode,其他情况下pred.next指向newNode即可
5.访问元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
首先进行index检查,然后通过node(index)获取index下的元素,最后通过node.item获取到值,需要注意index检查的时候与add index检查的方法存在一点点细微的区别:index >= 0 && index < size;这里index必须在[0,size-1]
jdk还提供了一些取特殊位置元素的方法
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
6. 删除元素
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
首先index检查与上面get中的方式相同,然后调用找到index指向的元素,调用unlink完成删除
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 {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
因为node(index)方法返回一个非null的node,所以这边把 x != null注释掉了,unlink是一个双向链表删除的操作
- 首先获取x的值,值作为最后的返回
- 分别获取x next和prev指向的元素
- 如果prev==null 表示要删除的是第一个元素,同样,如果next == null表示要删除的是最后一个元素
- 如果prev==null 将first指向链表的第二个元素即next指向的元素,否则prev.next = next;x.prev = null;
- 如果next==null 表示要删除的是最后一个元素,last = prev;表示将x前面的一个元素作为链表的最后一个元素,否则 next.prev = prev;x.next = null;
- 最后x.item = null size– modCount++
注 最好的方式还是画一张图帮助理解
同样的,jdk也提供了一些在特殊位置删除元素的方法
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
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; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
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;
}
不详细展开了,需要注意的是比如删除第一个节点的时候需要注意链表只有一个元素的情况
7. 修改元素的值
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
需要注意最后返回原先的值
8. 其他常见操作
public int size() {
return size;
}
返回当前链表中元素的个数
public boolean contains(Object o) {
return indexOf(o) != -1;
}
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
返回元素o在链表中的index,准确说应该是第一个o的index,与ArrayList中处理的方式是一样的,遍历的时候对null和non-null做了判断
在LinkedList中定义了一些Queue operations.即队列的操作
- peek
- element
- poll & pollFirst & pollLast
- remove & pop
- offer & add & push
- offerFirst & offerLast
- peekFirst & peekLast
等操作,实质上就是运用了上面已经提到的增删改查方法