3,LinkedList
一,LinkedList简介
1,LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
2,LinkedList 实现List 接口,能对它进行队列操作。
3,LinkedList 实现Deque 接口,即能将LinkedList当作双端队列使用。
4,LinkedList 实现Cloneable接口,即覆盖了函数clone(),能克隆。
5,LinkedList 实现Serializable接口,说明ArrayList支持序列化。
二,数据结构
LinkedList的数据结构如下:
底层使用的双向链表结构,有一个头结点和一个尾结点,双向链表意味着我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作。LinkedList实际上是通过双向链表去实现的。既然是双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低。
三,LinkedList源码
1,LinkedList结构
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; 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; } } 省略...... }
1.1,Node内部类
Node就是实际的结点,用于存放实际元素的地方。
1.2,size
实际元素个数。
1.3,first、last
first表示一个头结点、last表示一个尾结点。注意,头结点、尾结点都有transient关键字修饰,这也意味着在序列化时该域是不会序列化的。
2,构造函数
ArrayList提供了二种方式的构造器,如下:
public LinkedList() { } public LinkedList(Collection<? extends E> c) { // 调用无参构造函数 this(); // 添加集合中所有的元素 addAll(c); }
3,部分函数
3.1,add()函数
public boolean add(E e) { // 添加到末尾 linkLast(e); return true; }
add()函数用于向LinkedList中添加一个元素,并且添加到链表尾部。具体添加到尾部的逻辑是由linkLast函数完成的,具体代码如下:
void linkLast(E e) { // 保存尾结点,l为final类型,不可更改 final Node<E> l = last; // 新生成结点的前驱为l,后继为null final Node<E> newNode = new Node<>(l, e, null); // 重新赋值尾结点 last = newNode; if (l == null) // 尾结点为空 first = newNode; // 赋值头结点 else // 尾结点不为空 l.next = newNode; // 尾结点的后继为新生成的结点 // 大小加1 size++; // 结构性修改加1 modCount++; }
3.2,addAll()函数
//添加指定集合的元素到列表,默认从最后开始添加 public boolean addAll(Collection<? extends E> c) { return addAll(size, c);//size表示最后一个位置,可以理解为元素的位置分别为1~size } //从指定位置(而不是下标!下标即索引从0开始,位置可以看做从1开始,其实也是0)后面添加指定集合的元素到列表中,只要有至少一次添加就会返回true public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index); //检查索引是否正确(0<=index<=size) Object[] a = c.toArray(); //得到元素数组 int numNew = a.length; //得到元素个数 if (numNew == 0) //若没有元素要添加,直接返回false return false; Node<E> pred, succ; //succ后节点,pred前节点 if (index == size) { //如果是在末尾开始添加,当前节点后一个节点初始化为null,前一个节点为尾节点 succ = null; pred = last; } else { //如果不是从末尾开始添加,当前位置的节点为指定位置的节点,前一个节点为要添加的节点的前一个节点 succ = node(index); //获取指定位置的节点 pred = succ.prev; //这里依然是node(index-1) } //遍历数组并添加到列表中 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null);//创建一个节点,向前指向上面得到的前节点 if (pred == null) first = newNode; //若果前节点为null,则新加的节点为首节点 else pred.next = newNode;//如果存在前节点,前节点会向后指向新加的节点 pred = newNode; //新加的节点成为前一个节点 } if (succ == null) { last = pred; //如果是从最后开始添加的,则最后添加的节点成为尾节点 } else { pred.next = succ; //如果不是从最后开始添加的,则最后添加的节点向后指向之前得到的后续第一个节点 succ.prev = pred; //当前,后续的第一个节点也应改为向前指向最后一个添加的节点 } size += numNew; modCount++; return true; }
四,LinkedList遍历方式
LinkedList支持7种遍历方式。
1,迭代器遍历。即通过Iterator去遍历。
for (Iterator<String> iter = linkedList.iterator(); iter.hasNext();) { iter.next(); }
2,通过快速随机访问遍历LinkedList
for (int i = 0; i < linkedList.size(); i++) { linkedList.get(i); }
3,通过for循环来遍历LinkedList
String tString; for (String string : linkedList) { tString = string; }
4,通过pollFirst()来遍历LinkedList
while(linkedList.pollFirst() != null) ;
5,通过pollLast()来遍历LinkedList
while(linkedList.pollLast() != null) ;
6,通过removeFirst()来遍历LinkedList
while(linkedList.removeFirst() != null) ;
7,通过removeLast()来遍历LinkedList
while(linkedList.removeLast() != null) ;
下面通过一个实例,比较这7种方式的效率,代码如下:
public class TestLinkedList { static long startTime = 0; static long endTime = 0; public static void main(String[] args) { LinkedList<String> linkedList = new LinkedList<String>(); for (int i = 0; i < 100000; i++) { linkedList.add(i + ""); } TestLinkedList.loopLinkedList_For(getLinkedList()); TestLinkedList.loopLinkedList_Indexes(getLinkedList()); TestLinkedList.loopLinkedList_Iterator(getLinkedList()); TestLinkedList.loopLinkedList_pollFirst(getLinkedList()); TestLinkedList.loopLinkedList_pollLast(getLinkedList()); TestLinkedList.loopLinkedList_removeFirst(getLinkedList()); TestLinkedList.loopLinkedList_removeLast(getLinkedList()); } private static LinkedList<String> getLinkedList() { LinkedList<String> llist = new LinkedList<String>(); for (int i=0; i<100000; i++) llist.addLast(i + ""); return llist; } //快速随机访问遍历LinkedList public static void loopLinkedList_Indexes(LinkedList<String> linkedList){ startTime = System.currentTimeMillis(); for (int i = 0; i < linkedList.size(); i++) { linkedList.get(i); } endTime = System.currentTimeMillis(); long interval = endTime - startTime; System.out.println("快速随机访问遍历LinkedList(loopLinkedList_Indexes):" + interval + " ms"); } //通过迭代器遍历。即通过Iterator去遍历。 public static void loopLinkedList_Iterator(LinkedList<String> linkedList){ startTime = System.currentTimeMillis(); for (Iterator<String> iter = linkedList.iterator(); iter.hasNext();) { iter.next(); } endTime = System.currentTimeMillis(); long interval = endTime - startTime; System.out.println("通过迭代器遍历(loopLinkedList_Iterator):" + interval + " ms"); } //通过for循环遍历LinkedList public static void loopLinkedList_For(LinkedList<String> linkedList){ startTime = System.currentTimeMillis(); String tString; for (String string : linkedList) { tString = string; } endTime = System.currentTimeMillis(); long interval = endTime - startTime; System.out.println("通过for循环遍历(loopLinkedList_For):" + interval + " ms"); } //通过pollFirst()来遍历LinkedList public static void loopLinkedList_pollFirst(LinkedList<String> linkedList){ startTime = System.currentTimeMillis(); while(linkedList.pollFirst() != null) ; endTime = System.currentTimeMillis(); long interval = endTime - startTime; System.out.println("通过pollFirst()来遍历(loopLinkedList_pollFirst):" + interval + " ms"); } //通过pollLast()来遍历LinkedList public static void loopLinkedList_pollLast(LinkedList<String> linkedList){ startTime = System.currentTimeMillis(); while(linkedList.pollLast() != null) ; endTime = System.currentTimeMillis(); long interval = endTime - startTime; System.out.println("通过pollLast()来遍历(loopLinkedList_pollLast):" + interval + " ms"); } //通过removeFirst()来遍历LinkedList public static void loopLinkedList_removeFirst(LinkedList<String> linkedList) { if (linkedList == null) return ; long start = System.currentTimeMillis(); try { while(linkedList.removeFirst() != null) ; } catch (NoSuchElementException e) { } long end = System.currentTimeMillis(); long interval = end - start; System.out.println("通过removeFirst()来遍历(loopLinkedList_removeFirst):" + interval + " ms"); } //通过removeLast()来遍历LinkedList public static void loopLinkedList_removeLast(LinkedList<String> linkedList) { if (linkedList == null) return ; long start = System.currentTimeMillis(); try { while(linkedList.removeLast() != null) ; } catch (NoSuchElementException e) { } long end = System.currentTimeMillis(); long interval = end - start; System.out.println("通过removeLast()来遍历(loopLinkedList_removeLast):" + interval + " ms"); } }
运行结果:
由此可见,遍历LinkedList时,使用removeFist()或removeLast()效率最高(删除原始数据)。不建议随机访问遍历LinkedList。
五,LinkedList应用举例
public class TestLinkedList2 { static long startTime = 0; static long endTime = 0; public static void main(String[] args) { TestLinkedList2.test(); System.out.println("------------------------------------------"); TestLinkedList2.likeStack(); System.out.println("------------------------------------------"); TestLinkedList2.likeQueue(); } public static void test(){ LinkedList<String> linkedList = new LinkedList<String>(); linkedList.add("ZenderA"); linkedList.add("ZenderB"); linkedList.add("ZenderC"); linkedList.add("ZenderD"); linkedList.add("ZenderE"); System.out.println("linkedList:" + linkedList); System.out.println("------------------------------------------"); // 将“替换的内容”添加到第索引为1的个位置 linkedList.add(1, "替换的内容"); //linkedList.addFirst("替换的内容");同样的效果 System.out.println("添加E到索引为1的个位置后的linkedList:" + linkedList); System.out.println("------------------------------------------"); //将“addZender”添加到第一个位置。 返回true。 linkedList.offerFirst("addZenderFirst"); //将“addZender”添加到最后一个位置。 返回true。 linkedList.offerLast("addZenderLast"); System.out.println("将addZender添加到第一个位置和最后一个位置后的linkedList:" + linkedList); System.out.println("------------------------------------------"); //删除第一个元素/删除最后一个元素并获得并返回被删除元素 System.out.println("删除第一个元素并获得并返回被删除元素:" + linkedList.removeFirst()); System.out.println("删除最后一个元素并获得并返回被删除元素:" + linkedList.removeLast()); System.out.println("删除第一个/最后一个元素后的linkedList:" + linkedList); System.out.println("------------------------------------------"); // 将第3个元素设置Zender,并返回旧值。 String text = linkedList.set(2, "Zender"); System.out.println(text); System.out.println("替换后的linkedList:" + linkedList); } //将LinkedList作为栈(先进后出)使用 public static void likeStack() { LinkedList<String> stack = new LinkedList<String>(); //向栈中添加元素 stack.push("A"); stack.push("B"); stack.push("C"); stack.push("D"); System.out.println("stack:"+stack); stack.pop() ;// 删除“栈顶元素” System.out.println("删除栈顶后的stack:"+stack); System.out.println("栈顶元素:"+stack.peek()); // 取出栈顶 } //将LinkedList作为队列(先进先出)使用 public static void likeQueue() { LinkedList<String> queue = new LinkedList<String>(); //向队列中添加元素 queue.add("A"); queue.add("B"); queue.add("C"); queue.add("D"); System.out.println("queue:"+queue); queue.remove() ;//删除队列的第一个元素 System.out.println("删除队列的第一个元素后queue:"+queue); System.out.println("队列第一个元素:"+queue.element()); // 读取队列的第一个元素 } }
运行结果:
六,常用函数
//在指定节点前插入节点,节点succ不能为空 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;//如果前一个节点为null,新的节点就是首节点 else pred.next = newNode;//如果存在前节点,那么前节点的向后指向新节点 size++; modCount++; } //删除首节点并返回删除前首节点的值,内部使用 private E unlinkFirst(Node<E> f) { final E element = f.item;//获取首节点的值 final Node<E> next = f.next;//得到下一个节点 f.item = null; f.next = null; //便于垃圾回收期清理 first = next; //首节点的下一个节点成为新的首节点 if (next == null) last = null; //如果不存在下一个节点,则首尾都为null(空表) else next.prev = null;//如果存在下一个节点,那它向前指向null size--; modCount++; return element; } //删除尾节点并返回删除前尾节点的值,内部使用 private E unlinkLast(Node<E> l) { final E element = l.item;//获取值 final Node<E> prev = l.prev;//获取尾节点前一个节点 l.item = null; l.prev = null; //便于垃圾回收期清理 last = prev; //前一个节点成为新的尾节点 if (prev == null) first = null; //如果前一个节点不存在,则首尾都为null(空表) else prev.next = null;//如果前一个节点存在,先后指向null size--; modCount++; return element; } //删除指定节点并返回被删除的元素值 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; } //获取第一个元素 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; } //删除第一个元素并返回删除的元素 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); } //添加元素作为第一个元素 public void addFirst(E e) { linkFirst(e); } //店家元素作为最后一个元素 public void addLast(E e) { linkLast(e); } //检查是否包含某个元素,返回bool public boolean contains(Object o) { return indexOf(o) != -1;//返回指定元素的索引位置,不存在就返回-1,然后比较返回bool值 } //返回列表长度 public int size() { return size; } //添加一个元素,默认添加到末尾作为最后一个元素 public boolean add(E e) { linkLast(e); return true; } //删除指定元素,默认从first节点开始,删除第一次出现的那个元素 public boolean remove(Object o) { //会根据是否为null分开处理。若值不是null,会用到对象的equals()方法 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; } //添加指定集合的元素到列表,默认从最后开始添加 public boolean addAll(Collection<? extends E> c) { return addAll(size, c);//size表示最后一个位置,可以理解为元素的位置分别为1~size } //从指定位置(而不是下标!下标即索引从0开始,位置可以看做从1开始,其实也是0)后面添加指定集合的元素到列表中,只要有至少一次添加就会返回true public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index); //检查索引是否正确(0<=index<=size) Object[] a = c.toArray(); //得到元素数组 int numNew = a.length; //得到元素个数 if (numNew == 0) //若没有元素要添加,直接返回false return false; Node<E> pred, succ; //succ后节点,pred前节点 if (index == size) { //如果是在末尾开始添加,当前节点后一个节点初始化为null,前一个节点为尾节点 succ = null; pred = last; } else { //如果不是从末尾开始添加,当前位置的节点为指定位置的节点,前一个节点为要添加的节点的前一个节点 succ = node(index); //获取指定位置的节点 pred = succ.prev; //这里依然是node(index-1) } //遍历数组并添加到列表中 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null);//创建一个节点,向前指向上面得到的前节点 if (pred == null) first = newNode; //若果前节点为null,则新加的节点为首节点 else pred.next = newNode;//如果存在前节点,前节点会向后指向新加的节点 pred = newNode; //新加的节点成为前一个节点 } if (succ == null) { last = pred; //如果是从最后开始添加的,则最后添加的节点成为尾节点 } else { pred.next = succ; //如果不是从最后开始添加的,则最后添加的节点向后指向之前得到的后续第一个节点 succ.prev = pred; //当前,后续的第一个节点也应改为向前指向最后一个添加的节点 } size += numNew; modCount++; return true; } //清空表 public void clear() { //方便gc回收垃圾 for (Node<E> x = first; x != null; ) { Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; } //获取指定索引的节点的值 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; } //指定位置后面(即索引为这个值的元素的前面)添加元素 public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); //如果指定位置为最后,则添加到链表最后 else //如果指定位置不是最后,则添加到指定位置前 linkBefore(element, node(index)); } //删除指定位置的元素, public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } //检查索引是否超出范围,因为元素索引是0~size-1的,所以index必须满足0<=index<size private boolean isElementIndex(int index) { return index >= 0 && index < size; } //检查位置是否超出范围,index必须在index~size之间(含),如果超出,返回false private boolean isPositionIndex(int index) { return index >= 0 && index <= size; } //异常详情 private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; } //检查元素索引是否超出范围,若已超出,就抛出异常 private void checkElementIndex(int index) { if (!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } //检查位置是否超出范围,若已超出,就抛出异常 private void checkPositionIndex(int index) { if (!isPositionIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } //获取指定位置的节点 Node<E> node(int index) { //如果位置索引小于列表长度的一半(或一半减一),从前面开始遍历;否则,从后面开始遍历 if (index < (size >> 1)) { Node<E> x = first;//index==0时不会循环,直接返回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; } } //获取指定元素从first开始的索引位置,不存在就返回-1 //不能按条件双向找了,所以通常根据索引获得元素的速度比通过元素获得索引的速度快 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; } //获取指定元素从first开始最后出现的索引,不存在就返回-1 //但实际查找是从last开始的 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; } //提供普通队列和双向队列的功能,当然,也可以实现栈,FIFO,FILO //出队(从前端),获得第一个元素,不存在会返回null,不会删除元素(节点) public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } //出队(从前端),不删除元素,若为null会抛出异常而不是返回null public E element() { return getFirst(); } //出队(从前端),如果不存在会返回null,存在的话会返回值并移除这个元素(节点) public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } //出队(从前端),如果不存在会抛出异常而不是返回null,存在的话会返回值并移除这个元素(节点) public E remove() { return removeFirst(); } //入队(从后端),始终返回true public boolean offer(E e) { return add(e); } //入队(从前端),始终返回true public boolean offerFirst(E e) { addFirst(e); return true; } //入队(从后端),始终返回true public boolean offerLast(E e) { addLast(e);//linkLast(e) return true; } //出队(从前端),获得第一个元素,不存在会返回null,不会删除元素(节点) public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; } //出队(从后端),获得最后一个元素,不存在会返回null,不会删除元素(节点) public E peekLast() { final Node<E> l = last; return (l == null) ? null : l.item; } //出队(从前端),获得第一个元素,不存在会返回null,会删除元素(节点) public E pollFirst() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } //出队(从后端),获得最后一个元素,不存在会返回null,会删除元素(节点) public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l); } //入栈,从前面添加 public void push(E e) { addFirst(e); } //出栈,返回栈顶元素,从前面移除(会删除) public E pop() { return removeFirst(); } //节点的数据结构,包含前后节点的引用和当前节点 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 Iterator<E> descendingIterator() { return new DescendingIterator(); } //因为采用链表实现,所以迭代器很简单 private class DescendingIterator implements Iterator<E> { private final ListItr itr = new ListItr(size()); public boolean hasNext() { return itr.hasPrevious(); } public E next() { return itr.previous(); } public void remove() { itr.remove(); } }