※List
List<Integer> list = new LinkedList<>(); list.add(4); list.add(1); list.add(3); list.add(2); // int x = list.size(); for (int i = 0; i < list.size(); ++i) { /* if (list.get(i) == 3 || list.get(i) == 1) {
list.remove(i); i--; } */ list.remove(i); } /* Iterator<Integer> it = list.iterator(); while (it.hasNext()) { Integer i = it.next(); if (i == 3) it.remove(); } */ /* for (Iterator<Integer> it = list.iterator(); it.hasNext();) { Integer i = it.next(); if (i == 3) it.remove(); } */ System.out.println("size=" + list.size()); System.out.println("list=" + list);
※此例並不會全移除,會發現還有兩個元素,因為移除後, index 會自動往上移
元素:index--> 4:0 1:1 3:2 2:3
迴圈跑一圈後會變成 1:0 3:1 2:2
迴圈跑兩圈後會變成 1:0 2:1,所以會剩下內容為 1 和 2
可以通過移除後,將迴圈裡的變數 i 減 1 達成,但用 Iterator 會比較漂亮,再或者用 for 加iterator 也可以
※remove 完後就 break 是可行的,但不適合有重覆的元素
※從元素最後開始遍例到最前面,也就是用 i-- 的方式可以解決 index 自動往上移的問題
※使用 forEach 然後 remove 是不行的
※和 Java8 List 一樣,使用 forEach 然後 remove 會死得很難看
※刪除倒數第二個時(33)時,不會報錯,其他都會報「java.util.ConcurrentModificationException」的錯
※Java8 List
List<String> list = Stream.of("a", "b", "c", "c", "e").collect(Collectors.toList()); list.removeIf(data -> "c".equals(data) ? true : false); // data -> "c".equals(data)
// 或 new String("c")::equals
/* list.forEach(i -> { if ("c".equals(i)) { list.remove(i); } }); */ System.out.println(list);
※使用 forEach 然後 remove 是不行的
※Map
Map<String, Integer> map = new TreeMap<>(); map.put("four", 4); map.put("one", 1); map.put("three", 3); map.put("two", 2); Iterator<Entry<String, Integer>> it = map.entrySet().iterator(); while(it.hasNext()){ Entry<String, Integer> i = it.next(); if(i.getValue() == 3){ it.remove(); } } /* for (Iterator<Entry<String, Integer>> it = map.entrySet().iterator(); it.hasNext();) { Entry<?, Integer> i = it.next(); if (i.getValue() == 3) { it.remove(); } } */ System.out.println("size=" + map.size()); System.out.println("map=" + map);※
※Java8 Map
List<String> list = Stream.of("a", "b", "c", "d", "e").collect(Collectors.toList()); Map<String, Object> map = list.stream().collect(Collectors.toMap(Function.identity(), v -> "c")); map.values().removeIf(data -> "c".equals(data) ? true : false); // map.keySet()、map.entrySet() 也都有 removeIf /* map.forEach((k, v) -> { map.remove(k); }); */ System.out.println(map);
※和 Java8 List 一樣,使用 forEach 然後 remove 會死得很難看
※Properties
Properties prop = System.getProperties(); //prop.list(System.out); System.out.println(prop.size()); //錯誤寫法 // for (Entry<Object, Object> entrySet : prop.entrySet()) { // prop.remove(entrySet.getKey()); // } // System.out.println(prop.size()); // 正確寫法1 // Iterator<Entry<Object, Object>> it1 = prop.entrySet().iterator(); // while(it1.hasNext()){ // Entry<Object, Object> entry = it1.next(); // //if("".equals(entry.getKey("xxx")) ) // it1.remove(); // } // System.out.println(prop.size()); // 正確寫法2 for(Iterator<Entry<Object, Object>> it2 = prop.entrySet().iterator(); it2.hasNext();) { Entry<Object, Object> entry = it2.next(); //if("".equals(entry.getKey("xxx")) ) it2.remove(); } System.out.println(prop.size());
※原碼分析
List<String> list = new ArrayList<>(); list.add("11"); list.add("22"); list.add("33"); list.add("44"); Iterator<String> it = list.iterator(); while (it.hasNext()) { String data = it.next(); System.out.println(data); if ("44".equals(data)) { list.remove(data); } }
※刪除倒數第二個時(33)時,不會報錯,其他都會報「java.util.ConcurrentModificationException」的錯
這個 Exception 表示執行緒在刪除時,不能更改裡面的結構,不是修改值,如增加刪除都會改到結構,但單執行緒不知道為什麼也是這樣的限制,這樣的機制叫 fail-fast
Iterator 的 remove 最後也會呼叫 java.util.ArrayList 的 remove,但因為也個地方用的是 native,看不到原碼怎麼寫的,使得增加和原來的 size 一樣,所以不會報錯
※如果 33 有很多個且第一個是倒數第二個會和前面一樣,只會刪倒數第二個
但若第一個不是倒數第二個也是拋上面的例外,使用 for 也是一樣的情形
※使用 add、remove 時,modCount 變數都會加 1;size 變數使用 add 方法時會加 1;remove 方法時會減1
※list.iterator() 時,會使用裡面的方法
※刪除 11、22、44 時:執行到 next() 後,會呼叫 checkForComodification(),上個迴圈執行 remove 後,modCount 變數會加1;而 expectedModCount 都是原本的長度 4
5 != 4,所以報錯
※刪除倒數第二個 (33) 時:這時要注意 hasNext()
cursor size value
0 1 11
1 2 22
2 3 33
3 4 44
刪除 33 後,執行下一次迴圈來到 hasNext(),size 會被減 1,所以 3 != 3 為 false,結束迴圈,造成不出錯的假像
※hasNext(),刪除11、22、33、44 時的情形,cursor != size 表如下:
11:0 != 4 --> 1 != 3
22:0 != 4 --> 1 != 4 --> 2 != 3
33:0 != 4 --> 1 != 4 --> 2 != 4 --> 3 != 3
44:0 != 4 --> 1 != 4 --> 2 != 4 --> 3 != 4 --> 4 != 3
除了 倒數第二筆 (33) 以外,其他都會繼續執行,呼叫 next(),再呼叫 checkForComodification(),就出錯了
※Itr 的 remove() 因為有 expectedModCount = modCount,所以不會有問題
※以上在多線程的情況還是會錯,可以使用 new CopyOnWriteArrayList<>() 解決
它會複製一份到別的地方,然後在將指向移到這個地方,所以不會有問題
複製前和指向前,如果有人要讀這份資料,取到的是舊值;指向後,取得的是新值
因為是複製一份,所以資料量大時會變慢
※更改了原來的 String 陣列居然會影響 List,使用 Arrays.asList 才會,用 List.of、Stream.of、System.arraycopy 都不會有這種情形
※如果 33 有很多個且第一個是倒數第二個會和前面一樣,只會刪倒數第二個
但若第一個不是倒數第二個也是拋上面的例外,使用 for 也是一樣的情形
※ArrayList 重要原代碼
※從 java8 複製的
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = 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 boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
※使用 add、remove 時,modCount 變數都會加 1;size 變數使用 add 方法時會加 1;remove 方法時會減1
※Itr 重要原代碼
※java.util.ArrayList 有四個內部類別,Itr、ListItr、SubList、ArrayListSpliterator※list.iterator() 時,會使用裡面的方法
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
※刪除 11、22、44 時:執行到 next() 後,會呼叫 checkForComodification(),上個迴圈執行 remove 後,modCount 變數會加1;而 expectedModCount 都是原本的長度 4
5 != 4,所以報錯
※刪除倒數第二個 (33) 時:這時要注意 hasNext()
cursor size value
0 1 11
1 2 22
2 3 33
3 4 44
刪除 33 後,執行下一次迴圈來到 hasNext(),size 會被減 1,所以 3 != 3 為 false,結束迴圈,造成不出錯的假像
※hasNext(),刪除11、22、33、44 時的情形,cursor != size 表如下:
11:0 != 4 --> 1 != 3
22:0 != 4 --> 1 != 4 --> 2 != 3
33:0 != 4 --> 1 != 4 --> 2 != 4 --> 3 != 3
44:0 != 4 --> 1 != 4 --> 2 != 4 --> 3 != 4 --> 4 != 3
除了 倒數第二筆 (33) 以外,其他都會繼續執行,呼叫 next(),再呼叫 checkForComodification(),就出錯了
※Itr 的 remove() 因為有 expectedModCount = modCount,所以不會有問題
※以上在多線程的情況還是會錯,可以使用 new CopyOnWriteArrayList<>() 解決
它會複製一份到別的地方,然後在將指向移到這個地方,所以不會有問題
複製前和指向前,如果有人要讀這份資料,取到的是舊值;指向後,取得的是新值
因為是複製一份,所以資料量大時會變慢
※陣列轉 List
String[] str = new String[] { "aa", "bb" }; List<String> list = Arrays.asList(str); str[0] = "cc"; list.forEach(System.out::println);
※更改了原來的 String 陣列居然會影響 List,使用 Arrays.asList 才會,用 List.of、Stream.of、System.arraycopy 都不會有這種情形
沒有留言:
張貼留言