文档头
- LinkedList是List和Deque接口的双向链表实现,实现所有列表操作,允许null。
- 有关index的操作都会遍历链表,只是从头走或从尾遍历的区别
- 是不同步,需要外部同步操作或 Collections.synchronizedList(new LinkedList(...));
- iterator也是fast-fail类型的,iterator生成后,除了iterator自己的操作,其它对实例的结构性修改都会报ConcurrentModificationException。
变量定义
- 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; } }
LinkedList的基本数据单位,Node会记录上一个、下一个节点的引用,以及自身值。
-
transient int size = 0;
链表长度
-
transient Node<E> first;
表头
-
transient Node<E> last;
表尾
节点的插入、删除
- linkFist(E e) 将e链到表头
private void linkFirst(E e) { final Node<E> f = first; // 获取原first final Node<E> newNode = new Node<>(null, e, f); // 新建节点, prev为null,新节点为 //头,prev指定为null,,next为原first。 first = newNode; // 指定first为新节点 if (f == null) // 如果原first为空,size=0,新节点就是第一个节点,尾也是新节点 last = newNode; else f.prev = newNode; // 否则原first的prev指向新新节点 size++; modCount++; }
-
linkLast(E e) 将e链到表尾
void linkLast(E e) { final Node<E> l = last; // 获取原last final Node<E> newNode = new Node<>(l, e, null); // 创建新node,next为null,prev为 // 原last last = newNode; if (l == null) // 原last为null时,原来没有元素, 这是第一个,所以first也为node。 first = newNode; else l.next = newNode; // 否则原last的next指向现last。 size++; modCount++; }
-
linkBefore(E e, Node succ) 将e链接到succ节点前 , 要求succ节点非空
void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; // 获取succ前一个节点 final Node<E> newNode = new Node<>(pred, e, succ); // 创建新节点,prev为succ前, // next为succ succ.prev = newNode; // succ的prev指向新节点, 将succ链到新节点后。 if (pred == null) // 如果succ原来的前一个节点为null,说明succ原来是first节点,那么 // 现在的first就是新节点 first = newNode; else // succ原来前一个不为null, 将新节点链到其后。 pred.next = newNode; size++; modCount++; }
- unlinkFirst、unlinkLast 分别移除first、last元素,也只是对节点的prev、next修改就可以实现,无需遍历集合
- unlink(Node<E> e) 移除一个非空节点, 可以是头是尾
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) { // 如果前一个为null,说明节点原来是first,移除后的first为节点 // 的next first = next; } else { // 前一个不为null, 移除节点后, 前一个的next应该指向节点的next。 prev.next = next; x.prev = null; } if (next == null) { // 后一个节点为null, 节点为原来的last, 现在的last为节点的前 // 一个 last = prev; } else { // 否则, 后一个节点的prev,指向节点的前一个。 next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
-
node(int index) 获取index处的节点
Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { // 如果index < (size/2)时,从头开始遍历 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { // index > (size/2)时,从尾开始遍历。 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
node方法主要用作内部增删之前,找到index处的节点方便操作,最坏会遍历sise/2个元素。
public方法
public方法都是前面link、unlink、node方法的封装,前面看懂了就很容易。
-
getFirst()、getLast()
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; }
first不为null,返回first、last值
-
removeFirst()、removeLast()
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); }
判断不为null,分别调用unlinkFirst、unlinkLast。
-
addFirst()、addLast()
public void addFirst(E e) { linkFirst(e); } public void addLast(E e) { linkLast(e); }
直接调用
-
indexOf(Object o)
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;
}
IndexOf方法直接从表头开始遍历,找到第一个equals匹配的即返回。最差会遍历到表尾,也就是size个元素。
-
contains(Object o)
public boolean contains(Object o) {
return indexOf(o) != -1;
}
contains方法调用了indexOf(int index),因此也可能遍历全表。
-
get(int index) 、 set(int index, E element)
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; }
get、set通过node(index)查找index处元素,因此会根据index判断从头或是从尾遍历,最多遍历size/2个元素。
总结
- LinkedList由Node构成,是双向链表结构
- LinkedList对头、尾的增删比较高效;对链表中间对象的插入需要先遍历并找到处对象,然后执行引用的修改
- LinkedList contains(o)、indexOf(o)会从头遍历链表,最多遍历全表; get(index)、set(index,e)时,会根据index判断从头还是从尾开始,最差遍历size/2元素。
LinkedList和ArrayList的区别
- ArrayList是基于数组实现,通过新建数组复制元素来扩容;LinkedList是基于双向链表实现,没有容量这个概念,增删元素后直接修改size。
- 从头插入时, LinkedList非常快,只需要新建节点然后改变原first节点的prev,,而ArrayList需要将所有元素后移,因此开销会比较大; 尾部插入时,LinkedList同理很快,,ArrayList容量足够时,只需将新节点存入数组的size位置,也挺快的。中间插入时,LinkedList需要有找到index处元素的开销,这个开销是不一定的;ArrayList需要将index处往后的元素集体移位,开销也不一定。
- get(index)、set(index, e)时,因为ArrayList基于数组,查找效率是常量级的;LinkedList需要找到index处元素。 这里ArrayList肯定比LinkedList快。
- 遍历时, ArrayList支持随机快速访问,可以用for(int i; i<size;i++)循环,这样的效率非常高;LinkedList虽然也可以用fori,或者迭代器,但遍历效率不如ArrayList。