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作為構造引數入參傳入。
後續可以接著看: