數(shù)據(jù)結(jié)構(gòu)(一)——線(xiàn)性表、棧和隊(duì)列

數(shù)據(jù)結(jié)構(gòu)是編程的起點(diǎn),理解數(shù)據(jù)結(jié)構(gòu)可以從三方面入手:

  1. 邏輯結(jié)構(gòu)。邏輯結(jié)構(gòu)是指數(shù)據(jù)元素之間的邏輯關(guān)系,可分為線(xiàn)性結(jié)構(gòu)和非線(xiàn)性結(jié)構(gòu),線(xiàn)性表是典型的線(xiàn)性結(jié)構(gòu),非線(xiàn)性結(jié)構(gòu)包括集合、樹(shù)和圖。
  2. 存儲(chǔ)結(jié)構(gòu)。存儲(chǔ)結(jié)構(gòu)是指數(shù)據(jù)在計(jì)算機(jī)中的物理表示,可分為順序存儲(chǔ)、鏈?zhǔn)酱鎯?chǔ)、索引存儲(chǔ)和散列存儲(chǔ)。數(shù)組是典型的順序存儲(chǔ)結(jié)構(gòu);鏈表采用鏈?zhǔn)酱鎯?chǔ);索引存儲(chǔ)的優(yōu)點(diǎn)是檢索速度快,但需要增加附加的索引表,會(huì)占用較多的存儲(chǔ)空間;散列存儲(chǔ)使得檢索、增加和刪除結(jié)點(diǎn)的操作都很快,缺點(diǎn)是解決散列沖突會(huì)增加時(shí)間和空間開(kāi)銷(xiāo)。
  3. 數(shù)據(jù)運(yùn)算。施加在數(shù)據(jù)上的運(yùn)算包括運(yùn)算的定義和實(shí)現(xiàn)。運(yùn)算的定義是針對(duì)邏輯結(jié)構(gòu)的,指出運(yùn)算的功能;運(yùn)算的實(shí)現(xiàn)是針對(duì)存儲(chǔ)結(jié)構(gòu)的,指出運(yùn)算的具體操作步驟。

因此,本章將以邏輯結(jié)構(gòu)(線(xiàn)性表、樹(shù)、散列、優(yōu)先隊(duì)列和圖)為縱軸,以存儲(chǔ)結(jié)構(gòu)和運(yùn)算為橫軸,分析常見(jiàn)數(shù)據(jù)結(jié)構(gòu)的定義和實(shí)現(xiàn)。


在Java中談到數(shù)據(jù)結(jié)構(gòu)時(shí),首先想到的便是下面這張Java集合框架圖:

Java 集合框架圖

從圖中可以看出,Java集合類(lèi)大致可分為L(zhǎng)ist、Set、Queue和Map四種體系,其中:

  • List代表有序、重復(fù)的集合;
  • Set代表無(wú)序、不可重復(fù)的集合;
  • Queue代表一種隊(duì)列集合實(shí)現(xiàn);
  • Map代表具有映射關(guān)系的集合。

在實(shí)現(xiàn)上,List、Set和Queue均繼承自Collection,因此,Java集合框架主要由Collection和Map兩個(gè)根接口及其子接口、實(shí)現(xiàn)類(lèi)組成。


本文將重點(diǎn)探討線(xiàn)性表的定義和實(shí)現(xiàn),首先梳理Collection接口及其子接口的關(guān)系,其次從存儲(chǔ)結(jié)構(gòu)(順序表和鏈表)維度分析線(xiàn)性表的實(shí)現(xiàn),最后通過(guò)線(xiàn)性表結(jié)構(gòu)導(dǎo)出棧、隊(duì)列的模型與實(shí)現(xiàn)原理。主要內(nèi)容如下:

  1. Iterator、Collection及List接口
  2. ArrayList / LinkedList實(shí)現(xiàn)原理
  3. Stack / Queue模型與實(shí)現(xiàn)

一、Iterator、Collection及List接口

Collection接口是ListSetQueue的根接口,抽象了集合類(lèi)所能提供的公共方法,包含size()isEmpty()add(E e)remove(Object o)contains(Object o)等,iterator()返回集合類(lèi)迭代器。

public interface Collection<E> extends Iterable<E> {
    int size();
    boolean isEmpty();
    Iterator<E> iterator();
    boolean add(E e);
    boolean addAll(Collection<? extends E> c);
    boolean remove(Object o);
    boolean removeAll(Collection<?> c);
    boolean contains(Object o);
    boolean containsAll(Collection<?> c);
    void clear();
    boolean equals(Object o);
    int hashCode();
}

Collection接口繼承自Iterable接口,實(shí)現(xiàn)Iterable接口的集合類(lèi)可以通過(guò)迭代器對(duì)元素進(jìn)行安全、高效的遍歷,比如for-each,Iterableiterator方法負(fù)責(zé)返回Iterator迭代器。

public interface Iterable<T> {
    Iterator<T> iterator();
}

Iterator迭代器包含集合迭代時(shí)兩個(gè)最常用的方法:hasNextnexthasNext用于查詢(xún)集合是否存在下一項(xiàng),next方法用于獲取下一項(xiàng)。

public interface Iterator<E> {
    boolean hasNext();
    E next();
}

List接口繼承自Collection接口,相比于Collection接口已有的增刪改查的方法,List主要增加了index屬性和ListIterator接口。因此,除Collection接口方法,List接口的主要方法如下:

public interface List<E> extends Collection<E> {
    public E get(int location);
    public int indexOf(Object object);
    public int lastIndexOf(Object object);
    public ListIterator<E> listIterator();
    // ……
}

ListIterator接口繼承Iterator接口,因此,在正向遍歷方法hasNextnext的基礎(chǔ)上,ListIterator接口增加了實(shí)現(xiàn)逆序遍歷的方法hasPreviousprevious,使其具有雙向遍歷的特性。如下所示:

public interface ListIterator<E> extends Iterator<E> {
    public boolean hasPrevious();
    public E previous();
    public int previousIndex();
    // ……
}

下面舉個(gè)栗子說(shuō)明采用ListIterator進(jìn)行雙向遍歷。

List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");

ListIterator<String> listIterator = list.listIterator();
while(listIterator.hasNext()) {
    System.out.print(listIterator.next());
}
while (listIterator.hasPrevious()) {
    System.out.print(listIterator.previous());
}

ArrayList通過(guò)內(nèi)部類(lèi)Itr實(shí)現(xiàn)了ListIterator接口,Itr包含指示迭代器當(dāng)前位置的域cursornext()方法會(huì)把cursor向后推動(dòng),相反,previous()方法則把cursor向前推動(dòng),所以上述代碼能對(duì)該List的元素進(jìn)行雙向遍歷。

另外,在List上使用for-each語(yǔ)法遍歷集合時(shí),編譯器判斷List實(shí)現(xiàn)了Iterable接口,會(huì)調(diào)用iterator的方法來(lái)代替for循環(huán)。

// 程序版本
private void traversalA() {
    for (String s : list) {
        Log.d("TraversalA Test:", s);
    }
}
// 編譯器版本
private void traversalB() {
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String s = iterator.next();
        Log.d("TraversalB:", s);
    }
}

二、ArrayList / LinkedList實(shí)現(xiàn)原理

Java程序員都知道ArrayList 基于數(shù)組、LinkedList基于鏈表實(shí)現(xiàn),因此,這里不再對(duì)基本原理進(jìn)行贅述,下面主要從數(shù)據(jù)結(jié)構(gòu)、添加/刪除方法和迭代器三個(gè)方面分別說(shuō)明ArrayList和LinkedList實(shí)現(xiàn)原理:

對(duì)比內(nèi)容 ArrayList LinkedList
數(shù)據(jù)結(jié)構(gòu) 數(shù)組 雙向鏈表
添加/刪除方法 System.arraycopy復(fù)制 改變前后元素的指向
迭代器 Iterator和ListIterator ListIterator

2.1 ArrayList實(shí)現(xiàn)原理

ArrayList是可改變大小的、基于索引的數(shù)組,使用索引讀取數(shù)組中的元素的時(shí)間復(fù)雜度是O(1),但通過(guò)索引插入和刪除元素卻需要重排該索引后所有的元素,因此消耗較大。但相比于LinkedList,其內(nèi)存占用是連續(xù)的,空間利用效率更高。

2.1.1 擴(kuò)容

擴(kuò)容是ArrayList能夠改變?cè)卮鎯?chǔ)數(shù)組大小的保證。在JDK1.8中,ArrayList存放元素的數(shù)組的默認(rèn)容量是10,當(dāng)集合中元素?cái)?shù)量超過(guò)它時(shí),就需要擴(kuò)容。另外,ArrayList最大的存儲(chǔ)長(zhǎng)度為Integer.MAX_VALUE - 8(虛擬機(jī)可能會(huì)在數(shù)組中添加一些頭信息,這樣避免內(nèi)存溢出)。

private static final int DEFAULT_CAPACITY = 10;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

擴(kuò)容方法主要通過(guò)三步實(shí)現(xiàn):1)保存舊數(shù)組;2)擴(kuò)展新數(shù)組;3)把舊數(shù)據(jù)拷貝回新數(shù)組。

// 擴(kuò)容方法
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
// Arrays拷貝
public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}
// 調(diào)用System.arraycopy實(shí)現(xiàn)
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

由于oldCapacity >> 1等于oldCapacity / 2,所以擴(kuò)容后的數(shù)組大小為舊數(shù)組大小的1.5倍。另外,Arrays中的靜態(tài)方法是通過(guò)調(diào)用Native方法System.arraycopy來(lái)實(shí)現(xiàn)的。

2.1.2 add / remove方法

當(dāng)在ArrayList末尾添加/刪除元素時(shí),由于對(duì)其他元素沒(méi)有影響,所以時(shí)間負(fù)責(zé)度仍為O(1)。這里忽略這種情況,以通過(guò)索引插入/刪除數(shù)據(jù)為例說(shuō)明add / remove方法的實(shí)現(xiàn):

public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

public E remove(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

public static native void arraycopy(Object src,  int  srcPos, 
                            Object dest, int destPos, int length);

添加元素時(shí),首先確保數(shù)組容量足夠存放size+1個(gè)元素,然后將index后的size-index個(gè)元素依次后移一位,在index處保存新加入的元素element,同時(shí)增加元素總量;與添加元素相反,刪除元素時(shí)將index后的size-(index+1)個(gè)元素依次前移動(dòng)一位,同時(shí)減小元素總量。可見(jiàn),添加/刪除元素均通過(guò)調(diào)用System.arraycopy方法來(lái)實(shí)現(xiàn)數(shù)據(jù)的移動(dòng),效率較高。但另一方面,從上述實(shí)現(xiàn)可以看出,ArrayList并非線(xiàn)程安全,在并發(fā)環(huán)境下需要使用線(xiàn)程安全的容器類(lèi)。

2.1.3 Iterator和ListIterator

如前所述,ArrayList實(shí)現(xiàn)了List接口,其包含兩種迭代器:IteratorListIteratorListIterator相比于Iterator能實(shí)現(xiàn)前向遍歷。在ArrayList中,通過(guò)內(nèi)部類(lèi)Itr實(shí)現(xiàn)了Iterator接口,內(nèi)部類(lèi)ListItr繼承自Itr并且實(shí)現(xiàn)了ListIterator,因此,ArrayListiterator()方法放回的是Itr對(duì)象,listIterator()方法反回ListIterator對(duì)象。

private class Itr implements Iterator<E> {
    int cursor;
    int lastRet = -1;
    int expectedModCount = modCount;
    // ……
}

private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;
    }
    
    // ……
}

public Iterator<E> iterator() {
    return new Itr();
}

public ListIterator<E> listIterator() {
    return new ListItr(0);
}

Itr的成員變量中,cursor表示下一個(gè)訪問(wèn)對(duì)象的索引,lastRet表示上一個(gè)訪問(wèn)對(duì)象的索引,expectedModCount表示對(duì)ArrayList修改次數(shù)的期望值,初始值為modCount,而modCount是ArrayList父類(lèi)AbstractList中定義的成員變量,初始值為0,在上述add()和remove()方法中,都會(huì)對(duì)modCount加1,增加修改次數(shù)。

在使用ArrayList的remove()方法進(jìn)行對(duì)象刪除時(shí),一種常見(jiàn)的運(yùn)行時(shí)異常是ConcurrentModificationException,雖名為并發(fā)修改異常,但實(shí)際上單線(xiàn)程環(huán)境中也可能報(bào)出,原因就是上述expectedModCount與modCount不相等的問(wèn)題。

一種常見(jiàn)的使用場(chǎng)景是通過(guò)for-each語(yǔ)法刪除元素:

public void removeElement(List<Integer> list) {
    for (Integer x : list) {
        if (x % 2 == 0) {
            list.remove(x); // 調(diào)用ArrayList的remove方法
        }
    }
}

上節(jié)提到,for-each是一種語(yǔ)法糖,編譯之后依然調(diào)用了迭代器實(shí)現(xiàn)。而迭代器的next()方法會(huì)首先調(diào)用checkForComodification()方法檢查expectedModCount與modCount,如果不等就拋出ConcurrentModificationException。

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

所以,當(dāng)上述代碼中調(diào)用ArrayList的remove刪除元素后,modCount自增,而迭代器中expectedModCount保持不變,就會(huì)拋出ConcurrentModificationException,但是,如果使用迭代器的remove()方法則不會(huì)拋出異常,為什么呢?

public void removeElement2(List<Integer> list) {
    Iterator<Integer> itr = list.iterator();
    while (itr.hasNext()) {
        if (itr.next() % 2 == 0) {
            itr.remove(); // 調(diào)用迭代器的remove方法
        }
    }
}
public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet); // 調(diào)用ArrayList的remove方法
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount; // 設(shè)置expectedModCount 為modCount
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

從代碼中可以看出,其實(shí)迭代的remove方法也是調(diào)用了ArrayList的remove方法實(shí)現(xiàn)元素刪除,只不過(guò)在刪除元素之后設(shè)置了expectedModCount為modCount,避免checkForComodification時(shí)拋出異常。

2.2 LinkedList實(shí)現(xiàn)原理

鏈表按照鏈接形式可分為:?jiǎn)捂湵怼㈦p鏈表和循環(huán)鏈表。單鏈表節(jié)點(diǎn)包含兩個(gè)域:信息域和指針域,信息域存放元素,指針域指向下一個(gè)節(jié)點(diǎn),因此只支持單向遍歷。其結(jié)構(gòu)如下圖所示:

單鏈表存儲(chǔ)結(jié)構(gòu)

相比于單鏈表,雙鏈表節(jié)點(diǎn)包含三個(gè)域:信息域、前向指針域和后向指針域,前向指針指向前一個(gè)節(jié)點(diǎn)地址,后向指針指向后一個(gè)節(jié)點(diǎn)地址,因此可以實(shí)現(xiàn)雙向的遍歷。其結(jié)構(gòu)如下圖所示:

雙鏈表存儲(chǔ)結(jié)構(gòu)

循環(huán)鏈表分為單循環(huán)鏈表和雙循環(huán)鏈表。即在普通單鏈表和雙鏈表的基礎(chǔ)上增加了鏈表頭節(jié)點(diǎn)和尾節(jié)點(diǎn)的相互指向。頭節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)是尾節(jié)點(diǎn),尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)是頭節(jié)點(diǎn)。其結(jié)構(gòu)如下圖所示:

循環(huán)鏈表存儲(chǔ)結(jié)構(gòu)

LinkedList基于雙鏈表實(shí)現(xiàn),插入和刪除元素的時(shí)間復(fù)雜度是O(1),支持這種實(shí)現(xiàn)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)是LinkedList中定義的靜態(tài)內(nèi)部類(lèi)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;
    }
}

Node有三個(gè)成員變量:item負(fù)責(zé)存儲(chǔ)元素,next和prev分別指向下一個(gè)節(jié)點(diǎn)和前一個(gè)節(jié)點(diǎn),因此可實(shí)現(xiàn)雙向的元素訪問(wèn),LinkedList的操作方法都是基于Node節(jié)點(diǎn)特性設(shè)計(jì)的。

2.2.1 插入/刪除元素

在實(shí)現(xiàn)上,由于Deque接口同時(shí)具有隊(duì)列(雙向)和棧的特性,LinkedList實(shí)現(xiàn)了Deque接口,使得LinkedList能同時(shí)支持鏈表、隊(duì)列(雙向)和棧的操作。其插入/刪除方法如下表所示:

方法 鏈表 隊(duì)列
添加 add(int index, E element) offer(E e) push(E e)
刪除 remove(int index) E poll() E pop()

三者的差別在于,offer在鏈表末尾插入元素,調(diào)用linkLast實(shí)現(xiàn);push在鏈表頭部插入元素,調(diào)用linkFirst實(shí)現(xiàn);而add在指定位置插入元素,根據(jù)位置判斷調(diào)用linkLast或linkBefore方法。這里重點(diǎn)關(guān)注linkLast、linkFirst和linkBefore的實(shí)現(xiàn)。

private void linkFirst(E e) {
    final Node<E> f = first;
    // 創(chuàng)建新節(jié)點(diǎn),其prev節(jié)點(diǎn)為null,元素值為e,next結(jié)點(diǎn)為之前的first節(jié)點(diǎn)
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    // 如果初始列表為空,則將尾結(jié)點(diǎn)設(shè)置為當(dāng)前新節(jié)點(diǎn)
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    // 增加鏈表數(shù)量及修改次數(shù)
    size++;
    modCount++;
}

void linkLast(E e) {
    final Node<E> l = last;
    // 創(chuàng)建新節(jié)點(diǎn),其prev結(jié)點(diǎn)為之前的尾節(jié)點(diǎn),元素值為e,next節(jié)點(diǎn)為null
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    // 如果初始列表為空,則將頭結(jié)點(diǎn)設(shè)置為當(dāng)前新節(jié)點(diǎn)
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    // 增加鏈表數(shù)量及修改次數(shù)
    size++;
    modCount++;
}

void linkBefore(E e, Node<E> succ) {
    // 創(chuàng)建succ的prev節(jié)點(diǎn)引用
    final Node<E> pred = succ.prev;
    // 創(chuàng)建新節(jié)點(diǎn),其prev節(jié)點(diǎn)為succ的prev節(jié)點(diǎn),元素值為e,next節(jié)點(diǎn)為succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    // 修改原succ節(jié)點(diǎn)的prev指向
    succ.prev = newNode;
    // 如果succ為頭節(jié)點(diǎn),則設(shè)置新節(jié)點(diǎn)為頭節(jié)點(diǎn)
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    // 增加鏈表數(shù)量及修改次數(shù)
    size++;
    modCount++;
}

以上代碼中的注釋對(duì)linkLast、linkFirst和linkBefore的實(shí)現(xiàn)進(jìn)行了詳細(xì)的說(shuō)明,其核心原理便是初始化新節(jié)點(diǎn),并重新鏈接與原鏈表中元素的關(guān)系。remove、poll和pop在刪除元素時(shí)調(diào)用了與插入操作相反的方法unlinkFirst、unlinkLast和unlink,由于實(shí)現(xiàn)原理類(lèi)似,這里不再贅述。

2.2.2 查找及迭代器

從上節(jié)分析可以看出,LinkedList的插入/刪除操作只需要改變節(jié)點(diǎn)元素的鏈接指向,因此效率較高。但其查找元素需要從頭節(jié)點(diǎn)或尾節(jié)點(diǎn)開(kāi)始對(duì)集合進(jìn)行遍歷,相比于ArrayList基于數(shù)組索引,效率較低。

Node<E> node(int 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;
    }
}

LinkedList通過(guò)比較index與size/2(size >> 1)判斷是從頭節(jié)點(diǎn)還是尾節(jié)點(diǎn)開(kāi)始遍歷,然后通過(guò)分別獲取該節(jié)點(diǎn)的next節(jié)點(diǎn)或prev節(jié)點(diǎn)來(lái)實(shí)現(xiàn)。另外,由于LinkedList本身就同時(shí)支持前向/后向移動(dòng),所以其iterator方法直接返回ListIterator實(shí)現(xiàn)。

public Iterator<E> iterator() {
    return listIterator();
}

三、Stack / Queue模型、實(shí)現(xiàn)及應(yīng)用

Stack和Queue在模型設(shè)計(jì)上具有相似性,其核心方法對(duì)比如下:

方法 Stack Queue
插入 push(E item) offer(E e)
刪除 E pop() poll()
查看 E peek() E peek()

兩者的核心區(qū)別在于Stack是先進(jìn)后出(FILO),數(shù)據(jù)操作在一端進(jìn)行;而Queue是先進(jìn)先出(FIFO),在一端存儲(chǔ),另一端取出(Deque繼承自Queue,支持雙向存儲(chǔ)/取出)。

從上節(jié)可知,LinkedList是Queue(Deque)模型最常見(jiàn)的一種實(shí)現(xiàn)。下面通過(guò)一個(gè)實(shí)例,說(shuō)明如何利用LinkedList的隊(duì)列特征來(lái)模擬單向循環(huán)鏈表。比如有一個(gè)任務(wù)集合,任務(wù)有是否完成兩種狀態(tài),初始狀態(tài)均為未完成,需要實(shí)現(xiàn)從第一個(gè)任務(wù)開(kāi)始的單向循環(huán)遍歷,如果當(dāng)前任務(wù)完成,則不再參與遍歷,直到所有任務(wù)完成。

private Task getNextUnCompleteTask(LinkedList<Task> taskList) {
    Task task = taskList.peek(); 
    if (task == null) {
        return null;
    }
    if (task.isComplete()) {
        taskList.poll();
    } else {
        taskList.offer(taskList.poll());
    }
    return taskList.peek();
}

上述代碼通過(guò)將未完成的任務(wù)重新添加至隊(duì)尾,從而在循環(huán)調(diào)用getNextUnCompleteTask方法時(shí),實(shí)現(xiàn)對(duì)未完成任務(wù)的循環(huán)遍歷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,327評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,554評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,337評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,883評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,114評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評(píng)論 1 332
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,555評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,737評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,973評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,615評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,343評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,699評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • 一.線(xiàn)性表 定義:零個(gè)或者多個(gè)元素的有限序列。也就是說(shuō)它得滿(mǎn)足以下幾個(gè)條件:??①該序列的數(shù)據(jù)元素是有限的。??②...
    Geeks_Liu閱讀 2,703評(píng)論 1 12
  • Collection接口 Collection接口是所有集合的祖先類(lèi)。他有兩個(gè)構(gòu)造方法,一個(gè)無(wú)參構(gòu)造,一個(gè)是帶Co...
    夜幕繁華閱讀 601評(píng)論 0 0
  • java筆記第一天 == 和 equals ==比較的比較的是兩個(gè)變量的值是否相等,對(duì)于引用型變量表示的是兩個(gè)變量...
    jmychou閱讀 1,507評(píng)論 0 3
  • 人為什么總是喜歡懷念小時(shí)候?因?yàn)槟菚r(shí)我們一無(wú)所有,整日無(wú)憂(yōu)。 長(zhǎng)大后,我們擁有的越來(lái)越多,想擁有的也越來(lái)越多。 每...
    Lancy日記閱讀 2,756評(píng)論 0 15
  • 琴中應(yīng)數(shù)誰(shuí)為最,獨(dú)憶京胡不解緣。 嚴(yán)父竹竿雕兩把,頑兒馬尾練七年。 推拉頓挫方知妙,急緩悠揚(yáng)竟品甜。 梁繞余音君老...
    艾思閱讀 996評(píng)論 21 23