1. 程式人生 > >JavaSE 學習 —— 淺談 List

JavaSE 學習 —— 淺談 List

List 簡介

  List 是 java.util 包下面的類,是一個有序集合(有時被稱為序列),List 可以包含重複的元素,其繼承了 Collection 的操作。除此之外,List 本身也是一個介面,其還包括以下操作:

  • 按位置訪問:根據元素在序列中的位置索引訪問元素。
  • 查詢:在序列中查詢指定的物件,並返回其位置索引。
  • 迭代:擴充套件了 Iterator 介面,以利用序列的順序特性。
  • List 子集合:在序列上執行任意範圍的操作。
public interface List<E> extends Collection<E>

ArrayList

ArrayList 簡介

  ArrayList 基於陣列實現,是一個動態的陣列佇列。但是它和 Java 中的陣列又不一樣,它的容量可以自動增長。

ArrayList 繼承了 AbstractList,實現了 RandomAccess、Cloneable 和 Serializable 介面。

public class ArrayList<E> extends AbstractList<E>
			implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList 的容量為什麼會自動增長?

  ArrayList 的列表物件實質上是儲存在一個引用型數組裡的,有人認為該陣列有“自動增長機制”可以自動改變 size 大小。準確地說,該陣列是無法改變大小的,實際上它只是改變了該引用型陣列的指向而已。下面,讓我們來看看 java 是怎樣實現 ArrayList 類的。

ArrayList 原始碼

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{	
	//建立 ArrayList 完成之後,預設的容量為 0。
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

	// ArrayList 的擴容問題其實就是這個 Object 型別的陣列的擴容問題
	//建立一個容量為 x 的 ArrayList 物件,其實就是一個長度為 x 的 Object 陣列
	transient Object[] elementData;	
	
	private void grow(int minCapacity) {
        // ArrayList的原始大小
        int oldCapacity = elementData.length;
		// 在原始大小的基礎上計算擴充後的大小,擴充後的大小是元素大小的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
		//跟前面計算的擴充後長度 minCapacity 比較,取較大的那個為擴充後長度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
		// 如果擴充後長度大於最大長度
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 擴充
        elementData = Arrays.copyOf(elementData, newCapacity);
	}
}

ArrayList 實質
  ArrayList 底層採用 Object 型別的陣列實現,當使用不帶引數的構造方法生成 ArrayList 物件時,實際上會在底層生成一個的 Object 型別陣列。首先,ArrayList 定義了一個私有的未被序列化的陣列 elementData ,用來儲存 ArrayList 的物件列表。
  其次,以指定初始容量(Capacity)或把指定的 Collection 轉換為引用型陣列後例項化 elementData 陣列;如果沒有指定,則引用原始碼中的初始容量進行例項化。把私有陣列預先例項化,然後通過 copyOf 方法覆蓋原陣列,是實現自動改變 ArrayList 的大小(size)的關鍵。

add() 與 addAll() 的區別

  add 是將傳入的引數作為當前 List 中的一個 Item 儲存,即使你傳入一個 List 也只會另當前的List增加1個元素。
  addAll 是傳入一個 List,將此 List 中的所有元素加入到當前List中,也就是當前 List 會增加的元素個數為傳入的 List 的大小。

	Collection demo1 = new ArrayList();   
	Collection demo2 = new ArrayList(); 
	
	demo1.add(1);
	demo1.add(2);
	demo2.add("a");
	demo2.add("b");
	
	System.out.println("demo1:" + demo1);
	System.out.println("demo1Size:" + demo1.size());
	System.out.println("demo2:" + demo2);
	System.out.println("demo2Size:" + demo2.size());
	System.out.println();
	
	demo1.add(demo2);
	System.out.println("---------add--------");
	System.out.println("demo1:" + demo1);
	System.out.println("demo1Size:" + demo1.size());
	System.out.println();
	
	// 恢復原始的 demo1
	demo1.remove(demo2);
	
	demo1.addAll(demo2);
	System.out.println("-------addAll------");
	System.out.println("demo1:" + demo1);
	System.out.println("demo1Size:" + demo1.size());
結果

	demo1:[1, 2]
	demo1Size:2
	demo2:[a, b]
	demo2Size:2
	
	---------add--------
	demo1:[1, 2, [a, b]]
	demo1Size:3
	
	-------addAll------
	demo1:[1, 2, a, b]
	demo1Size:4

建立幾個 ArrayList

Person p1 = new Person();
p1.setName("Springer");
p1.setAge(18);
p1.setSex("male");

Person p2 = new Person();
p2.setName("Simple");
p2.setAge(38);
p2.setSex("female");

List<Person> demo1 = new ArrayList<Person>();
demo1.add(p1);
demo1.add(p2);

ArrayList<Integer> argList = new ArrayList<Integer>();
argList.add(1);
argList.add(2);
argList.add(3);
argList.add(4);
argList.add(5);
argList.add(6);

四種遍歷方法:

1)通過迭代器遍歷

Iterator<Person> it = demo1.iterator();	
while(it.hasNext()){
	System.out.print(it.next());	
	System.out.println();
}

2)通過增強 for 迴圈遍歷

for(Person d : demo1){
    System.out.println(d);
}

3)通過索引遍歷

Iterator<Person> it = demo1.iterator();
for(int i = 0; i < demo1.size(); i++){
    System.out.print(demo1.get(i));
    System.out.println();
}
  1. Lambda 表示式

    demo1.forEach(System.out::println);
    System.out.println();

結果
name:Springer,age:18,sex:male 
name:Simple,age:38,sex:female

ArrayList 中幾種常用的方法

1. 獲取 List 中元素的個數
List<Person> demo1 = new ArrayList<Person>();
System.out.println("---------獲取 List 中元素個數---------");
System.out.println(demo1.size());

---------獲取 List 中元素個數---------
2
2.在指定位置插入元素
  • argList.add(index, element)

index:插入位置的下標
element:插入的元素

System.out.println("---------新增前遍歷---------");
	for(int i = 0; i < argList.size(); i++){
		System.out.print(argList.get(i) + "  ");
	}
     
    argList.add(2, 7);
    
    System.out.println("---------新增後遍歷---------");
    for(int i = 0; i < argList.size(); i++){
    System.out.print(argList.get(i) + "  ");
    }

---------新增前遍歷---------  
1  2  3  4  5  6    
---------新增後遍歷---------  
1  2  7  3  4  5  6  
3.刪除指定位置的元素
  • argList.remove(index)

index:刪除元素下標

    argList.remove(2); 
     System.out.println("---------刪除指定位置元素後遍歷---------");
     for(int i = 0; i < argList.size(); i++){
     System.out.print(argList.get(i) + "  ");
     }

---------刪除指定位置元素後遍歷---------  
1  2  3  4  5  6  
4.刪除指定元素
  • argList.remove(Object)

Object:刪除的元素

    argList.remove((Object)5);
    System.out.println("---------刪除指定元素後遍歷---------");
    for(int i = 0; i < argList.size(); i++){
    	System.out.print(argList.get(i) + "  ");
    }

---------刪除指定元素後遍歷---------  
1  2  3  4  6  
5.判斷元素是否存在
  • argList.contains(Object)

Object:需要判斷的元素

 boolean obj3 = argList.contains(3);
    boolean obj5 = argList.contains(5);

    System.out.println("---------判斷是否存在元素---------");
    System.out.println("ArrayList contains 3 is: " + obj3);
    System.out.println("ArrayList contains 5 is: " + obj5);

---------判斷是否存在元素---------  
ArrayList contains 3 is: true  
ArrayList contains 5 is: false 
6.清空集合

argList.clear()

System.out.println("---------清空前 argList 長度---------");
System.out.println("argList中元素個數:" + argList.size());
argList.clear();

System.out.println("---------清空後 argList 長度---------");
System.out.println("argList中元素個數:" + argList.size());

System.out.println("---------判斷 argList 是否為空---------");
System.out.println("判斷argList是否為空:" + argList.isEmpty());

---------清空前 argList 長度---------  
argList中元素個數:5  
---------清空後 argList 長度---------  
argList中元素個數:0  
---------判斷 argList 是否為空---------  
判斷argList是否為空:true

LinkedList

LinkedList 簡介

LinkedList 是一個繼承於 AbstractSequentialList 的雙向連結串列。它也可以被當作堆疊、佇列或雙端佇列進行操作,其相對於 ArrayList 來說,是可以快速新增,刪除元素,ArrayList 新增刪除元素的話需移動陣列元素,可能還需要考慮到擴容陣列長度。

LinkedList 繼承了 AbstractSequentialList,實現了 List、Deque、Cloneable 和Serializable 介面

public class LinkedList<E> extends AbstractSequentialList<E>
	   implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList 屬性

LinkedList 本身的的屬性比較少,主要有三個,一個是 size,代表當前有多少個節點;一個是 first,代表第一個節點;一個是 last,代表最後一個節點。

建立一個 LinkedList

LinkedList<String> linkedList = new LinkedList<>();

linkedList.add("first");
linkedList.add("second");
linkedList.add("third");
System.out.println(linkedList);

[first, second, third]

LinkedList 中幾種常用的方法

1. 在 LinkedList 中第一個節點新增元素
  • addFirst(element)

element:需要新增的元素

    linkedList.addFirst("addFirst");
    System.out.println(linkedList);

[addFirst, first, second, third]
2. 在 LinkedList 中最後一個節點新增元素
  • addLast(element)

element:需要新增的元素

    linkedList.addLast("addLast");
    System.out.println(linkedList);

[addFirst, first, second, third, addLast]
3. 在 LinkedList 中指定位置插入元素
  • add(index,element)

index:插入位置的下標
element:需要新增的元素

    linkedList.add(2, "addByIndex");
    System.out.println(linkedList);

[addFirst, first, addByIndex, second, third, addLast]

LinkedList 和 ArrayList 的區別

   ArrayList 與 LinkedList 都是 List 介面的實現類,因此都實現了 List 的所有未實現的方法,只是實現的方式有所不同,而 List 介面繼承了 Collection 介面, Collection 介面又繼承了 Iterable 介面,因此可以看出 List 同時擁有了 Collection 與 Iterable 介面的特性。
   ArrayList 實現了 List 介面,它是以陣列的方式來實現的,陣列的特性是可以使用索引的方式來快速定位物件的位置, 因此對於快速的隨機取得物件的需求,使用 ArrayList 實現執行效率上會比較好。
  LinkedList 是採用連結串列的方式來實現 List 介面的,它本身有自己特定的方法,如: addFirst() , addLast() , getFirst(), removeFirst()等。 由於是採用連結串列實現的,因此在進行 insert 和 remove 動作時在效率上要比ArrayList要好得多!適合用來實現 Stack (堆疊)與 Queue (佇列),前者先進後出,後者是先進先出。

關於 ArrayList 和 LinkedList 的效率問題

ArrayList 底層是用陣列來儲存物件的,這種方式將物件放在連續的位置中。

優點:可以通過陣列下標快速的拿到值,大量修改時高效。
缺點:每一次新增和刪除都需要將操作的元素後面的元素們全部移動,非常麻煩。

LinkedList 則是將物件放在獨立的空間中,而且在每一個空間中存放下一個連結的索引。

優點:增加和刪除非常快速,其底層是連結串列,插入元素只要打斷這個節點插入元素即可。
缺點:要定位元素的位置時,要從頭一個一個尋找,耗費資源。

package test;

import java.util.ArrayList;
import java.util.LinkedList;

public class TimeForList {

public static long getArrayListFindTime(){
        ArrayList<Integer> list = new ArrayList<Integer>(); 
        for (int i = 0; i < 71000; i++) {
            list.add(0,1);
        }
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.get(i);
            
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    
    public static long getLinkedListFindTime(){
        LinkedList<Integer> list = new LinkedList<Integer>(); 
        for (int i = 0; i < 71000; i++) {
            list.add(0,1);
        }
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            
            list.get(i);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }

    public static long getLinkedListAddTime(){
        LinkedList<Integer> list = new LinkedList<Integer>(); 
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.add(0,1);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    
    
    public static long getArrayListAddTime(){
        ArrayList<Integer> list = new ArrayList<Integer>(); 
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.add(0, 1);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    
	public static long getLinkedListAddLastTime(){
        LinkedList<Integer> list = new LinkedList<Integer>(); 
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.add(i,1);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    
    
    public static long getArrayListAddLastTime(){
        ArrayList<Integer> list = new ArrayList<Integer>(); 
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.add(i, 1);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }

    public static void main(String[] args) {
        System.out.println("用LinkedList完成新增操作(開頭新增)所需時間為:" + getLinkedListAddTime() + "ms");
        
        System.out.println( "用Linked完成新增操作(末尾新增)所需時間為:" + getLinkedListAddLastTime() + "ms");
        
        System.out.println("用ArrayList完成新增操作(開頭新增)所需時間為:" + getArrayListAddTime() + "ms");
        
        System.out.println( "用ArrayList完成新增操作(末尾新增)所需時間為:" + getArrayListAddLastTime() + "ms");
        
        System.out.println("用LinkedList完成查詢操作所需時間為:" + getLinkedListFindTime() + "ms");
        
        System.out.println("用ArrayList完成查詢操作所需時間為:" + getArrayListFindTime() + "ms");
    }
}

結果
用 LinkedList 完成新增操作(開頭新增)所需時間為:15ms
用 Linked 完成新增操作(末尾新增)所需時間為:17ms
用 ArrayList 完成新增操作(開頭新增)所需時間為:941ms
用 ArrayList 完成新增操作(末尾新增)所需時間為:11ms
用 LinkedList 完成查詢操作所需時間為:4457ms
用 ArrayList 完成查詢操作所需時間為:2ms

由此可以直觀的看出 LinkedList 和 ArrayList 的效率


點選下載文章內容原始碼
提取密碼:jnfj
本文內容部分取自百度內容,如有雷同部分請見諒。