1. 程式人生 > >List基礎--這一篇全瞭解

List基礎--這一篇全瞭解

為什麼需要集合

  首先,Java是一門面向物件的語言,奉承一切皆物件的思想,在實際開發過程中,免不了要經常操作物件,而且會同時操作多個甚至大量物件,這時就需要一個專門儲存這些物件的容器,這樣就可以利用容器物件的特性來方便的進行操作。
  其次,在Java中還有陣列,也是一種容器,但和集合相比,陣列長度不可變,只能儲存同一種類型元素
  所以,Java提供了集合的概念,相比於陣列,集合長度可變,可儲存多種型別元素只能儲存引用型別元素;同時不同的集合容器,有不同的實現方法和特性,可以更加方便的對物件進行操作。

簡介:

List介面為Collection直接介面。List所代表的是有序的Collection,即它用某種特定的插入順序來維護元素順序。使用者可以對列表中每個元素的插入位置進行精確地控制,同時可以根據元素的整數索引(在列表中的位置)訪問元素,並搜尋列表中的元素。實現List介面的集合主要有:ArrayList、LinkedList、Vector、Stack。

為什麼元素“有序”、“可重複”呢?
  首先,List的資料結構就是一個序列,儲存內容時直接在記憶體中開闢一塊連續的空間,然後將空間地址與索引對應
  其次,List介面的實現類在實現插入元素時,都會根據索引進行排列,如ArrayList,本質是一個數組,插入時元素互不干擾,自然可以重複。
  由於List繼承了Collection,所以就具有了Collection的方法:

int size();//集合中元素個數
isEmpty();//是否為空
boolean contains(Object o);//比較地址是否相等
Iterator<E> iterator();//迭代器方法,可用於遍歷
Object[] toArray();//轉成陣列
boolean add(E e);//向list尾部新增新元素
boolean remove(Object o);//刪除元素
boolean containsAll(Collection<?> c);//集合A是否包含集合B(A為呼叫者)
boolean addAll(Collection<? extends E> c);//將集合B中元素新增到集合A中尾部(A為呼叫者)
boolean removeAll(Collection<?> c);//將兩集合中的交集元素刪除
int hashCode();//求雜湊值

 同時,也具有自己獨特的方法(對索引的操作):

E get(int index);//取出對應索引的元素
E set(int index, E element);//往指定索引中插入指定元素,並將原元素覆蓋
void add(int index, E element);//往指定索引插入指定元素,其他元素依次後移一位
E remove(int index);//刪除索引位置的元素,此位置後的元素前移一位
int indexOf(Object o);//獲取元素的索引,若有重複元素,則取最靠前的
int lastIndexOf(Object o);//從後往前數,第一個元素的索引
ListIterator<E> listIterator();//list獨有迭代器,迭代式可以add元素
List<E> subList(int fromIndex, int toIndex);//擷取原集合元素生成新的集合,值域為[fromIndex,toIndex)

ArrayList

       ArrayList是一個動態陣列允許任何符合規則的元素插入甚至包括null。每一個ArrayList都有一個初始容量(10),該容量代表了陣列的大小。隨著容器中的元素不斷增加,容器的大小也會隨著增加。在每次向容器中增加元素的同時都會進行容量檢查,當快溢位時,就會進行擴容操作。所以如果我們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進行擴容操作而浪費時間、效率。

       size、isEmpty、get、set、iterator 和 listIterator 操作都以固定時間執行。add 操作以分攤的固定時間執行,也就是說,新增 n 個元素需要 O(n) 時間(由於要考慮到擴容,所以這不只是新增元素會帶來分攤固定時間開銷那樣簡單)。

ArrayList擅長於隨機訪問。同時ArrayList是非同步的

LinkedList

       實現List介面的LinkedList與ArrayList不同,ArrayList是一個動態陣列,而LinkedList是一個雙向連結串列。所以它除了有ArrayList的基本操作方法外還額外提供了get,remove,insert方法在LinkedList的首部或尾部。

       由於實現的方式不同,LinkedList不能隨機訪問,它所有的操作都是要按照雙重連結串列的需要執行。在列表中索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。這樣做的好處就是可以通過較低的代價在List中進行插入和刪除操作

       與ArrayList一樣,LinkedList也是非同步的。如果多個執行緒同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在建立List時構造一個同步的List: 
List list = Collections.synchronizedList(new LinkedList(...));

Vector

       與ArrayList相似,但是Vector是同步的。所以說Vector是執行緒安全的動態陣列。它的操作與ArrayList幾乎一樣。

Stack

       Stack繼承自Vector,實現一個後進先出的堆疊。Stack提供5個額外的方法使得Vector得以被當作堆疊使用。基本的push和pop 方法,還有peek方法得到棧頂的元素,empty方法測試堆疊是否為空,search方法檢測一個元素在堆疊中的位置。Stack剛建立後是空棧。

什麼時候會用到List

  如果涉及到“棧”、“佇列”、“連結串列”等操作,應該考慮用List,具體用哪個具體分析:需要快速插入或刪除,用LinkedList;需要快速訪問,用ArrayList;多執行緒環境,且List可能同時被多個執行緒操作,用vector。
  當然,對於執行緒安全的List,也可以用CopyOnWriteArrayList,基於陣列實現的執行緒安全(ReentrantLock加鎖)的寫時複製集合,效能比vector高,適合高併發的讀多寫少的場景
  CopyOnWrite容器,是一個寫時複製容器,當我們往一個容器新增元素時,不是直接往當前容器新增,而是先將容器copy一份,複製出新的容器,然後對新的容器裡的元素進行操作,最後將原容器的引用指向新的容器。所以,這是一種讀寫分離的思想,讀和寫不同的容器。缺點:若寫操作頻繁,會頻繁複制容器,從而影響效能。

 使用List可能出現的問題

  1.NullPointException和IndexOutOfBoundsException問題。List是一個容器物件,在業務邏輯中經常定義List容器,盛放多個業務物件,當容器中沒有任何元素時,呼叫容器操作索引方法,則出現索引越界異常;當作為返參容器時,並沒有查到資料,會返回null,此時就容易出現空指標異常。
  2.Arrays.asList方法。我們經常用此API將陣列轉成List物件,但稍不注意就會報異常。
  情況一:方法呼叫的入參為基本資料(byte、short、int、long、char、double、float、boolean)型別的陣列時,結果會返回一個元素數量為1的集合,和原資料的元素個數無關,這樣是因為此方法接收泛型引數,而基本型別不支援泛型化,而陣列支援,所以那為1的元素是整個陣列,實質存的是此陣列的引用地址
  情況二生成新的List集合後,呼叫add()方法,會出現UnsupportedOperationException異常。抽象類AbstractList定義了對List操作的方法如add等,但用此方法生成的list集合並沒有實現它們,所以會報異常。解決:用new關鍵字建立一個List容器物件,而Arrays.asList作為構造引數入參傳入。

後續可以接著看: