1. 程式人生 > >Java集合系列(一)List集合

Java集合系列(一)List集合

List的幾種實現的區別與聯絡

List主要有ArrayList、LinkedList與Vector幾種實現。 

ArrayList底層資料結構是陣列, 增刪慢、查詢快; 執行緒不安全, 效率高; 不可以設定擴充套件容量, 預設增長1.5倍; 無參構造器初始化時, 初始容量為0。 

LInkedList底層資料結構是連結串列, 增刪快、查詢慢; 執行緒不安全, 效率高。 

Vector底層資料結構是陣列, 增刪慢、查詢快; 執行緒安全, 效率低; 可以設定擴充套件容量, 預設增長2倍; 無參構造器初始化時, 初始容量為10。

List轉換及刪除元素

陣列轉List 集合

對於一個數組, 可以通過 Arrays.asList(T... a)

 方法轉換成List集合, 需要注意的是, 此方法得到的ArrayList物件是基於Arrays內部類 java.util.Arrays$ArrayList 來建立的, 而非 java.util.ArrayList 。這就涉及到了一個問題, 通過 asList(T... a) 轉換得到的List集合是不允許進行增刪操作的, 我們先看如下程式碼: 

@Test
public void convertList() {
    List<String> list = Arrays.asList("張小凡", "陸雪琪", "碧瑤");
    //list.add("qingshanli");
list.remove(0); }

執行時會報異常 java.lang.UnsupportedOperationException 

我們先來看看 java.util.Arrays$ArrayList 的方法層次結構: 

可以看出,  java.util.Arrays$ArrayList 並沒有覆寫父類AbstractList的 add() 和 remove() 方法, 根據Java的三大特性之多型性可知, 上面程式碼中的增刪操作實際呼叫的是父類AbstractList的方法, 我們再來看看AbstractList的部分原始碼: 

public E set(int
index, E element) { throw new UnsupportedOperationException(); } public void add(int index, E element) { throw new UnsupportedOperationException(); } public E remove(int index) { throw new UnsupportedOperationException(); } 

到此, 可以得知其實通過 Arrays.asList(T... a) 轉換得到的List集合是一個固定長度的集合, 所以不能進行增刪操作。

如何在遍歷時刪除ArrayList中元素

方式一: 普通迴圈

public void test(List<Integer> list) {
    for (int i = 0; i < list.size(); i++) {
        if (list.get(i) % 2 == 0) {
            list.remove(list.get(i));
            i--; // 索引改變!
        }
    }
}

這種方式在刪除操作時, 會改變集合的索引和size大小, 遍歷時可能會產生角標越界異常, 因此不是特別推薦。

方式二: 高階for迴圈

public static void main(String[] args) {
    List<Integer> list = new ArrayList<Integer>();
    for (int i = 0; i < 5; i++){
        list.add(i);
    }
    for (Integer num : list) {
        System.out.print("value="+num);
        if (num % 2 == 0) {
            list.remove(num);
            System.out.println(" delete");
        }else{
            System.out.println(" not delete");
        }
    }
} 

執行結果如下, 第一個元素刪除正常, 後面繼續遍歷刪除則拋異常 java.util.ConcurrentModificationException  

如下, 反編譯上述程式碼, 可以看出高階for迴圈底層其實就是使用iterator迭代器來進行遍歷

public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<Integer>();
    for (int i = 0; i < 5; ++i) {
        list.add(Integer.valueOf((int)i));
    }
    Iterator i = list.iterator();
    while (i.hasNext()) {
        Integer num = (Integer)i.next();
        System.out.print((String)new StringBuilder().append((String)"value=").append((Object)num).toString());
        if (num.intValue() % 2 == 0) {
            list.remove((Object)num);
            System.out.println((String)" delete");
            continue;
        }
        System.out.println((String)" not delete");
    }
}

再來看看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;

    Itr() {}

    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();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
View Code

可以看出, 迭代器內部的每次遍歷操作 next() 、 remove() 方法都會記錄List集合內部的modCount當做預期值expectedModCount, 然後在每次迴圈中判斷預期值expectedModCount與List的成員變數modCount是否相等。但是因為上面 list.remove() 呼叫的是List集合的 remove() 方法, 繼續跟蹤原始碼發現每次呼叫該方法就會 modCount++; , 但是迭代器內記錄的預期值expectedModCount並沒有跟著改變, 所以當第二次刪除操作時就會發生異常。

方式三: iterator迭代器遍歷

public void test(List<Integer> list) {
    Iterator<Integer> it = list.iterator();
    while (it.hasNext()) {
        int num = it.next();
        System.out.print("value="+num);
        if (num % 2 == 0) {
            it.remove();
            System.out.println(" delete");
        }else{
            System.out.println(" not delete");
        }
    }
}

原理與方式二基本類似, 但是這裡使用的是迭代器iterator的 remove() 方法, 我們再回顧之前迭代器實現類的原始碼, 發現 remove() 方法中有 expectedModCount = modCount; 這個操作, 即呼叫迭代器的 remove() 方法時會同步List集合的modCount到迭代器的預期值expectedModCount當中, 所以迭代器方式刪除才不會產生。

什麼是快速失敗, 安全失敗機制

快速失敗(fail-fast)

fail-fast機制是java集合中的一種錯誤機制。當多個執行緒對同一個集合的內容進行操作時,就可能會產生fail-fast事件。

使用迭代器遍歷一個集合物件時,如果遍歷過程中對集合進行了增刪改, 則會丟擲 ConcurrentModificationException 。 

for (Integer id : list) {
    if (id == 2) {
        list.remove(id);
    }
}

在前面我們已經介紹過, 迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個modCount變數來作為預期值expectedmodCount。集合在被遍歷期間如果內容發生變化,就會改變集合內部的modCount值。每當迭代器使用而而迭代遍歷呼叫 next() 方法時每次都會檢測  if(modCount==expectedmodCount)  ,符合條件就返回遍歷;否則將丟擲異常終止遍歷。

如果集合發生變化時修改modCount值, 並且又同步到expectedmodCount預期值, 比如前文中提到的iterator迭代器的 remove() 方法, 異常則不會丟擲。因此, 不能依賴於這個異常是否丟擲而進行併發操作的程式設計,這個異常只建議用於檢測併發修改的bug。另外, 在java.util包下的集合類都是快速失敗的, 是不能在多執行緒下發生併發修改的(即迭代過程中被修改)。

安全失敗(fail-safe)

採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是在開始遍歷時先複製原有集合內容,在拷貝的集合上進行遍歷, 即在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,所以不會觸發ConcurrentModificationException。java.util.concurrent包下的容器都是安全失敗的, 可以在多執行緒下併發修改。

物件排序

實體類自身具備排序能力

Comparable介面用於使某個類具備可排序能力。實體類實現該介面後覆寫其 compareTo() 方法,即可使實體類自身具備可排序的能力 。程式碼清單如下: 

public class Student implements Comparable<Student> {
    private String name;
    private int age;
    @Override
    public int compareTo(Student o) {
        int flag = this.name.compareTo(o.name);
        if(flag == 0) {
            flag = this.age - o.age;
        } 
        return flag;
    }
}

實體類具備了排序能力後, 呼叫List集合的 sort(Comparator<? super E> c) 或者Collections工具類的 sort(List<T> list) 方法即可實現排序。

List<Student> list = new ArrayList<Student>();list.sort(null);
//Collections.sort(list);

使用比較器排序

Comparator是一個比較器介面,可以用來給不具備排序能力的物件進行排序。實現該比較器需覆寫其 compare() 方法即可進行排序, 程式碼清單如下:

public class Student {
    private String name;
    private int age;
}

public class StudentComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        int flag = o1.getName().compareTo(o2.getName());
        if(flag == 0) {
            flag = o1.getAge() - o2.getAge();
        }
        return flag;
    }
}

public class Test {
    public void sortTest() {
        List<student> list = new ArrayList<Student>();
       list.sort(new StudentComparator());
        //Collections.sort(list, new StudentComparator());
    }
} 

參考資料