List集合介紹
List集合概述
List集合是一個元素有序(每個元素都有對應的順序索引,第一個元素索引為0)、且可重復的集合。
List集合常用方法
List是Collection接口的子接口,擁有Collection所有方法外,還有一些對索引操作的方法。
void add(int index, E element);
:將元素element插入到List集合的index處;boolean addAll(int index, Collection<? extends E> c);
:將集合c所有的元素都插入到List集合的index起始處;E remove(int index);
:移除並返回index處的元素;int indexOf(Object o);
:返回對象o在List集合中第一次出現的位置索引;int lastIndexOf(Object o);
:返回對象o在List集合中最后一次出現的位置索引;E set(int index, E element);
:將index索引處的元素替換為新的element對象,並返回被替換的舊元素
;E get(int index);
:返回集合index索引處的對象;List<E> subList(int fromIndex, int toIndex);
:返回從索引fromIndex(包含)到索引toIndex(不包含)所有元素組成的子集合;void sort(Comparator<? super E> c)
:根據Comparator參數對List集合元素進行排序;void replaceAll(UnaryOperator<E> operator)
:根據operator指定的計算規則重新設置集合的所有元素。ListIterator<E> listIterator();
:返回一個ListIterator對象,該接口繼承了Iterator接口,在Iterator接口基礎上增加了以下方法,具有向前迭代功能且可以增加元素:
bookean hasPrevious()
:返回迭代器關聯的集合是否還有上一個元素;
E previous();
:返回迭代器上一個元素;
void add(E e);
:在指定位置插入元素;
示例
1)運行主類
public class DemoApplication {
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add(new String("book001"));
list.add(new String("book002"));
list.add(new String(" book003 "));
System.out.println("原列表:" + list);
//將新字符串插入第二個位置
list.add(1, new String("newBook002"));
System.out.println("新增第二個位置元素后列表:" + list);
//刪除第三個元素
list.remove(2);
System.out.println("刪除第三個元素后列表:" + list);
//判斷指定元素在List集合的位置
System.out.println("判斷newBook002的位置:" + list.indexOf(new String("newBook002")));
//將第二元素替換新的字符串
System.out.println("替換的舊值:" + list.set(1, new String("book002")));
System.out.println("替換第二個元素后的列表:" + list);
//返回第二個元素
System.out.println("回第二個元素:" + list.get(1));
List<String> newList = new ArrayList<>();
newList.add("book001");
newList.add("book004");
newList.add("book002");
//新增集合
list.addAll(1, newList);
System.out.println("新增一個集合后的列表:" + list);
//返回元素最后一次出現的位置索引
System.out.println("返回\"book001\"最后一次出現的位置:" + list.lastIndexOf("book001"));
//截取子集合
System.out.println("返回一個范圍子集合列表:" + list.subList(0, 3));
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//逆序
return o2.compareTo(o1);
}
});
//lambda表達式輸出
list.forEach(book -> System.out.println(book));
list.replaceAll(String::trim);
System.out.println("replaceAll去除兩端空格" + list);
list.replaceAll(t -> t.replace("book00", "書籍系列"));
System.out.println("replaceAll替換字符串:" + list);
System.out.println("正向迭代輸出:");
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
//添加元素,會影響list元素
listIterator.add("book");
}
System.out.println("反向迭代輸出:");
while(listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}
System.out.println(list);
}
}
2)運行結果:
原列表:[book001, book002, book003 ]
新增第二個位置元素后列表:[book001, newBook002, book002, book003 ]
刪除第三個元素后列表:[book001, newBook002, book003 ]
判斷newBook002的位置:1
替換的舊值:newBook002
替換第二個元素后的列表:[book001, book002, book003 ]
回第二個元素:book002
新增一個集合后的列表:[book001, book001, book004, book002, book002, book003 ]
返回"book001"最后一次出現的位置:1
返回一個范圍子集合列表:[book001, book001, book004]
book004
book002
book002
book001
book001
book003
replaceAll去除兩端空格[book004, book002, book002, book001, book001, book003]
replaceAll替換字符串:[書籍系列4, 書籍系列2, 書籍系列2, 書籍系列1, 書籍系列1, 書籍系列3]
正向迭代輸出:
書籍系列4
書籍系列2
書籍系列2
書籍系列1
書籍系列1
書籍系列3
反向迭代輸出:
book
書籍系列3
book
書籍系列1
book
書籍系列1
book
書籍系列2
book
書籍系列2
book
書籍系列4
[書籍系列4, book, 書籍系列2, book, 書籍系列2, book, 書籍系列1, book, 書籍系列1, book, 書籍系列3, book]
從上述運行結果看出,System.out.println("判斷newBook002的位置:" + list.indexOf(new String("newBook002")));
我們重新new一個"newBook002"進行判斷索引位置時,還是可以返回索引位置,List集合判斷兩個對象相當只通過equals()
方法,所以如果重寫對象的equals()
方法都是true,則存入List集合中的對象其實都是相等的。
ArrayList
ArrayList概述
ArrayList 是一個數組隊列,相當於動態數組。與Java中的數組相比,它的容量能動態增長。它繼承於AbstractList,實現了List
, RandomAccess
(隨機訪問), Cloneable
(克隆), java.io.Serializable
(可序列化)這些接口。
ArrayList 繼承了AbstractList
,實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能。
ArrayList 實現了RandmoAccess
接口,即提供了隨機訪問功能。RandmoAccess是java中用來被List實現,為List提供快速訪問功能的。在ArrayList中,我們即可以通過元素的序號快速獲取元素對象;這就是快速隨機訪問
。
ArrayList 實現了Cloneable
接口,即覆蓋了函數clone()
,能被克隆。
ArrayList 實現java.io.Serializable
接口,這意味着ArrayList支持序列化
,能通過序列化去傳輸。
和Vector不同,ArrayList中的操作不是線程安全
的!所以,建議在單線程
中才使用ArrayList
,而在多線程
中可以選擇Vector
或者CopyOnWriteArrayList
。
ArrayList源碼解析
- ArrayList包含了兩個重要的對象:
elementData
和size
。
elementData
是"Object[]類型的數組",它保存了添加到ArrayList中的元素。實際上,elementData是個動態數組,我們能通過構造函數 ArrayList(int initialCapacity)來執行它的初始容量為initialCapacity;如果通過不含參數的構造函數ArrayList()來創建ArrayList,則elementData的容量默認是10。elementData數組的大小會根據ArrayList容量的增長而動態的增長
size
則是動態數組的實際大小。
// 默認初始化容量為10
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA默認為空數組
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 才開始構造的時候是一個空list,只有當第一個元素add的時候,擴展到DEFAULT_CAPACITY值,即長度為10.
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
// 構造成10長度的空list;
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- ArrayList 實際上是通過一個數組去保存數據的。當我們構造ArrayList時;若使用默認構造函數,則ArrayList的默認容量大小是
10
。 - 當ArrayList容量不足以容納全部元素時,ArrayList會重新設置容量:
新的容量=“(原始容量x3)/2 + 1
”。 - ArrayList的克隆函數,即是將全部元素克隆到一個數組中。
- ArrayList實現
java.io.Serializable
的方式。當寫入到輸出流時,先寫入“容量”,再依次寫入“每一個元素”;當讀出輸入流時,先讀取“容量”,再依次讀取“每一個元素”。
ArrayList的遍歷方式
3種方式
- 第一種,通過迭代器遍歷。即通過
Iterator
去遍歷。
Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
- 第二種,
隨機訪問index
,通過索引值去遍歷。
由於ArrayList實現了RandomAccess接口,它支持通過索引值去隨機訪問元素。
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
value = (Integer)list.get(i);
}
- 第三種,
增強for循環遍歷
。如下:
Integer value = null;
for (Integer integ:list) {
value = integ;
}
遍歷ArrayList時,使用隨機訪問(即,通過索引序號訪問)效率最高,而使用迭代器的效率最低
LinkedList
LinkedList概述
LinkedList 是一個繼承於AbstractSequentialList
的雙向鏈表。它也可以被當作堆棧、隊列或雙端隊列進行操作。LinkedList的本質是雙向鏈表。1)LinkedList繼承於AbstractSequentialList,並且實現了Dequeue接口。2) LinkedList包含兩個重要的成員:header 和 size。
header
是雙向鏈表的表頭,它是雙向鏈表節點所對應的類Entry的實例。Entry中包含成員變量:previous, next, element。(其中,previous
是該節點的上一個節點,next
是該節點的下一個節點,element
是該節點所包含的值。)
size
是雙向鏈表中節點的個數。
LinkedList 實現 List接口
,能對它進行隊列操作。
LinkedList 實現 Deque接口
,即能將LinkedList當作雙端隊列使用。
LinkedList 實現了Cloneable接口
,即覆蓋了函數clone()
,能克隆。
LinkedList 實現java.io.Serializable接口
,這意味着LinkedList支持序列化
,能通過序列化去傳輸。
LinkedList 是非同步的
。(若要實現同步 List list = Collections.synchronizedList(new LinkedList(...))
;)
LinkedList源碼分析
- 訪問性
LinkedList實際上是通過雙向鏈表
去實現的。既然是雙向鏈表,那么它的順序訪問會非常高效
,而隨機訪問效率比較低
。 - 根據索引值操作
既然LinkedList是通過雙向鏈表的,但是它也實現了List接口,也就是說,它實現了get(int index)
、remove(int index)
等根據索引值來獲取、刪除節點的函數。 - LinkedList是如何實現List的這些接口的,如何將雙向鏈表和索引值聯系起來的?其實,它是通過一個
計數索引值
來實現的。例如,當程序調用get(int index)
方法時,首先會比較location
和雙向鏈表長度的1/2
;如果前者大,則從鏈表頭開始向后
查找,直到location位置
;否則,從鏈表末尾開始向前
查找,直到location位置
。
總結:
- LinkedList 實際上是通過
雙向鏈表
去實現的。包含一個非常重要的內部類:Entry
。Entry是雙向鏈表節點所對應的數據結構,它包括的屬性有:當前節點所包含的值
,上一個節點
,下一個節點
。 - LinkedList的克隆函數,即是將全部元素克隆到一個新的LinkedList對象中。
- LinkedList實現
java.io.Serializable
。當寫入到輸出流時,先寫入“容量”,再依次寫入“每一個節點保護的值”;當讀出輸入流時,先讀取“容量”,再依次讀取“每一個元素”。 - 由於LinkedList實現了
Deque
,而Deque接口定義了在雙端隊列兩端訪問元素的方法。提供插入、移除和檢查元素的方法。每種方法都存在兩種形式:一種形式在操作失敗時拋出異常,另一種形式返回一個特殊值(null 或 false,具體取決於操作)。
LinkedList遍歷方式
支持多種遍歷方式。建議不要采用隨機訪問的方式去遍歷LinkedList,而采用逐個遍歷的方式。
- 第一種,通過迭代器遍歷。即通過
Iterator
去遍歷。
for(Iterator iter = list.iterator(); iter.hasNext();)
iter.next();
- 通過
快速隨機index
訪問遍歷LinkedList
int size = list.size();
for (int i=0; i<size; i++) {
list.get(i);
}
- 通過另外一種
增強版for循環
來遍歷LinkedList
for (Integer ele: list) {
}
- 通過
pollFirst()
來遍歷LinkedList,獲取並移除此列表的第一個元素
;如果此列表為空,則返回 null
while(list.pollFirst() != null){
}
- 通過
pollLast()
來遍歷LinkedList,獲取並移除此列表的最后一個元素
;如果此列表為空,則返回 null。
while(list.pollLast() != null) {
}
- 通過
removeFirst()
來遍歷LinkedList,移除並返回此列表的第一個元素
。 NoSuchElementException - 如果此列表為空。
try {
while(list.removeFirst() != null) {
}
} catch (NoSuchElementException e) {
}
- 通過
removeLast()
來遍歷LinkedList,移除並返回此列表的最后一個元素
。NoSuchElementException - 如果此列表為空。
try {
while(list.removeLast() != null) {
}
} catch (NoSuchElementException e) {
}
QA
ArrayList底層動態擴容的原理?
ArrayList底層采用數組實現,當使用不帶參數的構造方法生成ArrayList對象時,底層實際會生成一個長度為10
的Object類型數組
,如果增加的元素個數超過10個,則ArrayList底層會新生成一個數組,長度為原數組的1.5倍+1
,然后將原數組的內容復制到新數組中去,兵器后續增加的內容都會放入新數組中,當新數組無法容納新元素時,又會重復上述步驟。
ArrayList和LinkedList的區別?
- 底層實現:ArrayList實現是基於動態數組的數據結構(新建一個數組進行擴容,然后copy原來數組中內容,實現數組可增長);LinkedList是基於雙向鏈表的數據結構,其每個對象除了數據本身外,還有兩個引用,分別指向前一個元素和后一個元素。
- 查詢:對於隨機訪問get和set,ArrayList支持;LinkedList不支持,因為LinkedList要移動指針。
- 增刪:對於新增和刪除操作add和remove,在ArrayList的中間插入或刪除一個元素意味着這個列表中剩余的元素都會被移動;而在LinkedList的中間插入或刪除一個元素的開銷是固定的。
- 應用場景:ArrayList適合一列數據的后面添加數據而不是在前面或中間,且需要隨機訪問元素;LinkedList適合在一列數據的前面或中間添加或刪除數據,且按照順序訪問其中的元素。
- 消耗內存:LinkedList比ArrayList消耗更多的內存,因為LinkedList中的每個節點都存儲前后節點的引用。(雙向鏈表)
ArrayList和Vector的區別?
- 線程安全性:ArrayList是非線程安全的,Vector是線程安全的,如果需要再迭代的時候對列表進行改變,使用CopyOnWriteArrayList。
- 效率:ArrayList是非同步的,效率高;Vector是同步的,效率低;
ArrayList和CopyOnWriteArrayList的區別
- 和ArrayList繼承於AbstractList不同,CopyOnWriteArrayList沒有繼承於AbstractList,它僅僅只是實現了List接口。
- ArrayList的iterator()函數返回的Iterator是在AbstractList中實現的;而CopyOnWriteArrayList是自己實現Iterator。
- ArrayList的Iterator實現類中調用next()時,會“調用checkForComodification()比較'expectedModCount'和'modCount'的大小”;但是,CopyOnWriteArrayList的Iterator實現類中,沒有所謂的checkForComodification(),更不會拋出ConcurrentModificationException異常!
Iterater和ListIterator區別
- 遍歷目標:可以使用Iterator來遍歷Set和List集合;而ListIterator只能遍歷List。
- 遍歷方向:Iterator只可以后向順序遍歷;而ListIterator可以雙向遍歷。
- 功能區別:ListIterator從Iterator接口繼承,然后添加了一些額外的功能,比如添加一個元素、替換一個元素、獲取前面或后面元素的索引位置。