1. 程式人生 > 其它 >ArrayList和LinkedList

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

那裡,StringBuffer的預設初始容量是16個字元,當字串容量大於當前容量時,進行擴容,新的容量 = [2 * (舊的容量) + 2 ] 個字元。
  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檢索但不刪除當前集合的第一個元素
  上表中是一些針對頭指標和尾指標的方法,還有push(E e)、pop()這兩個對棧的操作函式,除此之外還有一些對於Queue佇列的peek()、poll()、offer()的方法沒有列上去,因此LinkedList除了可以用List的方法之外,還可以用棧和佇列的方法。

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一樣會丟擲異常。

小結

  1. 使用for迴圈索引形式正序遍歷刪除時,必須注意索引號,避免漏掉元素或者陣列越界
  2. 使用for迴圈索引形式逆序遍歷刪除時,正確執行
  3. 使用迭代器方式遍歷刪除時,使用迭代器的remove()正確執行,使用集合的remove()可能會拋異常
  4. 使用foreach形式遍歷刪除時,一次刪除之後執行break或者return,可以正確執行,但如果刪除元素不是最後一個元素的話,集合沒有遍歷完
  5. 使用foreach形式遍歷刪除時,如果刪除的是倒數第二個元素,可以正確執行,但是最後一個元素不會被遍歷到
  6. 使用foreach形式遍歷刪除時,如果不是上面兩種情況的話,刪除時會丟擲異常