简介 特点:
LinkedList底层是链表结构
插入和删除比较快(O(1)),查询则相对慢一些(O(n))
因为是链表结构,所以分配的空间不要求是连续的
继承关系:
源码定义:
1 2 3 public class LinkedList <E > extends AbstractSequentialList <E > implements List <E >, Deque <E >, Cloneable , java .io .Serializable
AbstractSequentialList这个类提供了List的一个骨架实现接口,以尽量减少实现此接口所需的工作量由“顺序访问”数据存储(如链接列表)支持。对于随机访问数据(如数组),应使用AbstractList优先于此类。
实现了List接口,意味着LinkedList元素是有序的,可以重复的,可以有null元素的集合.
Deque是Queue的子接口,Queue是一种队列形式,而Deque是双向队列,它支持从两个端点方向检索和插入元素.
实现了Cloneable接口,标识着可以它可以被复制.注意,ArrayList里面的clone()复制其实是浅复制(不知道此概念的赶快去查资料,这知识点非常重要).
实现了Serializable 标识着集合可被序列化。
属性定义 节点定义 1 2 3 4 5 6 7 8 9 10 11 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是LinkedList的静态内部类。
为什么是静态内部类?我觉得可能原因如下:普通内部类会有外部类的强引用,而静态内部类就没有;有外部类的强引用的话,很容易造成内存泄漏,写成静态内部类可以避免这种情况的发生。
成员变量 1 2 3 4 5 6 7 8 9 10 11 transient int size = 0 ;transient Node<E> first;transient Node<E> last;
这里为什么要存在一个成员变量尾节点?我感觉是为了方便,比如查找相应索引处元素+插入元素到最后。查找相应索引处元素时,先判断索引是在前半段还是在后半段,如果是在后半段,那么直接从尾节点出发,从后往前进行查找,这样速度更快。在插入元素到最后时,可以直接通过尾节点方便的进行插入。
构造方法 1 2 3 4 5 6 7 8 9 10 11 12 13 public LinkedList () {} public LinkedList (Collection<? extends E> c) { this (); addAll(c); }
添加元素 add(E e) 方法作用:将e添加到链表末尾,返回是否添加成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public boolean add (E e) { linkLast(e); return true ; } 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++; }
大体思路:
构建一个新的节点
将该新节点作为新的尾节点,如果是空链表插入第一个元素,那么头结点=尾节点=新节点;如果不是,那么将之前的尾节点指向新节点.
增加链表长度
addFirst(E e) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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++; }
大体思路:
构建一个新的节点
将该新节点作为新的头节点.如果是空链表插入第一个元素,那么头结点=尾节点=新节点;如果不是,那么将之前的头节点的prev指针指向新节点.
增加链表长度
add(int index, E element) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 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)); } Node<E> node (int 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; } } 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++; } void linkBefore (E e, Node<E> succ) { 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++; }
大体思路:
addAll(int index, Collection) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public boolean addAll (Collection<? extends E> c) { return addAll(size, c); } 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<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; Node<E> newNode = new Node<>(pred, e, null ); if (pred == null ) first = newNode; else pred.next = newNode; pred = newNode; } if (succ == null ) { last = pred; } else { pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true ; }
大体思路:
将需要添加的集合转成数组a
判断需要插入的位置index是否等于链表长度size,如果相等则插入到链表最后;如果不相等,则插入到链表中间,还需要找到index处节点succ,方便拿到该节点的前后节点信息.
记录index索引处节点的前一个节点pred,循环将集合中所有元素连接到pred的后面
将集合最后一个元素的next指针指向succ,将succ的prev指针指向集合的最后一个元素
删除元素 remove()、removeFirst()、removeLast() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public E remove () { return removeFirst(); } public E removeFirst () { final Node<E> f = first; if (f == null ) throw new NoSuchElementException(); return unlinkFirst(f); } private E unlinkFirst (Node<E> f) { final E element = f.item; final Node<E> next = f.next; f.item = null ; f.next = null ; first = next; if (next == null ) last = null ; else next.prev = null ; size--; modCount++; return element; }
remove(int index) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public E remove (int index) { checkElementIndex(index); return unlink(node(index)); } E unlink (Node<E> x) { 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; }
大体思路:
首先找到index索引处的节点(这样就可以方便的获取该节点的前后节点),记为x
记录x的前(prev)后(next)节点
判断x前后节点为null的特殊情况,进行相应处理
将x节点置空,链表长度-1
remove(Object o) 方法作用:从此链表中删除第一次出现的指定元素o,与 removeFirstOccurrence(Object o) 的作用相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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
如果为null,那么循环遍历链表,从头节点开始往后查找,找到第一个节点的数据值为null的,直接删除该节点.
如果非null,那么循环遍历链表,从头节点开始往后查找,找到第一个节点的数据值为o的(通过equals),直接删除该节点。
removeLastOccurrence(Object o) 方法作用:从此链表中删除最后一次出现的指定元素o。
实现:其实和上面的remove(o)是一样的,只不过这里遍历时是从尾节点开始往前查找的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public boolean removeLastOccurrence (Object o) { if (o == null ) { for (Node<E> x = last; x != null ; x = x.prev) { if (x.item == null ) { unlink(x); return true ; } } } else { for (Node<E> x = last; x != null ; x = x.prev) { if (o.equals(x.item)) { unlink(x); return true ; } } } return false ; }
修改元素 set(int index, E element) 1 2 3 4 5 6 7 8 9 10 11 12 public E set (int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
查询元素 element() 方法作用:获取链表第一个元素,方法比较简单,就是将链表头节点数据值进行返回
1 2 3 4 5 6 7 8 9 public E element () { return getFirst(); } public E getFirst () { final Node<E> f = first; if (f == null ) throw new NoSuchElementException(); return f.item; }
get(int index) 方法作用:获取指定索引处元素. 方法比较简单,就是通过node(index)找到index索引处节点,然后返回其数据值
1 2 3 4 5 6 public E get (int index) { checkElementIndex(index); return node(index).item; }
getFirst() 方法作用:获取链表第一个元素.
1 2 3 4 5 6 public E getFirst () { final Node<E> f = first; if (f == null ) throw new NoSuchElementException(); return f.item; }
getLast() 方法作用:获取链表最后一个元素
1 2 3 4 5 6 public E getLast () { final Node<E> l = last; if (l == null ) throw new NoSuchElementException(); return l.item; }
通过 listIterator 遍历 我们先来看看listIterator(int index)方法,就是new了一个ListItr进行返回.ListItr是LinkedList的内部类
1 2 3 4 public ListIterator<E> listIterator (int index) { checkPositionIndex(index); return new ListItr(index); }
接下来,我们看看这个内部类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 private class ListItr implements ListIterator <E > { private Node<E> lastReturned; private Node<E> next; private int nextIndex; private int expectedModCount = modCount; ListItr(int index) { next = (index == size) ? null : node(index); nextIndex = index; } public boolean hasNext () { return nextIndex < size; } public E next () { checkForComodification(); if (!hasNext()) throw new NoSuchElementException(); lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; } public boolean hasPrevious () { return nextIndex > 0 ; } public E previous () { checkForComodification(); if (!hasPrevious()) throw new NoSuchElementException(); lastReturned = next = (next == null ) ? last : next.prev; nextIndex--; return lastReturned.item; } public int nextIndex () { return nextIndex; } public int previousIndex () { return nextIndex - 1 ; } public void remove () { checkForComodification(); if (lastReturned == null ) throw new IllegalStateException(); Node<E> lastNext = lastReturned.next; unlink(lastReturned); if (next == lastReturned) next = lastNext; else nextIndex--; lastReturned = null ; expectedModCount++; } public void set (E e) { if (lastReturned == null ) throw new IllegalStateException(); checkForComodification(); lastReturned.item = e; } public void add (E e) { checkForComodification(); lastReturned = null ; if (next == null ) linkLast(e); else linkBefore(e, next); nextIndex++; expectedModCount++; } public void forEachRemaining (Consumer<? super E> action) { Objects.requireNonNull(action); while (modCount == expectedModCount && nextIndex < size) { action.accept(next.item); lastReturned = next; next = next.next; nextIndex++; } checkForComodification(); } final void checkForComodification () { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
这里的ListIterator有点强
ListIterator只能用于List及其子类型。
有add()方法,可以往链表中添加对象
可以通过hasNext()和next()往后顺序遍历,也可以通过hasPrevious()和previous()实现往前遍历
可以通过nextIndex()和previousIndex()返回当前索引处的位置
可以通过set()实现当前遍历对象的修改
总结 LinkedList的关键点:
底层是双向链表存储数据,并且记录了头节点和尾节点
添加元素非常快,如果是添加到头部和尾部的话更快,因为已经记录了头节点和尾节点,只需要链接一下就行了。如果是添加到链表的中间部分的话,那么多一步操作,需要先找到添加索引处的元素(因为需要链接到这里),才能进行添加。
遍历的时候,建议采用forEach()进行遍历,这样可以在每次获取下一个元素时都非常轻松(next = next.next;)。然后如果是通过fori和get(i)的方式进行遍历的话,效率是极低的,每次get(i)都需要从最前面(或者最后面)开始往后查找i索引处的元素,效率很低.
删除也是非常快,只需要改动一下指针就行了,代价很小。