一:LinkedList简介
LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,即LinkedList支持序列化,能通过序列化去传输。
LinkedList 是非同步的。
LinkedList也和ArrayList一样实现了List接口,但是它执行插入和删除操作时比ArrayList更加高效,因为它是基于链表的。基于链表也决定了它在随机访问方面要比ArrayList逊色一点。
除此之外,LinkedList还提供了一些可以使其作为栈、队列、双端队列的方法。这些方法中有些彼此之间只是名称的区别,以使得这些名字在特定的上下文中显得更加的合适。
二:LinkedList源码解析
对于LinkedList而言,它实现List接口、底层使用Entry保存节点对象。其操作基本上是对双链节点的操作。下面我们来分析LinkedList的源代码:
私有属性
LinkedList只定义了两个私有属性:private transient Entry<E> header = new Entry<E>(null, null, null); private transient int size = 0;
size肯定就是LinkedList对象里面存储的元素个数了。LinkedList既然是基于链表实现的,那么这个header肯定就是链表的头结点了,Entry就是节点对象了。以下是Entry类的代码。
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; } }
只定义了存储的元素、前一个元素、后一个元素,很明显就是双向链表的节点的定义,每个节点只知道自己的上一个节点和下一个节点。
构造方法
LinkedList提供了两个构造方法。
第一个构造方法不接受参数,只是将header节点的前一节点和后一节点都设置为自身,这样整个链表其实就只有header一个节点,用于表示一个空的链表。下面是其源码:/** * Constructs an empty list. */ public LinkedList() { header.next = header.previous = header; }
(注意,这个是一个双向循环链表,如果不是循环链表,空链表的情况应该是header节点的前一节点和后一节点均为null)
第二个构造方法接收一个Collection参数c,调用第一个构造方法构造一个空的链表,之后通过addAll将c中的元素全部添加到链表中。
public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
接下来我们来看看addAll的内容:
public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } // index参数指定collection中插入的第一个元素的位置 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++; Entry<E> successor = (index==size ? header : entry(index)); Entry<E> predecessor = successor.previous; for (int i=0; i<numNew; i++) { Entry<E> e = new Entry<E>((E)a[i], successor, predecessor); predecessor.next = e; predecessor = e; } successor.previous = predecessor; size += numNew; return true; }
整个添加过程,其实就是将Collection转换成toArray数组之后进行遍历添加;与C语言中的单双链别无二致。
三:LinkedList的方法解析
数据存储
LinkedList提供了add(E e)、add(int index, E element)、addAll(Collection<?extends E> c)、addAll(int index, Collection< ? extends E> c)、addBefore(E e, Entry entry)、addFirst(E e)、addLast(E e)这些添加元素的方法。下面我们一一讲解:
//添加一个元素,在头之前 public boolean add(E e) { addBefore(e, header); return true; } //在特点位置添加一元素 public void add(int index, E element) { addBefore(element, (index==size ? header : entry(index))); } //该方法在构造方法中已使用,即批量添加 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++; Entry<E> successor = (index==size ? header : entry(index)); Entry<E> predecessor = successor.previous; for (int i=0; i<numNew; i++) { Entry<E> e = new Entry<E>((E)a[i], successor, predecessor); predecessor.next = e; predecessor = e; } successor.previous = predecessor; size += numNew; return true; } //调用上一个方法 public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } //在某节点对象之后添加一对象e,私有的 private Entry<E> addBefore(E e, Entry<E> entry) { Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; size++; modCount++; return newEntry; } //在首位添加 public void addFirst(E e) { addBefore(e, header.next); } //在末尾添加 public void addLast(E e) { addBefore(e, header); }
整个数据添加过程,重点在于 addAll和addBefore两个方法的实现上,也就是对节点之间的操作上;
数据读取
读取的话主要有 get(int index)、getFirst()、getLast();即获取index索引处数据和首末尾数据,下面看看具体实现:public E get(int index) { return entry(index).element; } public E getFirst() { if (size==0) throw new NoSuchElementException(); return header.next.element; } public E getLast() { if (size==0) throw new NoSuchElementException(); return header.previous.element; }
数据修改
数据修改就一个方式,即set(int index, E element),对特定index索引处数据进行覆盖操作public E set(int index, E element) { Entry<E> e = entry(index); E oldVal = e.element; e.element = element; return oldVal; }
数据删除
//删除第一个 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 { for (Entry<E> e = header.next; e != header; e = e.next) { if (o.equals(e.element)) { remove(e); return true; } } } return false; } //清除所有 public void clear() { Entry<E> e = header.next; while (e != header) { Entry<E> next = e.next; e.next = e.previous = null; e.element = null; e = next; } header.next = header.previous = header; size = 0; modCount++; } //删除特定index处 public E remove(int index) { return remove(entry(index)); }
转出数组
//将本身转换为数组 public Object[] toArray() { Object[] result = new Object[size]; int i = 0; for (Entry<E> e = header.next; e != header; e = e.next) result[i++] = e.element; return result; } //将特定数进行转换 public <T> T[] toArray(T[] a) { if (a.length < size) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size); int i = 0; Object[] result = a; for (Entry<E> e = header.next; e != header; e = e.next) result[i++] = e.element; if (a.length > size) a[size] = null; return a; }
先判断出入的数组a的大小是否足够,若大小不够则拓展。这里用到了发射的方法,重新实例化了一个大小为size的数组。之后将数组a赋值给数组result,遍历链表向result中添加的元素。最后判断数组a的长度是否大于size,若大于则将size位置的内容设置为null。返回a。
从代码中可以看出,数组a的length小于等于size时,a中所有元素被覆盖,被拓展来的空间存储的内容都是null;若数组a的length的length大于size,则0至size-1位置的内容被覆盖,size位置的元素被设置为null,size之后的元素不变。
其他方法
clone():调用父类的clone()方法初始化对象链表clone,将clone构造成一个空的双向循环链表,之后将header的下一个节点开始将逐个节点添加到clone中。最后返回克隆的clone对象
public Object clone() { LinkedList<E> clone = null; try { clone = (LinkedList<E>) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } // Put clone into "virgin" state clone.header = new Entry<E>(null, null, null); clone.header.next = clone.header.previous = clone.header; clone.size = 0; clone.modCount = 0; // Initialize clone with our elements for (Entry<E> e = header.next; e != header; e = e.next) clone.add(e.element); return clone; }
clear():将整个集合容器进行清空操作;
public void clear() { Entry<E> e = header.next; while (e != header) { Entry<E> next = e.next; e.next = e.previous = null; e.element = null; e = next; } header.next = header.previous = header; size = 0; modCount++; }
四:总结
- LinkedList 实际上是通过双向链表去实现的。
它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。 - 从LinkedList的实现方式中可以发现,它不存在LinkedList容量不足的问题。
- LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
- LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
- 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,该对象提供了一些等价,如下表;
队列方法 等效方法 add(e) addLast(e) offer(e) offerLast(e) remove() removeFirst() poll() pollFirst() element() getFirst() peek() peekFirst()
LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,该对象提供了一些等价方法,如下表:
栈方法 等效方法 push(e) addFirst(e) pop() removeFirst() peek() peekFirst()