Java基礎-集合類-迭代器
技術標籤:# 知識樹/Java基礎 /集合類
任何容器類,都必須有某種方式可以將東西放進去,然後由某種方式將東西取出來。畢竟,存放事物是容器最基本的工作。對於ArrayList,add()是插入物件的方法,而get()是取出元素的方式之一。ArrayList很靈活,可以隨時選取任意的元素,或使用不同的下標一次選取多個元素。
如果從更高層的角度思考,會發現這裡有一個缺點:要使用容器,必須知道其中元素的確切型別。初看起來這沒有什麼不好的,但是考慮如下情況:如果原本是ArrayList ,但是後來考慮到容器的特點,你想換用Set ,應該怎麼做?或者你打算寫通用的程式碼,它們只是使用容器,不知道或者說不關心容器的型別,那麼如何才能不重寫程式碼就可以應用於不同型別的容器?
迭代器(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();//移除元素
}
之間的區別。
-
函式介面不同
Enumeration只有2個函式介面。通過Enumeration,我們只能讀取集合的資料,而不能對資料進行修改。
Iterator只有3個函式介面。Iterator除了能讀取集合的資料之外,也能資料進行刪除操作。 -
方法名稱得到了改進
迭代器的簡單使用:
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 能夠用來:
- 使用方法 iterator() 要求容器返回一個Iterator .第一次呼叫Iterator 的next() 方法時,它返回集合序列的第一個元素。
- 使用next() 獲得集合序列的中的下一個元素。
- 使用hasNext()檢查序列中是否元素。
- 使用remove()將迭代器新返回的元素刪除。
需要注意的是:方法刪除由next方法返回的最後一個元素,在每次呼叫next時,remove方法只能被呼叫一次 。
擴充套件:
ListIterator
ListIterator
ListIterator 有以下功能:
- 允許我們向前、向後兩個方向遍歷 List;
- 在遍歷時修改 List 的元素;
- 遍歷時獲取迭代器當前遊標所在位置。
注意,迭代器 沒有當前所在元素一說,它只有一個遊標( cursor )的概念,這個遊標總是在元素之間,比如這樣:
初始時它在第 0 個元素之前,呼叫 next() 遊標後移一位:
呼叫 previous() 遊標就會回到之前位置。
當向後遍歷完元素,遊標就會在元素 N 的後面:
也就是說長度為 N 的集合會有 N+1 個遊標的位置。
ListIterator 繼承自 Iterator 介面,在 Iterator 的基礎上增加了 6 個方法:
這裡寫圖片描述
介紹一下新來的幾個方法:
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();
}
}
}