Java中ArrayList和LinkedList區(qū)別
1 前言
許多語言,例如 Perl ,Python 和 Ruby ,都有集合的本地支持。有些語言(例如Python)甚至將基本集合組件(列表,映射和集合)作為內(nèi)置函數(shù)包含在其中。
通常,程序總是根據(jù)運行時才知道的某些條件去創(chuàng)建新的對象。在此之前,無法知道所需對象的數(shù)量甚至確切類型。為了解決這個普遍的編程問題,需要在任意時刻和任意位置創(chuàng)建任意數(shù)量的對象。java.util
庫提供了一套相當(dāng)完整的集合類(collection classes)來解決這個問題,其中基本的類型有 List 、 Set 、 Queue 和 Map。這些類型也被稱作容器類。
2 數(shù)據(jù)結(jié)構(gòu)的區(qū)別
2.1 ArrayList
ArrayList
是基于數(shù)組實現(xiàn)的,數(shù)組是典型的緊密結(jié)構(gòu),所以它使用索引在數(shù)組中搜索和讀取數(shù)據(jù)快,可以直接返回數(shù)組中index位置的元素,因此在隨機訪問集合元素上有較好的性能。
2.2 LinkedList
LinkedList是基于雙鏈表實現(xiàn)的,鏈表是典型的跳轉(zhuǎn)結(jié)構(gòu),插入和刪除只是指針指向的修改,所以它插入、刪除中間元素快,因此在操作數(shù)據(jù)方面性能較好。
2.3 使用場景
如果應(yīng)用程序?qū)?shù)據(jù)有較多的隨機訪問,ArrayList
對象要優(yōu)于LinkedList
對象;
如果應(yīng)用程序有更多的插入或者刪除操作,較少的隨機訪問,LinkedList對象要優(yōu)于ArrayList對象;
3 源碼分析
下面我們通過JDK1.8源碼源碼分析一下核心功能。
3.1 ArrayList核心源碼
public class ArrayList<E> extends AbstractList<E> ? ? ? ? implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ? ?? ? ? //默認(rèn)大小 ? ? private static final int DEFAULT_CAPACITY = 10; ? ? ? ? ? //省略。。。 ? ?? ? ? //動態(tài)數(shù)組 ? ? transient Object[] elementData;? ? ?? ? ? //數(shù)組大小 ? ? private int size; ? ?? ? ? //空構(gòu)造器 ? ? public ArrayList() { ? ? ? ? this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; ? ? } ? ?? ? ? // 查詢元素 ? ? public E get(int index) { ? ? ? ? // 檢查索引是否在范圍內(nèi) ? ? ? ? rangeCheck(index);?? ??? ??? ??? ??? ? ? ? ? ? return elementData(index); ? ? } ? ?? ? ? //順序添加元素 ? ? public boolean add(E e) { ? ? ? ? //擴容 ? ? ? ? ensureCapacityInternal(size + 1);? ? ? ? ? elementData[size++] = e; ? ? ? ? return true; ? ? } ? ?? ? ? //從數(shù)組中間添加元素 ? ? public void add(int index, E element) { ? ? ? ? // 檢查索引是否在范圍內(nèi) ? ? ? ? rangeCheckForAdd(index); ? ? ? ? // 擴容 ? ? ? ? ensureCapacityInternal(size + 1);? ? ? ? ? // 復(fù)制數(shù)組 ? ? ? ? System.arraycopy(elementData, index, elementData, index + 1, ? ? ? ? ? ? ? ? ? ? ? ? ?size - index); ? ? ? ? // 替換元素 ? ? ? ? elementData[index] = element; ? ? ? ? size++; ? ? } ? ?? ? ? //是否要擴容 ? ? private void ensureCapacityInternal(int minCapacity) { ? ? ? ? ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); ? ? } ? ?? ? ? //確定容量 ? ? private void ensureExplicitCapacity(int minCapacity) { ? ? ? ? modCount++; ? ? ? ? if (minCapacity - elementData.length > 0) ? ? ? ? ? ? grow(minCapacity); ? ? } ? ?? ? ? //計算數(shù)組容量 ? ? private static int calculateCapacity(Object[] elementData, int minCapacity) { ? ? ? ? if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { ? ? ? ? ? ? return Math.max(DEFAULT_CAPACITY, minCapacity); ? ? ? ? } ? ? ? ? return minCapacity; ? ? } ? ?? ? ? //動態(tài)數(shù)組擴容放法 ? ? private void grow(int minCapacity) { ? ? ? ? //? ? ? ? ? int oldCapacity = elementData.length; ? ? ? ? int newCapacity = oldCapacity + (oldCapacity >> 1); ? ? ? ? if (newCapacity - minCapacity < 0) ? ? ? ? ? ? newCapacity = minCapacity; ? ? ? ? if (newCapacity - MAX_ARRAY_SIZE > 0) ? ? ? ? ? ? newCapacity = hugeCapacity(minCapacity); ? ? ? ? // 數(shù)組復(fù)制 ? ? ? ? elementData = Arrays.copyOf(elementData, newCapacity); ? ? } ? ?? ? ? ?//省略。。。 ? ?? ? }
總結(jié):
ArrayLis
初始化構(gòu)造器的時候數(shù)組為{},在調(diào)用add方法以后默認(rèn)數(shù)組才賦值為新數(shù)組,新數(shù)組默認(rèn)長度為10。
通過擴容機制判斷原數(shù)組是否還有空間,若沒有則重新實例化一個空間更大的新數(shù)組,把舊數(shù)組的數(shù)據(jù)拷貝到新數(shù)組中。
在執(zhí)行查詢操作時,先判斷下標(biāo)是否越界,然后在直接通過下標(biāo)從數(shù)組中返回元素。
3.2 LinkedList核心源碼
//JDK1.8 public class LinkedList<E> ? ? extends AbstractSequentialList<E> ? ? implements List<E>, Deque<E>, Cloneable, java.io.Serializable {? ? ? // 元素數(shù)量 ? ? transient int size = 0; ? ?? ? ? // 鏈表首節(jié)點 ? ? transient Node<E> first; ? ?? ? ? // 鏈表尾節(jié)點 ? ? transient Node<E> last; ? ?? ? ? // 空構(gòu)造器 ? ? public LinkedList() { ? ? ? ?? ? ? } ? ?? ? ? // Node內(nèi)部類 ? ? private static class Node<E> { ? ? ? ? E item; ? ? ? ? Node<E> next; ? ? ? ? Node<E> prev; ? ? ? ? Node(Node<E> prev, E element, Node<E> next) { ? ? ? ? ? ? // 中間元素 ? ? ? ? ? ? this.item = element; ? ? ? ? ? ? // 下一個元素地址 ? ? ? ? ? ? this.next = next; ? ? ? ? ? ? // 上一個元素地址 ? ? ? ? ? ? this.prev = prev; ? ? ? ? } ? ? } ? ?? ? ? // 順序添加元素 ? ? public boolean add(E e) { ? ? ? ? linkLast(e); ? ? ? ? return true; ? ? } ? ?? ? ? // 指定位置添加元素 ? ? public void add(int index, E element) { ? ? ? ? // 檢查是否越界 ? ? ? ? checkPositionIndex(index);?? ? ? ? ? ? // 在鏈表末尾添加,否則在之前插入元素 ? ? ? ? if (index == size)?? ??? ??? ? ? ? ? ? ? ? linkLast(element); ? ? ? ? else ? ? ? ? ? ? linkBefore(element, node(index)); ? ? } ? ?? ? ? // 添加元素e ? ? void linkLast(E e) { ? ? ? ? //把鏈表中l(wèi)ast元素賦給l ,如果是第一個元素則為null ? ? ? ? final Node<E> l = last; ? ? ? ? //把元素封裝為一個Node對象 ? ? ? ? final Node<E> newNode = new Node<>(l, e, null); ? ? ? ? //把鏈表的last元素指向新的元素 ? ? ? ? last = newNode; ? ? ? ? //如果添加的是第一個元素 ? ? ? ? if (l == null){ ? ? ? ? ? ? //把鏈表的first元素指向新的元素 ? ? ? ? ? ? first = newNode; ? ? ? ? } ? ? ? ? //如果添加的不是第一個元素 ? ? ? ? else{ ? ? ? ? ? ? //把l的下一個元素指向新的元素 ? ? ? ? ? ? l.next = newNode; ? ? ? ? ? ? ? ? ? ? } ? ? ? ? //集合中元素數(shù)量加1 ? ? ? ? size++; ? ? ? ? modCount++; ? ? } ? ?? ? ? void linkBefore(E e, Node<E> succ) { ? ? ? ? final Node<E> pred = succ.prev; ? ? ? ? final Node<E> newNode = new Node<>(pred, e, succ); ? ? ? ? succ.prev = newNode; ? ? ? ? if (pred == null) ? ? ? ? ? ? first = newNode; ? ? ? ? else ? ? ? ? ? ? pred.next = newNode; ? ? ? ? size++; ? ? ? ? modCount++; ? ? } ? ?? ? ? //獲取元素 ? ? public E get(int index) { ? ? ? ? //檢測元素索引 ? ? ? ? checkElementIndex(index); ? ? ? ? return node(index).item; ? ? } ? ?? ? ? //返回指定元素索引處的(非空)節(jié)點 ? ? Node<E> node(int index) { ? ? ? ? //把鏈表分為兩段,如果index在鏈表的前段,則從前往后找,否則反之 ? ? ? ? if (index < (size >> 1)) { ? ? ? ? ? ? Node<E> x = first; ? ? ? ? ? ? for (int i = 0; i < index; i++) ? ? ? ? ? ? ? ? x = x.next; ? ? ? ? ? ? return x; ? ? ? ? } else { ? ? ? ? ? ? Node<E> x = last; ? ? ? ? ? ? for (int i = size - 1; i > index; i--) ? ? ? ? ? ? ? ? x = x.prev; ? ? ? ? ? ? return x; ? ? ? ? } ? ? } ? ?? ? ? //省略。。。 ? ?? }
總結(jié):
LinkedList
的get首先判斷需要獲取的數(shù)據(jù)在該鏈表的前半段還是后半段,這樣可以減少需要遍歷的數(shù)據(jù),由于需要遍歷數(shù)據(jù),所以獲取數(shù)據(jù)的速度會比ArrayList
慢。
由于LinkedList底層是用雙向鏈表實現(xiàn)的,沒有初始化大小,也沒有擴容的機制。
4 碼農(nóng)來洞見
4.1為什么ArrayList比LinkedList要快
ArrayList
和LinkedList
本質(zhì)上每次插入和刪除元素都會進行復(fù)制數(shù)組的操作,如果ArrayList插入操作沒有觸發(fā)數(shù)組擴容操作,并且在List靠近末尾的地方插入,那么ArrayList
只需要移動較少的數(shù)據(jù),而LinkedList則需要一直查找到列表尾部,反而耗費較多時間,這時ArrayList就比LinkedList要快。
4.2 注意ArrayList不同JDK版本源碼
JDK1.7中在調(diào)用構(gòu)造器的時候數(shù)組長度就初始化為了10,JDK1.8則是在調(diào)用add方法后才創(chuàng)建數(shù)組長度為10的新數(shù)組替換默認(rèn)空數(shù)組。
4.3 高并發(fā)下如何保證集合數(shù)據(jù)的同步
ArrayList
和LinkedList
都不是線程安全的。然而Vector類被認(rèn)為是過時的廢棄的了。
方案一: Collections.synchronizedList(); 得到一個線程安全的 ArrayList。
方案二: concurrent 并發(fā)包下的 CopyOnWriteArrayList 類。
CopyOnWriteArrayList和Collections.synchronizedList是實現(xiàn)線程安全的列表的兩種方式。兩種實現(xiàn)方式分別針對不同情況有不同的性能表現(xiàn),其中CopyOnWriteArrayList的寫操作性能較差,而多線程的讀操作性能較好。而Collections.synchronizedList的寫操作性能比CopyOnWriteArrayList在多線程操作的情況下要好很多,而讀操作因為是采用了synchronized關(guān)鍵字的方式,其讀操作性能并不如CopyOnWriteArrayList。因此在不同的應(yīng)用場景下,應(yīng)該選擇不同的多線程安全實現(xiàn)類。
4.4 為什么Java的Vector類被認(rèn)為是過時的或者廢棄的
Vector中對每一個獨立操作都實現(xiàn)了同步,這通常不是我們想要的做法。對單一操作實現(xiàn)同步通常不是線程安全的(舉個例子,比如你想遍歷一個Vector實例。你仍然需要申明一個鎖來防止其他線程在同一時刻修改這個Vector實例。如果不添加鎖的話
通常會在遍歷實例的這個線程中導(dǎo)致一個ConcurrentModificationException)同時這個操作也是十分慢的(在創(chuàng)建了一個鎖就已經(jīng)足夠的前提下,為什么還需要重復(fù)的創(chuàng)建鎖)
當(dāng)然,即使你不需要同步,Vector也是有鎖的資源開銷的。
到此這篇關(guān)于Java中ArrayList和LinkedList區(qū)別的文章就介紹到這了,更多相關(guān)ArrayList和LinkedList區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何基于SpringSecurity的@PreAuthorize實現(xiàn)自定義權(quán)限校驗方法
spring Security提供有若干個過濾器,它們能夠攔截Servlet請求,并將這些請求轉(zhuǎn)給認(rèn)證和訪問決策管理器處理,從而增強安全性,下面這篇文章主要給大家介紹了關(guān)于如何基于SpringSecurity的@PreAuthorize實現(xiàn)自定義權(quán)限校驗方法的相關(guān)資料,需要的朋友可以參考下2023-03-03Springcloud?feign傳日期類型參數(shù)報錯的解決方案
這篇文章主要介紹了Springcloud?feign傳日期類型參數(shù)報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03SpringBoot整合新版SpringSecurity完整過程
Spring Security是保障Spring應(yīng)用程序安全的強大框架,而新版的Spring Security引入了lambda表達(dá)式來配置,使得安全配置更加簡潔、優(yōu)雅,本文將介紹如何在Spring Boot項目中整合新版Spring Security,需要的朋友可以參考下2024-02-02SpringCloud使用Nacos保存和讀取變量的配置方法
在使用SpringCloud開發(fā)微服務(wù)時,經(jīng)常會遇到一些比較小的后臺參數(shù)配置,這些配置不足以單獨開一張表去存儲,而且其他服務(wù)會讀取該參數(shù),這篇文章主要介紹了SpringCloud使用Nacos保存和讀取變量,需要的朋友可以參考下2022-07-07SpringBoot結(jié)合JSR303對前端數(shù)據(jù)進行校驗的示例代碼
這篇文章主要介紹了SpringBoot結(jié)合JSR303對前端數(shù)據(jù)進行校驗的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java實現(xiàn)將PDF轉(zhuǎn)為PDF/A
通過將PDF格式轉(zhuǎn)換為PDF/A格式,可保護文檔布局、格式、字體、大小等不受更改,從而實現(xiàn)文檔安全保護的目的,同時又能保證文檔可讀、可訪問。本文將為大家介紹如何實現(xiàn)這一轉(zhuǎn)換,需要的可以參考一下2022-01-01