1. 程式人生 > 其它 >Java基礎-集合類-迭代器

Java基礎-集合類-迭代器

技術標籤:# 知識樹/Java基礎 /集合類

Java工程師知識樹 / Java基礎


任何容器類,都必須有某種方式可以將東西放進去,然後由某種方式將東西取出來。畢竟,存放事物是容器最基本的工作。對於ArrayList,add()是插入物件的方法,而get()是取出元素的方式之一。ArrayList很靈活,可以隨時選取任意的元素,或使用不同的下標一次選取多個元素。
如果從更高層的角度思考,會發現這裡有一個缺點:要使用容器,必須知道其中元素的確切型別。初看起來這沒有什麼不好的,但是考慮如下情況:如果原本是ArrayList ,但是後來考慮到容器的特點,你想換用Set ,應該怎麼做?或者你打算寫通用的程式碼,它們只是使用容器,不知道或者說不關心容器的型別,那麼如何才能不重寫程式碼就可以應用於不同型別的容器?

所以迭代器(Iterator)的概念,也是出於一種設計模式就是為達成此目的而形成的。所以Collection不提供get()方法。如果要遍歷Collection中的元素,就必須用Iterator。
迭代器(Iterator)本身就是一個物件,它的工作就是遍歷並選擇集合序列中的物件,而客戶端的程式設計師不必知道或關心該序列底層的結構。此外,迭代器通常被稱為“輕量級”物件,建立它的代價小。但是,它也有一些限制,例如,某些迭代器只能單向移動。

Collection 介面的 iterator() 方法返回一個 Iterator。Iterator 和Enumeration 介面類似。使用 Iterator 介面方法,您可以從頭至尾遍歷集合,並安全的從底層 Collection 中除去元素。

Enumeration

Enumeration 介面早在 JDK 1.0 時就推出了,當時比較早的容器比如 Hashtable, Vector 都使用它作為遍歷工具。
Enumeration是一個介面,它的原始碼如下:

public interface Enumeration<E> {

/**
 * 是否還有元素
 * @return {@code true} if there are more elements, {@code false} otherwise.
 * @see #nextElement
    */
    public boolean hasMoreElements();

/**

 * 返回下一個元素
 * @return the next element..
 * @throws NoSuchElementException
 * if there are no more elements.
 * @see #hasMoreElements
    */
    public E nextElement();
    }

Iterator 是一個集合上的迭代器,用來替代 Enumeration 進行遍歷、迭代。
Iterator也是一個介面,它的原始碼如下:

package java.util;
public interface Iterator<E> {
    boolean hasNext();//是否還有元素
    E next();//返回下一個元素
    void remove();//移除元素
}

之間的區別。

  1. 函式介面不同

    Enumeration只有2個函式介面。通過Enumeration,我們只能讀取集合的資料,而不能對資料進行修改。
    Iterator只有3個函式介面。Iterator除了能讀取集合的資料之外,也能資料進行刪除操作。

  2. 方法名稱得到了改進

迭代器的簡單使用:

public class IteratorDemo {
	public static void main(String[] args) {
		Collection collection = new ArrayList();
		collection.add("s1");
		collection.add("s2");
		collection.add("s3");
		Iterator iterator = collection.iterator();//得到一個迭代器
		while (iterator.hasNext()) {//遍歷
			Object element = iterator.next();
			System.out.println("iterator = " + element);
		}
		if(collection.isEmpty())
			System.out.println("collection is Empty!");
		else
			System.out.println("collection is not Empty! size="+collection.size());
		Iterator iterator2 = collection.iterator();
		while (iterator2.hasNext()) {//移除元素
			Object element = iterator2.next();
			System.out.println("remove: "+element);
			iterator2.remove();
		}		
		Iterator iterator3 = collection.iterator();
		if (!iterator3.hasNext()) {//察看是否還有元素
			System.out.println("還有元素");
		}	
		if(collection.isEmpty())
			System.out.println("collection is Empty!");
		//使用collection.isEmpty()方法來判斷
	}
}
程式的執行結果為:
iterator = s1
iterator = s2
iterator = s3
collection is not Empty! size=3
remove: s1
remove: s2
remove: s3
還有元素
collection is Empty!

可以看到,Java的Collection的Iterator 能夠用來:

  1. 使用方法 iterator() 要求容器返回一個Iterator .第一次呼叫Iterator 的next() 方法時,它返回集合序列的第一個元素。
  2. 使用next() 獲得集合序列的中的下一個元素。
  3. 使用hasNext()檢查序列中是否元素。
  4. 使用remove()將迭代器新返回的元素刪除。

需要注意的是:方法刪除由next方法返回的最後一個元素,在每次呼叫next時,remove方法只能被呼叫一次 。

擴充套件:

ListIterator

image-20200520143236971

ListIterator

ListIterator 有以下功能:

  • 允許我們向前、向後兩個方向遍歷 List;
  • 在遍歷時修改 List 的元素;
  • 遍歷時獲取迭代器當前遊標所在位置。

注意,迭代器 沒有當前所在元素一說,它只有一個遊標( cursor )的概念,這個遊標總是在元素之間,比如這樣:

image.png

初始時它在第 0 個元素之前,呼叫 next() 遊標後移一位:

image.png

呼叫 previous() 遊標就會回到之前位置。

當向後遍歷完元素,遊標就會在元素 N 的後面:

image.png

也就是說長度為 N 的集合會有 N+1 個遊標的位置。

ListIterator 繼承自 Iterator 介面,在 Iterator 的基礎上增加了 6 個方法:
這裡寫圖片描述

image.png

介紹一下新來的幾個方法:

void hasPrevious() 判斷遊標前面是否有元素;

Object previous() 返回遊標前面的元素,同時遊標前移一位。遊標前沒有元素就報 java.util.NoSuchElementException 的錯,所以使用前最好判斷一下;

int nextIndex() 返回遊標後邊元素的索引位置,初始為 0 ;遍歷 N 個元素結束時為 N;

int previousIndex() 返回遊標前面元素的位置,初始時為 -1,同時報 java.util.NoSuchElementException 錯;

void add(E) 在遊標 前面 插入一個元素 注意,是前面

void set(E) 更新迭代器最後一次操作的元素為 E,也就是更新最後一次呼叫 next() 或者 previous() 返回的元素。注意,當沒有迭代,也就是沒有呼叫 next() 或者 previous() 直接呼叫 set 時會報 java.lang.IllegalStateException 錯;

void remove() 刪除迭代器最後一次操作的元素,注意事項和 set 一樣。

ListIterator 有兩種獲取方式

  • List.listIterator()
  • List.listIterator(int location)

Iterator 和 Enumeration

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Random;

/*
 * 測試分別通過 Iterator 和 Enumeration 去遍歷Hashtable
 * @author skywang
 */
public class IteratorEnumeration {

    public static void main(String[] args) {
        int val;
        Random r = new Random();
        Hashtable table = new Hashtable();
        for (int i=0; i<100000; i++) {
            // 隨機獲取一個[0,100)之間的數字
            val = r.nextInt(100);
            table.put(String.valueOf(i), val);
        }

        // 通過Iterator遍歷Hashtable
        iterateHashtable(table) ;

        // 通過Enumeration遍歷Hashtable
        enumHashtable(table);
    }
    
    /*
     * 通過Iterator遍歷Hashtable
     */
    private static void iterateHashtable(Hashtable table) {
        long startTime = System.currentTimeMillis();

        Iterator iter = table.entrySet().iterator();
        while(iter.hasNext()) {
            //System.out.println("iter:"+iter.next());
            iter.next();
        }

        long endTime = System.currentTimeMillis();
        countTime(startTime, endTime);
    }
    
    /*
     * 通過Enumeration遍歷Hashtable
     */
    private static void enumHashtable(Hashtable table) {
        long startTime = System.currentTimeMillis();

        Enumeration enu = table.elements();
        while(enu.hasMoreElements()) {
            //System.out.println("enu:"+enu.nextElement());
            enu.nextElement();
        }

        long endTime = System.currentTimeMillis();
        countTime(startTime, endTime);
    }

    private static void countTime(long start, long end) {
        System.out.println("time: "+(end-start)+"ms");
    }
}
//執行結果如下:
time: 9ms
time: 5ms

從中,我們可以看出。Enumeration 比 Iterator 的遍歷速度更快。為什麼呢?
這是因為,Hashtable中Iterator是通過Enumeration去實現的,而且Iterator添加了對fail-fast機制的支援;所以,執行的操作自然要多一些。

SimpleListIterator

private class SimpleListIterator implements Iterator<E> {
    //遊標的位置,初始為 -1
    int pos = -1;
    //用來判斷是否 fail-fast 的變數
    int expectedModCount;
    //記錄上次迭代的位置
    int lastPosition = -1;
 
    SimpleListIterator() {
        expectedModCount = modCount;
    }
 
    //當遊標沒有跑到最後一個元素後面時 hasNext 返回 true
    public boolean hasNext() {
        return pos + 1 < size();
    }
 
    //獲取下一個元素
    public E next() {
        if (expectedModCount == modCount) {
            try {
                //獲取遊標後面的元素,具體子類有具體實現
                E result = get(pos + 1);
                //更新
                lastPosition = ++pos;
                return result;
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
        }
        //當迭代時修改元素,就會報這個錯,上篇文章介紹過解決辦法~
        throw new ConcurrentModificationException();
    }
 
    //刪除上次迭代操作的元素
    public void remove() {
        //還沒進行迭代操作就會報這個錯
        if (this.lastPosition == -1) {
            throw new IllegalStateException();
        }
 
        if (expectedModCount != modCount) {
            throw new ConcurrentModificationException();
        }
 
        try {
            //呼叫子類實現的刪除操作
            AbstractList.this.remove(lastPosition);
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
 
        expectedModCount = modCount;
        if (pos == lastPosition) {
            pos--;
        }
        //每次刪除後都會還原為 -1,也就是說我們迭代一次後只能 remove 一次,再 remove 就會報錯
        lastPosition = -1;
    }
}

FullListIterator

private final class FullListIterator extends SimpleListIterator implements ListIterator<E> {
    //根據 start 指定遊標位置
    FullListIterator(int start) {
        if (start >= 0 && start <= size()) {
            pos = start - 1;
        } else {
            throw new IndexOutOfBoundsException();
        }
    }
 
    //在遊標前面新增元素
    public void add(E object) {
        if (expectedModCount == modCount) {
            try {
                //呼叫子類的新增操作,ArrayList, LinkedList,Vector 的新增操作實現有所不同
                AbstractList.this.add(pos + 1, object);
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
            //遊標後移一位
            pos++;
            //!注意! 新增後 上次迭代位置又變回 -1 了,說明 add 後呼叫 remove, set 會有問題!
            lastPosition = -1;
            if (modCount != expectedModCount) {
                expectedModCount = modCount;
            }
        } else {
            throw new ConcurrentModificationException();
        }
    }
 
    //當遊標不在初始位置(-1)時返回true
    public boolean hasPrevious() {
        return pos >= 0;
    }
 
    //遊標後面的元素索引,就是遊標 +1
    public int nextIndex() {
        return pos + 1;
    }
 
    //遊標前面一個元素
    public E previous() {
        if (expectedModCount == modCount) {
            try {
                E result = get(pos);
                lastPosition = pos;
                pos--;
                return result;
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
        }
        throw new ConcurrentModificationException();
    }
 
    //遊標前面元素的索引,就是遊標的位置,有點暈的看開頭那幾張圖
    public int previousIndex() {
        return pos;
    }
 
    //更新之前迭代的元素為 object
    public void set(E object) {
        if (expectedModCount == modCount) {
            try {
                //呼叫子類的set
                AbstractList.this.set(lastPosition, object);
            } catch (IndexOutOfBoundsException e) {
                throw new IllegalStateException();
            }
        } else {
            throw new ConcurrentModificationException();
        }
    }
}