ArrayList和LinkedList
技術標籤:JAVA
List
在集合宣告中會看見<E>,這個是集合中的泛型,而E是填你希望儲存的引用資料型別,例如,ArrayList<String>表示這是一個儲存String型別的ArrayList陣列。
如果想要在集合中儲存不同型別的資料,那麼需要在E處填Object,例如,ArrayList<Object>,在儲存資料的時候發生向上轉型,在讀取資料的時候發生向下轉型。
ArrayList
建立物件
ArrayList提供了三種建構函式
- public ArrayList()
構造一個初始容量為十的空列表。 - public ArrayList(int initialCapacity)
- public ArrayList(Collection<? extends E> c)
構造一個包含指定集合的元素的列表(可以理解為一個複製列表的過程)
例如:
ArrayList<String> list1 = new ArrayList<String>();//Jdk1.7之前後面<>之內的不能省略
ArrayList<String> list2 = new ArrayList<>();//Jdk1.7之後後面的<>的內容可以省略
上面的構造方法中有初始容量這個概念,我們之前也接觸過這個概念,是在可變字串StringBuffer
ArrayList底層實現是Object[ ],在ArrayList中初始化容量預設是10,也就是底層的Object陣列長度是10,當容量不夠了進行擴容,新的容量 = [舊的容量 + (舊的容量 >> 1)],即新的容量 = 1.5 * 舊的容量。
常用方法
方法 | 返回值 | 功能 | |
新增 | add(E e) | boolean | 將指定物件e新增到當前集合 |
add(int index, E e) | void | 將指定集合元素e插入到當前集合指定索引index處 | |
addAll(Collection<?> c) | boolean | 將指定集合元素全部新增到當前集合 | |
addAll(int index, Collection<?> c) | boolean | 將指定集合元素全部插入到當前集合索引index處 | |
刪除 | remove(int index) | E | 將指定索引index處的元素從當前集合中移除 |
remove(Object o) | boolean | 從當前集合中移除第一個出現的指定元素o | |
removeAll(Collection<?> c) | boolean | 將指定集合元素全部從當前集合中移除 | |
clear() | void | 將當前集合中所有元素刪除 | |
查詢 | get(int index) | E | 返回當前集合索引為index處的元素 |
indexOf(Object o) | int | 返回當前集合中元素o第一次出現的索引 | |
size() | int | 返回當前集合中元素的個數 | |
iterator() | Iterator<E> | 返回在此集合的元素上進行迭代的迭代器(主要用於遍歷集合上) | |
修改 | set(int index, E e) | E | 修改當前集合索引為index處的元素為e,返回的E是被修改的元素 |
trimToSize() | void | 修改列表的容量為當前列表長度 | |
判斷 | isEmpty() | boolean | 判斷當前集合是否為空,若是空返回true,否則返回false |
containsAll(Collection<?> c) | boolean | 判斷當前集合中是否包含集合c的所有元素,若全包含返回true,否則返回false | |
contains(Object o) | boolean | 判斷某個元素在當前集合中是否存在,若存在返回true,否則返回false | |
交集 | retainAll(Collection<?> c) | boolean | 僅保留當前集合中包含在指定集合中的元素,若存在交集,當前集合僅保留交集元素,並返回true,否則當前集合變為空,返回false |
集合轉陣列 | toArray() | Object[] | 返回一個包含此集合中所有元素的陣列 |
ArrayList的遍歷
因為ArrayList可以使用索引訪問,因此可以使用普通的for迴圈來進行ArrayList集合的遍歷
for迴圈
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i));
}
foreach
for (String s : list1) {
System.out.println(s);
}
迭代器
Iterator<String> it = list1.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
判空
ArrayList<String> list1 = null;// 沒物件
ArrayList<String> list2 = new ArrayList<String>();// 有物件,元素個數為0
if (list2 == null || list2.isEmpty()) {
}
if (list2 != null && !list2.isEmpty()) {
}
LinkedList
建立物件
LinkedList提供了兩種建構函式
- public ArrayList()
構造一個空連結串列。 - public ArrayList(Collection<? extends E> c)
構造一個包含指定集合的元素的連結串列
LinkedList的底層實現不是陣列,因此沒有初始容量的概念,LinkedList的底層實現是雙向連結串列。我們可以在LinkedList的原始碼中找到下面的3個成員變數,分別是連結串列的長度,指向第一個元素的指標和指向最後一個元素的指標。
transient int size = 0;// 連結串列元素的數量
transient Node<E> first;// 指向連結串列第一個元素
transient Node<E> last;// 指向連結串列最後一個元素
ArrayList有的方法LinkedList都有,並且由於LinkedList是有兩個指標,所以LinkedList的方法中除了ArrayList中講過的方法,還有一些針對指標的方法。
方法 | 返回值 | 功能 | |
新增 | addFirst(E e) | void | 將指定物件e插入到當前集合的開頭 |
addLast(E e) | void | 將指定集合元素e追加到當前集合指定尾部 | |
push(E e) | void | 將指定集合元素e追加到當前集合指定尾部 | |
刪除 | removeFirst() | E | 刪除並返回當前集合的第一個元素 |
removeLast() | E | 刪除並返回當前集合中最後一個元素 | |
pop() | E | 和removeFirst()一樣刪除第一個元素 | |
查詢 | getFirst() | E | 返回當前集合第一個元素 |
getLast() | E | 返回當前集合中最後一個元素 | |
peek() | E | 檢索但不刪除當前集合的第一個元素 |
ArrayList和LinkedList的區別
底層實現 | 隨機訪問 get()和set() | 新增和刪除操作 | |
---|---|---|---|
ArrayList | 底層實現是陣列 ArrayList實現了基於動態陣列的資料結構 | 效率高,直接通過索引訪問 | 效率低,需要移動元素 |
LinkedList | 底層實現是雙向連結串列 | 效率低,需要移動指標 | 效率高,直接修改指標 |
幾個迴圈中刪除集合元素的坑
想想下面程式碼是否可以完成迴圈刪除某個元素的功能?
ArrayList<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
for (String s : list) {
System.out.println(s);
if ("cc".equals(s))
list.remove(s);
}
System.out.println(list);
結果:
出現異常!這是因為在用foreach迴圈遍歷集合的時候刪除元素就有可能發生這種異常現象。注意!是有可能,也就是說有可能不發生,那麼什麼情況不會發生呢?
for (String s : list) {
System.out.println(s);
if ("dd".equals(s))
list.remove(s);
}
System.out.println(list);
aa
bb
cc
dd
[aa, bb, cc, ee]
只有刪除倒數第二個元素的時候才不會出現異常,但是!請注意ee沒有輸出,所以第五次迴圈根本沒有進行,所以如果真的是想要遍歷集合的話,這種方法也是不行的,因為根本沒有遍歷完,沒有得到所有的元素。
for (String s : list) {
System.out.println(s);
if ("cc".equals(s)) {
list.remove(s);
break;
}
}
System.out.println(list);
如果在刪除元素之後加上break;那麼不會拋異常,因為進行到刪除之後就結束迴圈了,所有在不需要遍歷的時候,刪除第一個指定元素的時候可以。
使用foreach迴圈遍歷集合的時候,不能對集合元素進行修改(即使修改了集合元素也不會改變),不能對集合元素進行刪除(可能會拋異常)。
那麼普通for迴圈呢?
為了便於觀察,在本次迴圈剛開始和馬上結束本次迴圈的地方加上輸出
for (int i = 0; i < list.size(); i++) {
System.out.println("$$ " + i + " " + list.get(i) + " " + list.size());
if ("cc".equals(list.get(i)))
list.remove(i);
System.out.println("%% " + i + " " + list.get(i) + " " + list.size());
}
$$ 0 aa 5
%% 0 aa 5
$$ 1 bb 5
%% 1 bb 5
$$ 2 cc 5
%% 2 dd 4
$$ 3 ee 4
%% 3 ee 4
這種方法不會拋異常,但是並沒有去訪問dd,輸出的cc和dd只有一次,cc是在cc刪除前輸出的,dd是在cc刪除完本次迴圈結束前的輸出語句中輸出的,所以說dd並沒有被訪問到。
既然這樣,那麼我們在迴圈刪除的時候將i減一不就行了嗎
for (int i = 0; i < list.size(); i++) {
System.out.println("$$ " + i + " " + list.get(i) + " " + list.size());
if ("cc".equals(list.get(i)))
list.remove(i);
System.out.println("%% " + i + " " + list.get(i) + " " + list.size());
}
$$ 0 aa 5
%% 0 aa 5
$$ 1 bb 5
%% 1 bb 5
$$ 2 cc 5
%% 1 bb 4
$$ 2 dd 4
%% 2 dd 4
$$ 3 ee 4
%% 3 ee 4
輸出沒問題,每個元素都被訪問到了,並且刪除我們指定的元素。但這種迴圈每次在刪除元素的時候都需要想著i–,很容易漏掉的,其實我們完全可以用——逆序遍歷刪除指定元素。
for (int i = list.size() - 1; i >= 0; i--) {
System.out.println("$$ " + i + " " + list.get(i) + " " + list.size());
if ("cc".equals(list.get(i))) {
list.remove(i);
}
System.out.println("%% " + i + " " + list.get(i) + " " + list.size());
}
$$ 4 ee 5
%% 4 ee 5
$$ 3 dd 5
%% 3 dd 5
$$ 2 cc 5
%% 2 dd 4
$$ 1 bb 4
%% 1 bb 4
$$ 0 aa 4
%% 0 aa 4
在對集合進行迴圈遍歷並刪除元素的時候,推薦方法是使用逆序遍歷刪除元素。同理,迴圈遍歷並新增元素的時候,推薦使用正序遍歷
除了for迴圈和foreach迴圈,集合還有一種遍歷方法——迭代器迴圈。使用個迭代器迴圈這種方法在Jdk1.7之後就很少使用了,網上一些使用迭代器的程式碼都是比較老的了,不過迭代器迴圈有一個好處:
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String str = it.next();
System.out.println(str);
if ("cc".equals(str)) {
it.remove();
// list.remove(str);
}
}
aa
bb
cc
dd
ee
可以看到使用迭代器的方法可以完成迴圈中刪除元素的操作,不管是迴圈次數也好,刪除元素也好,都是正確的。但是需要注意的是,刪除操作一定要用迭代器的remove(),如果你使用的是我註釋中的集合remove()方法的話,你會發現和foreach一樣會丟擲異常。
小結
- 使用for迴圈索引形式正序遍歷刪除時,必須注意索引號,避免漏掉元素或者陣列越界
- 使用for迴圈索引形式逆序遍歷刪除時,正確執行
- 使用迭代器方式遍歷刪除時,使用迭代器的remove()正確執行,使用集合的remove()可能會拋異常
- 使用foreach形式遍歷刪除時,一次刪除之後執行break或者return,可以正確執行,但如果刪除元素不是最後一個元素的話,集合沒有遍歷完
- 使用foreach形式遍歷刪除時,如果刪除的是倒數第二個元素,可以正確執行,但是最後一個元素不會被遍歷到
- 使用foreach形式遍歷刪除時,如果不是上面兩種情況的話,刪除時會丟擲異常