LinkedList实现了List,Deque,Cloneable,Serializable接口,具有集合,队列,克隆以及序列化功能。使用泛型在编译期提早发现类型错误。
LinkedList使用私有内部类Node保存集合中元素的信息,本例分析一些主要的方法,比如add方法,其方法内部调用了linkLast方法,那就只查看linkLast方法。
添加节点,主要是确定并设置链表首节点和尾节点以及新添加节点的位置。
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; } }
linkFirst
头部添加元素,先定位当前要添加的元素的位置,其pre节点为null,next节点为当前的first,如果链表的首节点或者尾节点为空,那么这个链表必定为空
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 // 原首节点不为空,则其pre节点一定指向当前新加节点,其next节点没有变化 f.prev = newNode; size++; modCount++; }
linkLast
尾部添加元素,先定位当前要添加的元素的位置,其pre节点为当前的last节点,next节点为null
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 // 尾节点的next节点一定指向当前新加节点,其pre节点没有变化 l.next = newNode; size++; modCount++; }
addAll(int index, Collection<? extends E> c)
1、判断索引及添加的集合是否为空
2、将要添加的集合作为一个整体,获取其前驱节点pred和后继节点succ
3、将集合中的每个元素依次添加到链表中
4、处理要添加的集合的最后一个元素的next节点/链表尾部节点
public boolean addAll(int index, Collection<? extends E> c) { // 检测索引是否合法 checkPositionIndex(index); // 要添加的集合为空,返回false Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; // 将当前Collection看成一个整体,为简单,称为cton,获取其前驱节点pred和后继节点succ Node<E> pred, succ; if (index == size) { // 在尾部添加 succ = null; pred = last; } else { // succ = node(index); pred = succ.prev; } // 遍历要添加的元素,确定每个元素在LinkedList中的前一个pred及succ节点,最终确定其位置 for (Object o : a) { E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } /* * 经过上述遍历,pred为cton中最后一个元素的节点。cton集合中size-1个元素的pred和succ节点都已经确定, * 但最后一个元素的next节点为null,不能够确定是否准确 */ if (succ == null) { // 后继节点为null,则以上操作结束后,除了链表的尾部节点需要调整之外,其他节点的位置都已确定。 last = pred; } else { // cton中最后一个元素节点next节点为cton的succ节点,succ节点的pred节点为cton最后一个元素节点pred pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true; }
linkBefore(E e, Node<E> succ)
在某个元素前添加节点
1、获取元素的前驱节点
2、确定要添加的节点的位置
3、设置要添加元素的后继节点的前驱节点,前驱节点的后继节点
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++; }
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; }
node(int)
获取指定节点的元素,二分查找,提高效率
1、判断index是否小于链表元素的一半
2、若是,则从左侧开始查找,若否,则从右侧开始查找
3、将每次查找节点重新复制给原数据,则在index-1节点获取的节点的next即为所需要的节点
Node<E> node(int index) { if (index < (size >> 1)) { // 左侧开始遍历,初始值为首节点 Node<E> x = first; for (int i = 0; i < index; i++) // 将当前节点的next节点重新赋值给x变量 x = x.next; // index-1位置的node节点的next即为我们所查找的节点 return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) // 将当前节点的next节点重新赋值给x变量 x = x.prev; // size-(index +1)节点的prev节点为我们所查找的节点 return x; } }
set(int index, E element)
修改指定位置元素
1、验证索引是否超出范围
2、获取index处原节点
3、将index处原节点的值设置为当前值
public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
indexOf(Object o)
从首节点遍历,查找首次出现与item值相同的节点所在位置,要查找的元素必须实现equals方法
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; }
lastIndexOf(Object o)
从尾节点遍历,查找首次出现于item值相同的节点所在位置,要查找的元素必须实现equals方法
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; }
unlinkFirst
获取首节点的next节点,将首节点的next节点和item设置为null,将first设置为原首节点的next节点,原首节点的next节点的pre节点设置为null
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; }
unlinkLast
获取尾节点的pre节点,将尾节点的pre及item设置为null,将尾节点设置为原尾节点的pre节点,原尾节点的pre节点的next节点设置为null
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; }
remove(object)
1、找到要删除的元素对应的节点
2、找到该节点的前一个元素和后一个元素
3、设置前后节点的对应关系
4、将当前节点的前驱节点和后继节点以及item设置为null
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; } 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; }