Java數據結構篇六:List集合詳解

前言

Java集合就像一個容器,可以存儲任何類型的數據,也可以結合泛型來存儲具體的類型對象。在程序運行時,Java集合可以動態的進行擴展,隨着元素的增加而擴大。在Java中,集合類通常存在於java.util包中。

Java集合主要由2大體系構成,分別是Collection體系和Map體系,其中Collection和Map分別是2大體系中的頂層接口。

Collection主要有三個子接口,分別爲List(列表)、Set(集)、Queue(隊列)。其中,List、Queue中的元素有序可重複,而Set中的元素無序不可重複;在這裏插入圖片描述

在List集合中,我們常用到ArrayList和LinkedList這兩個類。下面做學習記錄。

一、ArrayList的定義:

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

ArrayList繼承了 抽象類AbstractList,實現了 List接口,並且實現了RandomAccess, Cloneable,
java.io.Serializable這三個標記接口,標記接口中是沒有聲明任何方法的,沒有方法並不代表沒有用,他們分別表示ArrayList具有可隨機訪問,可克隆及可序列化能力。

ArrayList的類的結構如下圖所示
在這裏插入圖片描述

ArrayList常用方法

// ArrayList類對於元素的操作,基本體現在——增、刪、查。常用的方法有:
A:添加功能
boolean add(E e):向集合中添加一個元素
void add(int index, E element):在指定位置添加元素
boolean addAll(Collection<? extends E> c):向集合中添加一個集合的元素。

B:刪除功能
void clear():刪除集合中的所有元素
E remove(int index):根據指定索引刪除元素,並把刪除的元素返回
boolean remove(Object o):從集合中刪除指定的元素
boolean removeAll(Collection<?> c):從集合中刪除一個指定的集合元素。

C:修改功能
E set(int index, E element):把指定索引位置的元素修改爲指定的值,返回修改前的值。

D:獲取功能
E get(int index):獲取指定位置的元素
Iterator iterator():就是用來獲取集合中每一個元素。

E:判斷功能
boolean isEmpty():判斷集合是否爲空。
boolean contains(Object o):判斷集合中是否存在指定的元素。
boolean containsAll(Collection<?> c):判斷集合中是否存在指定的一個集合中的元素。

F:長度功能
int size():獲取集合中的元素個數

G:把集合轉換成數組
Object[] toArray():把集合變成數組。

ArrayList擴容機制

1.判斷是不是第一次添加元素,若爲第一次,則設置初始化大小爲默認的值10,否則使用傳入的參數(ArrayList 構造函數中如果沒有設置初始化的容量大小,在沒有添加有元素的時候,其初始化容量是0,只有當添加第一個元素的時候,纔會初始化容量纔會設置成10.)
2.如果元素的個數,大於其容量,則把其容量擴展爲原來容量的1.5倍
3.實際擴容,使用了Arrays.copyof(elementData, newCapacity)
在這裏插入圖片描述
特性

  • ArrayList底層通過數組實現,讀取速度非常快,隨着元素的增加而動態擴容
  • ArrayList 頻繁擴容將會導致性能下降,因爲每次擴容都會複製原來的數組到創建的新數組。
  • ArrayList 插入時會移動數組元素,插入越靠前,移動的元素就越多,效率越差。
  • 有序集合,插入的元素可以爲null
  • 線程不安全
  • ArrayList底層通過數組實現,隨着元素的增加而動態擴容。而LinkedList底層通過鏈表來實現,隨着元素的增加不斷向鏈表的後端增加節點。

源碼參考:https://mp.weixin.qq.com/s/er7Dqzu6o5l3RpEsml3DVQ
https://www.jianshu.com/p/63b01b6379fb

源碼需注意一個點:transient修飾符是什麼含義?
當我們序列化對象時,如果對象中某個屬性不進行序列化操作,那麼在該屬性前添加transient修飾符即可實現;例如:

private transient Object[] elementData;

那麼,爲什麼ArrayList不想對elementData屬性進行序列化呢? elementData可是集合中保存元素的數組啊,如果不序列化elementData屬性,那麼在反序列化時候,豈不是丟失了原先的元素?ArrayList在添加元素時,可能會對elementData數組進行擴容操作,而擴容後的數組可能並沒有全部保存元素。

例如:我們創建了new Object[10]數組對象,但是我們只向其中添加了1個元素,而剩餘的9個位置並沒有添加元素。當我們進行序列化時,並不會只序列化其中一個元素,而是將整個數組進行序列化操作,那些沒有被元素填充的位置也進行了序列化操作,間接的浪費了磁盤的空間,以及程序的性能。所以,ArrayList纔會在elementData屬性前加上transient修飾符。

二、LinkedList定義

LinkedList是一個雙向鏈表,每一個節點都擁有指向前後節點的引用。相比於ArrayList來說,LinkedList的隨機訪問效率更低。LinkedList的類的結構如下圖所示:在這裏插入圖片描述
它繼承AbstractSequentialList,實現了List, Deque, Cloneable, Serializable接口。

(1) LinkedList實現List,得到了List集合框架基礎功能;
(2) LinkedList實現Deque,Deque是一個雙向隊列,也就是既可以先入先出,又可以先入後出,說簡單些就是既可以在頭部添加元素,也可以在尾部添加元素;
(3) LinkedList實現Cloneable,得到了clone()方法,可以實現克隆功能;
(4) LinkedList實現Serializable,表示可以被序列化,通過序列化去傳輸,典型的應用就是hessian協議。

假如LinkedList中的元素爲[“A”,“B”,“C”],其內部的結構如下圖所示
在這裏插入圖片描述

List實現類的使用場景

  • ArrayList,底層採用數組實現,如果需要遍歷集合元素,應該使用隨機訪問的方式,對於LinkedList集合應該採用迭代器的方式
  • 如果需要經常的插入。刪除操作可以考慮使用LinkedList集合
  • 如果有多個線程需要同時訪問List集合中的元素,開發者可以考慮使用Collections將集合包裝成線程安全的集合(Collections.synchronizedList(new
    LinkedList<>()))。

總結:

1)LinkedList是一個功能很強大的類,可以被當作List集合,雙端隊列和棧來使用。
2)LinkedList使用鏈表保存集合中的元素,因此隨機訪問的性能較差,但是插入刪除時性能高
3)LinkedList基於鏈表實現的,因此不存在容量不足的問題,所以沒有擴容的方法
4)注意源碼中的Node node(int index)方法。該方法返回雙向鏈表中指定位置處的節點,而鏈表中是沒有下標索引的,要指定位置出的元素,就要遍歷該鏈表,從源碼的實現中,我們看到這裏有一個加速動作。源碼中先將index與長度size的一半比較,如果index<size/2,就只從位置0往後遍歷到位置index處,而如果index>size/2,就只從位置size往前遍歷到位置index處。這樣可以減少一部分不必要的遍歷,從而提高一定的效率(實際上效率還是很低)。

源碼解析:https://www.jianshu.com/p/732b5294a985

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章