2017年7月2日 星期日

集合的刪除

※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 自動往上移的問題


※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 也是一樣的情形

※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 都不會有這種情形

沒有留言:

張貼留言