大家讲道理2017-04-17 14:59:37
具體語言實作不同,這裡是一些語言無關的點
如果你在遍歷時往數組增加數據,會導致遍歷不完整(因為增加了新成員長度變了),或者死循環(因為總是有新的進去)
如果在遍歷時刪除數據,則會導致數組存取越界(因為長度縮短了,指針指向了一個已經標示為空的區域)
如 @Terry_139061 的答案,如果你只是在遍歷時修改這個節點本身的數據,一般來說是安全的(當然需要看具體場景)
PHP中文网2017-04-17 14:59:37
你的問題描述得不太準確,標籤是java
和lisp
,只寫過一點 racket 程式碼就不談論 lisp 了,以下預設修改為 list 的 add/remove 操作
首先, java 裡面有很多種 list :
java.util.ArrayList;
java.util.LinkedList;
java.util.Stack;
java.util.Vector;
java.util.concurrent.CopyOnWriteArrayList;
其中 CopyOnWriteArrayList
在遍歷的時候修改是不會出錯的,實作方法是讀寫分離,參考維基 Copy-on-write
其次, java 裡面有好幾種遍歷方式:
for
iterator
foreach
for
,在 for 迴圈中修改並沒有問題,除非你把要存取的物件刪除,陣列越界,或一直add產生無窮序列
iterator
,你用 iterator.remove() 是不會有問題的,因為 iterator.remove() 會設定
this.expectedModCount = ArrayList.this.modCount;//(1)
這樣之後遍歷執行 iterator.next() 就不會拋異常
public E next() {
this.checkForComodification();
...
}
final void checkForComodification() {
if(ArrayList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
foreach
,本質上是隱式的iterator (可以用javap -c 比較字節碼),由於沒有重新設定expectedModCount ,當你使用list.remove() 後遍歷執行iterator.next( ) 時就會報ConcurrentModificationException
最後,這裡面有一個特別的地方,就如果刪除的是list 裡倒數第二個值,這樣觸發hasNext() 的時候結果正好為false 退出循環不繼續執行next() 也就不會報錯
public boolean hasNext() {
return this.cursor != ArrayList.this.size;
}
PS:使用的JDK是java-8-openjdk-amd64
天蓬老师2017-04-17 14:59:37
可以修改呀,下面的程式會印3個aaaaa
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("!");
for (int i = 0; i < list.size(); i++) {
list.set(i, "aaaaa");
}
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
你是用什麼方法遍歷的,請貼出程式碼來,好幫你分析
高洛峰2017-04-17 14:59:37
從java的角度回答這個問題, Iterator模式是用來遍歷集合類別的標準存取方法。它可以把存取邏輯從不同類型的集合類別中抽象化出來,從而避免向客戶端揭露集合的內部結構。
例如,如果沒有使用Iterator,遍歷一個陣列的方法是使用索引:
for(int i=0; i<array.size(); i++) { ... get(i) ... }
客戶端都必須事先知道集合的內部結構,存取程式碼和集合本身是緊密耦合,無法將存取邏輯從集合類別和客戶端程式碼中分離出來,每一種集合對應一種遍歷方法,客戶端程式碼無法重複使用。
更恐怖的是,如果以後需要把ArrayList更換為LinkedList,則原來的客戶端程式碼必須全部重寫。要解決以上問題,Iterator模式總是用同一種邏輯來遍歷集合:
for(Iterator it = c.iterater(); it.hasNext(); ) { ... }
- 奧秘在於客戶端本身不維護遍歷集合的"指針",所有的內部狀態(如當前元素位置,是否有下一個元素)都由Iterator來維護,而這個Iterator由集合類通過工廠方法生成,因此,它知道如何遍歷整個集合。客戶端從不直接和集合類別打交道,它總是控制Iterator,向它發送"向前","向後","取當前元素"的命令,就可以間接遍歷整個集合。
當使用Iterator對集合元素進行迭代的時候,collection並不是把集合元素本身傳給了迭代變量,而是把集合元素的值傳給了迭代變量。所以修改迭代變數的值對集合元素本身的值沒有任何改變。迭代器Iterator不保存對象,它依附於Collection對象,僅用於遍歷集合。迭代器Iterator採用的是快速-失敗(fail-fast)機制,一旦在迭代的過程中偵測到該集合已經被修改,程式立即引發java.util.ConcurrentModificationException,而不是顯示修改後的結果。這樣可以避免共享資源而引發的潛在問題。
public class TestCollections {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list.add("hello_" + i);
}
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
if (str.equals("hello_5")) {
// iterator.remove();
list.remove(str);
}
str = "hehe";
}
System.out.println(list);
}
}
執行{ str = "hello" } 語句,對外部的元素沒有任何影響,執行iterator.remove()會刪除當前的迭代對象.執行list.remove(str) ,會報java.util.ConcurrentModificationException ,因為他偵測到集合的元素被修改了.