若是说ArrayList是基于数组实现的List,那么LinkedList是基于链表实现的List。java
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
能够看获得LinkedList继承了AbstractSequentialList。实现了List,Deque. 后面两个和ArrayList同样,说明能够被克隆和序列化。数组
而AbstractSequentialList基础自AbstractList,并且还从新实现了get,set,add,remove,等等方法。数据结构
AbstractSequentialList的代码以下:函数
package java.util; public abstract class AbstractSequentialList<E> extends AbstractList<E> { protected AbstractSequentialList() { } public E get(int index) { try { return listIterator(index).next(); } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public E set(int index, E element) { try { ListIterator<E> e = listIterator(index); E oldVal = e.next(); e.set(element); return oldVal; } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public void add(int index, E element) { try { listIterator(index).add(element); } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public E remove(int index) { try { ListIterator<E> e = listIterator(index); E outCast = e.next(); e.remove(); return outCast; } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public boolean addAll(int index, Collection<? extends E> c) { try { boolean modified = false; ListIterator<E> e1 = listIterator(index); Iterator<? extends E> e2 = c.iterator(); while (e2.hasNext()) { e1.add(e2.next()); modified = true; } return modified; } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public Iterator<E> iterator() { return listIterator(); } public abstract ListIterator<E> listIterator(int index); }
而Dqueue接口 是一个双向队列,也就是既能够先入先出,又能够先入后出,再直白一点就是既能够在头部添加元素又在尾部添加元素,既能够在头部获取元素又能够在尾部获取元素。看下Deque的定义:this
public interface Deque<E> extends Queue<E>
private transient Entry<E> header = new Entry<E>(null, null, null); private transient int size = 0;
size和ArrayList里面的size同样,记录容器元素的个数。那这个Entry类型的变量header是个什么鬼。spa
Entry是个内部类,来描述链表的节点的信息,代码以下:.net
//描述链表节点的类 private static class Entry<E> { E element; //存储的对象 Entry<E> next;//链表的下一个节点元素 Entry<E> previous;//链表的上一个节点元素 Entry(E element, Entry<E> next, Entry<E> previous) { this.element = element; this.next = next; this.previous = previous; } }
能够看得出ArrayList底层是采用双向链表来实现的。指针
数据结构双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,可是第一个节点head的pre指向null,最后一个节点的tail指向null。code
//默认构造 public LinkedList() { 链表的头和尾都指向了本身 header.next = header.previous = header; } //将一个集合的元素来构造本身,这些元素按其 collection 的迭代器返回的顺序排列 public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
从默认构造函数能够看得出这是一个双向循环链表,若是是双向不循环链表的话,应该是:对象
header.next=header.previous=null。
有8个增长,5个增长是实现Dqueue里面的添加函数,其他3个是实现List接口里面的添加函数,而后实现Dqueue基本是调用实现List里面的添加函数,因此咱们使用的时候直接调用List里面的添加函数便可。能够少压一次栈。
//这5个add是LinkedList实现Dqueue里面的添加函数,其实都是调用实现List接口里面的函数 //将元素加到第一个,能够看获得,是加到header的后面,由于header就是一个空头,里面没有存储元素 public void addFirst(E e) { addBefore(e, header.next); } //将元素加列表的尾部 public void addLast(E e) { addBefore(e, header); } //封装链表插入操做,分两步走 //1.其实就是在构造的时候,本身的netx和previous指好 //2.本身的前节点的next指向本身,本身后结点的pre指向本身。 private Entry<E> addBefore(E e, Entry<E> entry) { Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); //本身的前节点的next指向本身 newEntry.previous.next = newEntry; //本身后结点的pre指向本身 newEntry.next.previous = newEntry; //记数加1 size++; modCount++; return newEntry; } public boolean offer(E e) { return add(e); } public boolean offerFirst(E e) { addFirst(e); return true; } public boolean offerLast(E e) { addLast(e); return true; } //下面三个增长是实现List接口里面的添加函数 //将元素加列表的尾部 public boolean add(E e) { addBefore(e, header); return true; } //添加一个集合元素到list中 public boolean addAll(Collection<? extends E> c) { //其实仍是调用在指定位置添加一个集合到list中 return addAll(size, c); } //在指定位置添加一个集合元素到list中 public boolean addAll(int index, Collection<? extends E> c) { //检查下标是否越界 if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size); //将集合转换为数组 Object[] a = c.toArray(); //要增长元素的长度 int numNew = a.length; if (numNew==0) return false; modCount++; //找出要插入元素的先后节点 //找出其后节点,若是位置和size大小相等(为何不是size-1,由于header占了一位),则他的下一个节点是header //不然要查找index位置的节点,这个就是他的后一个节点 Entry<E> successor = (index==size ? header : entry(index)); //他的前一个节点,就是index位置的前一个节点。 Entry<E> predecessor = successor.previous; for (int i=0; i<numNew; i++) { Entry<E> e = new Entry<E>((E)a[i], successor, predecessor); //将它前一个节点的next指向本身 predecessor.next = e; //后面的元素插入到这个元素的后面 predecessor = e; } //将index位置的节点的前指针指向本身这样就完成了链表的操做。 successor.previous = predecessor; size += numNew; return true; }
双向链表的增长比起数组的增长稍微是要麻烦的理解一点,本身画图应该不难理解,总结起来,就是一句话主要就是插入会改变前节点的next和后一节点的pre,主要把前一节点的next指向本身,后一节点的pre指向本身,便可。
//删除第一个容器元素 public E removeFirst() { return remove(header.next); } //删除最后一个容器元素 public E removeLast() { return remove(header.previous); } //删除指定的容器元素 public boolean remove(Object o) { if (o==null) { for (Entry<E> e = header.next; e != header; e = e.next) { if (e.element==null) { remove(e); return true; } } } else { //循环遍历节点,而后找到节点,而后删除。时间复杂度O(n) for (Entry<E> e = header.next; e != header; e = e.next) { if (o.equals(e.element)) { remove(e); return true; } } } return false; } //删除指定位置的容器元素 public E remove(int index) { //entry(index)是找到这个节点 return remove(entry(index)); } //删除指定的节点,供本身调用 private E remove(Entry<E> e) { if (e == header) throw new NoSuchElementException(); E result = e.element; //将本身的前一节点的后指针执行本身的后一节点 e.previous.next = e.next; //讲本身后一节点的前指针指向本身的前节点 //讲本身置为null,给gc回收 e.next.previous = e.previous; e.next = e.previous = null; e.element = null; //大小减一 size--; modCount++; return result; }
删除则要简单一点,删除了本身以后,主要
将本身的前一节点的后指针执行本身后一节点 讲本身后一节点的前指针指向本身的前节点
并且能够看到remove(Object o)时间负责度为O(n),而remove(int)的时间复杂度度为O(n/2),由于里面用到了二分查找的办法。因此删除的时候要注意了,要选用正确的方法删除。(其实我转载了一篇博客专门介绍这个LinkenList的局限。)因此之前所说的增删快的删有时也是很慢的。
public E set(int index, E element) { Entry<E> e = entry(index); E oldVal = e.element; e.element = element; return oldVal; }
这个比较简单,查找到,而后修改便可
咱们知道链表和数组相比,查找比数组要慢的很是多,数组直接定位,而链表每次咱们只能拿到一个头部,因此无论找什么,咱们都要从头开始遍历起,而LinkedList使用了双向循环链表,这样遍历起来就会快不少,既能够从头日后找,又能够从后往前找。直到找到index位置。
//查找指定index的链表里面元素 public E get(int index) { return entry(index).element; } //这个方法很重要,基本上查找都是使用这个方法来进行查找。 //这儿就显示双向循环链表的好处,既能够从头日后遍历,又能够从后往前遍历 //这儿使用了二分查找的方法,效率要高不少 private Entry<E> entry(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size); Entry<E> e = header; //若是下标小于size的通常,就从头日后遍历,找到元素 if (index < (size >> 1)) { for (int i = 0; i <= index; i++) e = e.next; } else {不然从后往前遍历 for (int i = size; i > index; i--) e = e.previous; } return e; }
到这里咱们明白,基于双向循环链表实现的LinkedList,经过索引Index的操做时低效的,index所对应的元素越靠近中间所费时间越长。而向链表两端插入和删除元素则是很是高效的(若是不是两端的话,都须要对链表进行遍历查找)。
public boolean contains(Object o) { return indexOf(o) != -1; } public int indexOf(Object o) { int index = 0; if (o==null) { for (Entry e = header.next; e != header; e = e.next) { if (e.element==null) return index; index++; } } else { for (Entry e = header.next; e != header; e = e.next) { if (o.equals(e.element)) return index; index++; } } return -1; } public int lastIndexOf(Object o) { int index = size; if (o==null) { for (Entry e = header.previous; e != header; e = e.previous) { index--; if (e.element==null) return index; } } else { for (Entry e = header.previous; e != header; e = e.previous) { index--; if (o.equals(e.element)) return index; } } return -1; }
要遍历,低效。
打完收工。