以前已经写了几篇有关Java集合的文章:java
今天咱们来介绍一下另一个容器类:LinkedList
。node
LinkedList
和ArrayList
同样是集合List的实现类,虽然较之ArrayList,其使用场景并很少,但一样有用到的时候,那么接下来,咱们来认识一下它。segmentfault
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{//底层是双向链表 //元素数量 transient int size = 0; //第一个结点 transient Node<E> first; //最后一个结点 transient Node<E> last; .....
其实LinkedList
底层使用双向链表实现的,能够看到上面有first
和last
两个Node节点,来看看其内部类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; }
很简单,学过链表的同窗应该都很清楚。函数
那首先咱们仍是来看看构造函数:性能
public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
addAll(Collection<? extends E> c)
方法用来添加指定的集合数据到LinkedList
中。优化
在去看LinkedList
的get
、add
等方法前,咱们先去看下几个比较重要的辅助函数:ui
linkFirst(E e)
,该方法用于插入元素到链表头部。/* * 插入元素到头部 */ private void linkFirst(E e) { final Node<E> f = first; // 设置newNode的前结点为null,后结点为f final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) // 首先连接元素,同时把newNode设为最后一个结点 last = newNode; else f.prev = newNode; size++; modCount++; }
回忆一下内部类 Node
的构造函数:Node(Node<E> prev, E element, Node<E> next)
,三个参数分别前一个结点、元素值以及下一个结点。this
从上面能够看出在插入元素到链表头部其实建立一个Node
结点,让其 next 指针指向链表首结点first
。spa
/* * 插入元素到尾部 */ void linkLast(E e) { final Node<E> l = last; // 设置newNode的前结点为l,后结点为null final Node<E> newNode = new Node<>(l, e, null); // 新结点变成最后一个结点 last = newNode; // 若l == null说明是首次连接元素,将first也指向新结点 if (l == null) first = newNode; else l.next = newNode; size++; // 修改次数+1 modCount++; }
插入到尾部和上面插入到头部的方法也有殊途同归之妙,这里就再也不细说。
/* * 在给定结点前插入元素e */ void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev; // 设置newNode的前结点为pred,后结点为succ final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null)//若是succ是头结点,将newNode设置为头结点 first = newNode; else pred.next = newNode; size++; modCount++; }
很简单,就是让给定结点succ
的 prev 指针和前一个结点的 next 指针都指向咱们的新结点就能够了。
/* * 删除链表结点 */ 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;//GC回收 } //判断是不是尾结点 if (next == null) { last = prev; } else { next.prev = prev; x.next = null;//GC回收 } x.item = null;//GC回收 size--; modCount++; return element; }
注意这里咱们删除结点 x 后要把x的先后引用包括自身引用都设为null,这样JVM垃圾回收才会帮咱们去回收x,不然会有内存泄漏的隐患。
还有最后一个辅助函数,用来获取指定位置的结点。
/* *返回指定位置的结点 */ Node<E> node(int index) { //根据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; } }
这里有一个值得咱们借鉴的:就是会根据索引的位置来判读是从头部仍是尾部遍历,这也是一个性能的小优化。
有了辅助函数后咱们就能够来看看咱们日常使用的比较多的API了
首先来看看get
和set
方法:
get(int index)
方法用来获取指定位置上的元素
/* * 返回指定位置元素 */ 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; }
能够看到先调用checkElementIndex
方法校验参数,而后调用辅助方法node(int index)
获取元素值。
/* * 设置元素 */ public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
set(int index, E element)
方法用来设置元素,比较简单再也不细说。
接下来看看另外两个方法:
peek
方法用于返回头结点,而pool
用于删除头结点并返回头结点的值。
/* * 返回头结点值,若是链表为空则返回null */ public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } /* * 删除头结点并返回头结点值,若是链表为空则返回null */ public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); }
如今来看看如何删除一个结点:
/* * 默认删除头结点 */ public E remove() { return removeFirst(); } /* * 删除指定位置结点 */ public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } /* * 删除指定结点 */ 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; }
其实只要搞懂上面几个辅助函数后,其它的增删查改就很简单了。
继续来看另一个index(Object o)
方法,该方法用于返回指定对象在链表中的索引。
/* * 返回指定对象在链表中的索引(若是没有则返回-1) * lastIndexOf同理(其实就是从后向前遍历) */ 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; }
如今咱们如何把LinkedList
转成数组返回呢?来看看toArray
方法:
/* * 将链表包装成数组返回 */ public Object[] toArray() { Object[] result = new Object[size]; int i = 0; //依次取出结点值放入数组 for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; return result; }
还有一个toArray(T[] a)
方法,能够返回指定类型的数组:
/* * 将链表包装成指定类型数组返回 */ public <T> T[] toArray(T[] a) { if (a.length < size)//给点的数组长度小于链表长度 //建立一个类型与a同样,长度为size的数组 a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); int i = 0; Object[] result = a;//定义result指向给定数组,修改result == 修改a //依次把结点值放入result数组 for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; if (a.length > size) a[size] = null; return a; }
LinkedList
和ArrayList
还有一个比较大的区别是LinkedList
除了iterator
方法以外还有一个listIterator
方法,该迭代器除了向后遍历数据外,也能够向前遍历,正是因为底层的双向链表结构才能实现。
public ListIterator<E> listIterator(int index) { checkPositionIndex(index);//参数校验 return new ListItr(index); } 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);//index默认为0 nextIndex = index; } /*判断是否有下一个元素*/ public boolean hasNext() { return nextIndex < size; } /*向后遍历,返回越过的元素*/ public E next() { checkForComodification();//fail-fast if (!hasNext()) throw new NoSuchElementException(); lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; } /*判断是否有上一个元素*/ public boolean hasPrevious() { return nextIndex > 0; } /*向前遍历,返回越过的元素*/ public E previous() { checkForComodification();//fail-fast if (!hasPrevious()) throw new NoSuchElementException(); lastReturned = next = (next == null) ? last : next.prev;//调用previous后lastReturned = next nextIndex--; return lastReturned.item; } /*返回下一个越过的元素索引*/ public int nextIndex() { return nextIndex; } /*返回上一个越过的元素索引*/ public int previousIndex() { return nextIndex - 1; } /*删除元素*/ public void remove() { checkForComodification();//fail-fast if (lastReturned == null) throw new IllegalStateException(); Node<E> lastNext = lastReturned.next; unlink(lastReturned);//从链表中删除lastReturned,modCount++(该方法会帮你处理结点指针指向) if (next == lastReturned)//调用previous后next == lastReturned next = lastNext; else nextIndex--; lastReturned = null;//GC expectedModCount++; } /*设置元素*/ public void set(E e) { if (lastReturned == null) throw new IllegalStateException(); checkForComodification();//fail-fast lastReturned.item = e; } /*插入元素*/ public void add(E e) { checkForComodification();//fail-fast 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(); } /*fail-fast*/ final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
今天有关LinkedList
的源码分就暂时先到这里,其实若是理解了链表结构那么上面源码应该不是很难,若是有什么不对的地方请多多指教。