※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 都不會有這種情形
沒有留言:
張貼留言