每日面試題05:ArrayList和LinkedList的底層原理

ArrayList與LinkedList深度解析:從底層原理到實戰選擇

在Java的List接口實現中,ArrayListLinkedList是最常用的兩種選擇。面試中“它們的區別”幾乎是必問題,但僅僅停留在“數組vs鏈表”的層面顯然不夠。本文將從??底層數據結構、內存布局、核心操作性能、線程安全、實際場景選擇??等維度展開深度解析,并結合性能測試數據,幫你徹底掌握兩者的差異與適用場景。


一、底層數據結構:連續內存 vs 離散節點

1. ArrayList:動態擴容的數組

ArrayList的底層是??動態數組??(本質是Object[] elementData),其核心特點是??內存連續??。這意味著:

  • ??隨機訪問高效??:數組的索引與內存地址直接對應(通過索引*元素大小+起始地址計算),因此通過索引訪問元素的時間復雜度是O(1)
  • ??動態擴容??:數組初始容量默認是10(若通過無參構造創建),當元素數量超過容量時,會觸發擴容機制:新容量 = 原容量 + 原容量/2(即1.5倍擴容)。擴容時需要新建數組并復制原數據(Arrays.copyOf()),這一步的時間復雜度是O(n),但屬于“均攤操作”——大多數情況下添加操作的時間復雜度仍是O(1)(只有擴容時才會額外消耗)。
2. LinkedList:雙向鏈表的節點網絡

LinkedList的底層是??雙向鏈表??(本質是Node節點的鏈式結構),每個節點(private static class Node<E>)包含三個字段:

Node(E e, Node<E> prev, Node<E> next) {this.item = e;this.prev = prev;this.next = next;
}
  • ??內存離散??:節點存儲在堆內存的不同位置,通過prevnext指針連接。這種結構導致節點的內存地址不連續,無法通過索引直接計算內存位置。
  • ??雙向遍歷??:雙向鏈表支持從頭部或尾部高效遍歷(通過firstlast指針直接訪問頭尾節點),但中間節點的訪問仍需從頭部或尾部開始遍歷。

二、內存占用:數組緊湊 vs 鏈表冗余

1. ArrayList的內存開銷

數組的內存占用主要由??元素本身??和??數組元數據??組成:

  • 元素存儲:連續的內存空間,無額外指針開銷(僅存儲對象引用)。
  • 數組元數據:包括數組長度、對象頭(Mark Word、類型指針等),但這些是所有數組共有的開銷,與元素數量無關。
2. LinkedList的內存開銷

鏈表的內存占用包含??節點數據??和??指針開銷??:

  • 每個節點需存儲prevnext兩個指針(64位JVM下每個指針8字節,共16字節),以及元素本身的引用(4或8字節)。
  • 對象頭開銷:每個Node對象還有自己的對象頭(約12字節,壓縮指針下),導致內存浪費更嚴重。

??示例對比??(以存儲Integer對象為例,64位JVM啟用壓縮指針):

  • 存儲1個Integer
    • ArrayList:數組元數據(16字節) + 元素引用(4字節)= 20字節(未算對象對齊)。
    • LinkedListNode對象頭(12字節) + prev(4字節) + next(4字節) + element(4字節)= 24字節(未算對象對齊)。
  • 存儲100萬元素:
    • ArrayList:約100萬 * 4字節(元素引用) + 數組元數據 ≈ 4MB(忽略擴容損耗)。
    • LinkedList:約100萬 * 24字節(節點) ≈ 24MB(是ArrayList的6倍)。

??結論??:存儲大量小對象時,LinkedList的內存占用遠高于ArrayList


三、核心操作性能:隨機訪問 vs 頭尾插入

1. 查找操作:隨機訪問 vs 順序遍歷
  • ??ArrayList??:通過索引訪問元素時,直接計算內存地址,時間復雜度O(1)
    但如果通過contains(Object o)indexOf(Object o)查找元素(需遍歷比較),時間復雜度是O(n)(與鏈表相同)。
  • ??LinkedList??:無論查找哪個元素,都需要從firstlast開始遍歷(最壞情況遍歷整個鏈表),時間復雜度O(n)
2. 插入/刪除操作:數組移動 vs 節點鏈接

插入/刪除的核心差異在于是否需要移動元素(數組)或定位節點(鏈表)。

操作場景ArrayListLinkedList
??尾部插入(add(E e))??均攤O(1)(僅需判斷是否擴容,無需移動元素)O(1)(直接操作last指針)
??頭部插入(add(0, e))??O(n)(需將所有元素后移一位)O(1)(修改first指針和新節點的next
??中間插入(add(index, e))??O(n)(需將index到末尾的元素后移一位)O(n)(需先遍歷找到index位置的節點,再修改前后指針)
??尾部刪除(remove(size()-1))??O(1)(直接置空末尾元素,數組長度減一)O(1)(修改last指針)
??頭部刪除(remove(0))??O(n)(需將所有元素前移一位)O(1)(修改first指針)
??中間刪除(remove(index))??O(n)(需移動index到末尾的元素)O(n)(需遍歷找到節點)

??關鍵誤區??:
很多人認為LinkedList的任意位置插入都是O(1),這是錯誤的。雖然鏈表節點的鏈接操作是O(1),但??定位插入位置需要遍歷??(除非已知前驅節點)。例如,add(index, e)的時間復雜度由node(index)方法的遍歷時間決定——若index接近頭部或尾部,遍歷次數少;若index在中間,仍需O(n)時間。


四、線程安全與擴展實現

兩者均??非線程安全??,多線程環境下可能出現數據不一致(如迭代時修改列表)。若需線程安全:

  • ??ArrayList??:可通過Collections.synchronizedList(new ArrayList<>())包裝,或使用CopyOnWriteArrayList(寫時復制,適合讀多寫少場景)。
  • ??LinkedList??:同樣可通過Collections.synchronizedList包裝,但更推薦使用ConcurrentLinkedQueue(無界非阻塞隊列,適合高并發場景)。

五、實戰選擇:根據場景匹配特性

1. 優先選ArrayList的場景
  • ??隨機訪問頻繁??:如遍歷、按索引獲取元素(如緩存系統、需要快速響應的查詢場景)。
  • ??數據量已知或可預估??:初始化時指定容量,避免動態擴容的性能損耗。
  • ??內存敏感場景??:存儲大量小對象時,數組的緊湊內存更節省資源。
2. 優先選LinkedList的場景
  • ??頭尾插入/刪除頻繁??:如實現隊列(addLast+removeFirst)或棧(addFirst+removeFirst)。
    (注:Java 6后ArrayDeque在頭尾操作上的性能已優于LinkedList,且內存占用更低,更推薦作為隊列/雙端隊列的選擇。)
  • ??數據量小且動態變化大??:小數據量時,鏈表的指針開銷可忽略,而數組的擴容損耗可能更明顯。
3. 避免踩坑
  • ??避免用LinkedList做隨機訪問??:即使get(index)方法時間復雜度是O(n),實際性能遠低于ArrayList
  • ??避免用ArrayList做高頻中間插入??:頻繁移動元素會導致大量內存復制,性能下降明顯。

六、性能測試:用數據說話

為了驗證理論分析,我們編寫一個簡單的性能測試(JDK 17,64位系統):

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;public class ListPerformanceTest {private static final int SIZE = 100000;private static final int INSERT_TIMES = 1000;public static void main(String[] args) {// 測試隨機訪問性能List<Integer> arrayList = new ArrayList<>(SIZE);for (int i = 0; i < SIZE; i++) arrayList.add(i);long start = System.nanoTime();for (int i = 0; i < SIZE; i += 1000) arrayList.get(i);System.out.println("ArrayList隨機訪問耗時:" + (System.nanoTime() - start)/1e6 + "ms");List<Integer> linkedList = new LinkedList<>();for (int i = 0; i < SIZE; i++) linkedList.add(i);start = System.nanoTime();for (int i = 0; i < SIZE; i += 1000) linkedList.get(i);System.out.println("LinkedList隨機訪問耗時:" + (System.nanoTime() - start)/1e6 + "ms");// 測試中間插入性能start = System.nanoTime();for (int i = 0; i < INSERT_TIMES; i++) {arrayList.add(SIZE/2, i);}System.out.println("ArrayList中間插入耗時:" + (System.nanoTime() - start)/1e6 + "ms");start = System.nanoTime();for (int i = 0; i < INSERT_TIMES; i++) {linkedList.add(SIZE/2, i);}System.out.println("LinkedList中間插入耗時:" + (System.nanoTime() - start)/1e6 + "ms");}
}

??測試結果(示例)??:

ArrayList隨機訪問耗時:0.2ms       // 幾乎瞬間完成
LinkedList隨機訪問耗時:125.3ms   // 遍歷耗時顯著
ArrayList中間插入耗時:12.1ms     // 移動元素的開銷
LinkedList中間插入耗時:156.7ms   // 遍歷+鏈接的雙重開銷

??結論??:隨機訪問和中間插入場景下,ArrayList的性能優勢非常明顯;而頭尾插入場景中,兩者性能接近(LinkedList略優,但實際開發中更推薦ArrayDeque)。


總結

ArrayListLinkedList的選擇沒有絕對答案,關鍵在于??場景匹配??:

  • 若需??高頻隨機訪問??(如遍歷、按索引操作),選ArrayList
  • 若需??高頻頭尾插入/刪除??(且數據量不大),選LinkedList(或更優的ArrayDeque);
  • 內存敏感場景,優先ArrayList(緊湊存儲更省內存);
  • 多線程環境,根據需求選擇線程安全的擴展實現(如CopyOnWriteArrayListConcurrentLinkedQueue)。

理解底層原理后,結合具體業務場景的性能瓶頸(如訪問頻率、插入位置、數據量),才能做出最優選擇。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/914680.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/914680.shtml
英文地址,請注明出處:http://en.pswp.cn/news/914680.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python的慈善捐贈平臺管理信息系統

前端開發框架:vue.js 數據庫 mysql 版本不限 后端語言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 數據庫工具&#xff1a;Navicat/SQLyog等都可以 摘要 本文…

三十二、【核心功能改造】數據驅動:重構儀表盤與關鍵指標可視化

三十二、【核心功能改造】數據驅動:重構儀表盤與關鍵指標可視化 前言準備工作第一部分:后端實現 - 統計 API1. 創建 `DashboardStatsView`2. 注冊統計 API 路由3. 后端初步測試第二部分:前端實現 - 重構儀表盤頁面1. 創建 `api/dashboard.ts` API 服務2. 重構 `HomeView.vue…

神經網絡與深度學習Python入門

一、神經網絡基礎 1. 神經元模型 在神經網絡中&#xff0c;最基本的單元是神經元&#xff08;Neuron&#xff09;&#xff0c;也稱為節點或單元。它模擬了生物神經系統中的神經元行為。一個典型的神經元模型包含多個輸入&#xff08;x1,x2,…,xnx_1, x_2, \ldots, x_nx1?,x2?…

Android System WebView:Android生態的核心組件

在Android生態系統中&#xff0c;Android System WebView&#xff08;簡稱WebView&#xff09;扮演著至關重要的角色。它是Chrome瀏覽器的內核&#xff0c;為Android設備提供了強大的網頁瀏覽和Web內容展示功能。無論是日常瀏覽網頁、使用基于Web的應用程序&#xff0c;還是進行…

Element Plus和Ant Design Vue深度對比分析與選型指南

在 Vue3生態中&#xff0c;Element Plus和Ant Design Vue&#xff08;以下簡稱 AntD Vue&#xff09;是兩款最主流的 UI 組件庫。它們分別脫胎于 Element UI&#xff08;Vue 2 版本&#xff09;和 Ant Design&#xff08;React 生態&#xff09;&#xff0c;經過多年迭代已成為…

AJAX 開發中的注意點

關鍵詞&#xff1a;AJAX、異步請求、前端開發、跨域、錯誤處理、安全、性能優化 ? 引言 在現代 Web 應用中&#xff0c;AJAX 是實現前后端數據交互的重要手段。然而&#xff0c;在實際開發過程中&#xff0c;如果不注意一些常見問題&#xff0c;可能會導致應用出現安全性漏洞…

類之間的縱向關系——繼承

繼承定義&#xff1a;被繼承的類叫做基類&#xff08;父類&#xff09;&#xff0c;繼承的類叫派生類&#xff08;子類&#xff09;&#xff0c;在派生類類名后面加&#xff1a; 繼承方式 基類class CFather{}; class CSon:public CFather{};父類(基類)與子類(派生類)之間的關系…

bytetrack漏檢補齊

bytetrack漏檢補齊1.人流慢速運動&#xff0c;跟蹤效果比較好&#xff0c;偶爾有漏檢&#xff0c;跟蹤可以自動補齊。2.快速運動&#xff0c;頻繁遮擋&#xff0c;效果可能不好*如果漏檢&#xff0c;倒著跟蹤&#xff0c;把丟失的檢測框拷貝出來&#xff0c;保留進行跟蹤。有時…

安裝Keycloak并啟動服務(macOS)

前提&#xff1a;電腦已經安裝Java 17 1、下載Keycloak 2、下載完后解壓縮&#xff0c;使用文本編輯器修改配置文件&#xff08;keycloak/conf/keycloak.conf&#xff09; # Basic settings for running in production. Change accordingly before deploying the server. # …

汽車動力轉向器落錘沖擊試驗臺

落錘沖擊試驗臺主要用于扣件減振量的測試&#xff0c;采用電動錨鏈提錘結構&#xff0c;控制精度高&#xff0c;定位準確。采用伺服電機減速機驅動&#xff0c;避免提錘加速和到位減速時的沖擊&#xff0c;具有多重安全保護功能&#xff0c;防止二次沖擊裝置。主機框架采用上下…

Linux系統集群部署模塊之Keepalived雙機熱備

目錄 概述 一、keepalived安裝 二、配置文件 三、 其他配置項說明 四、名詞解釋 五、高階使用 1、介紹 2、keepalived主要作用 3、工作在三層、四層和七層原理 4、健康狀態檢測方式 4.1 HTTP服務狀態檢測 4.2 TCP端口狀態檢測&#xff08;使用TCP端口服務基本上都可…

TDengine 使用最佳實踐(1)

目錄 數據建模 單列模型 多列模型 分庫分表 邊界限制 資源規劃 CPU 主頻 CPU 核數 內存分類 內存計算 CPU 內存比例 磁盤 網絡 下一篇 TDengine 使用最佳實踐&#xff08;1&#xff09; 關于 TDengine TDengine 是一款專為物聯網、工業互聯網等場景設計并優化的大數據平臺&am…

Java行為型模式---責任鏈模式

責任鏈模式基礎概念責任鏈模式&#xff08;Chain of Responsibility Pattern&#xff09;是一種行為型設計模式&#xff0c;其核心思想是將請求的發送者和接收者解耦&#xff0c;使多個對象都有機會處理請求。這些對象連接成一條鏈&#xff0c;請求沿著鏈傳遞&#xff0c;直到有…

嵌入式學習筆記- 結構體名字被賦值時是整體內容賦值

結構體變量名被賦值時&#xff0c;?不是賦值的地址&#xff0c;而是執行對整個結構體內容的復制&#xff08;值拷貝&#xff09;?直接賦值是成員級復制? 當使用 s2 s1; 形式的賦值時&#xff08;其中 s1 和 s2 是同類型結構體變量&#xff09;&#xff0c;系統會?逐成員復…

基于UDP/IP網絡游戲加速高級擁塞控制算法(示意:一)

/* ███████╗ 基于UDP/IP網絡游戲加速高級擁塞控制算法&#xff08;示意&#xff1a;一&#xff09; ███████╗ */#pragma once#include <iostream> #include <vector> #include <deque> #include <cmath> #include <algorithm> …

【YOLOv11-目標檢測】06-模型部署(C++)

上一節課,我們學習了模型的預測。那么,如何用C++部署呢? 克隆項目 進入cmd,進入自己的項目文件夾,然后git clone項目: git clone https://github.com/Geekgineer/YOLOs-CPP 進入到YOLOs-CPP文件夾: 配置環境 ONNX Runtime 后續構建項目的時候,會自動下載,因此,我…

【第零章編輯器開發與拓展】

前言&#xff1a;對編輯器拓展與開發可以節省很多時間&#xff0c;提高開發效率&#xff0c;比如技能編輯器&#xff0c;關卡編輯器這種。當然這只是編輯器開發的一些典型應用&#xff0c;它能做不止這些。學習完這個之后&#xff0c;我們可以開發項目需要的工具。我本意在編輯…

使用 mongoimport 導入本地 JSON 文件到 MongoDB 及數據查看指南

在項目中&#xff0c;我們經常需要將本地 JSON 文件批量導入 MongoDB 數據庫。本文以 Ubuntu 22.04 環境為例&#xff0c;詳細記錄了如何安裝 mongoimport 工具、正確導入多個 JSON 文件&#xff0c;以及查看導入后的數據。一、環境介紹操作系統&#xff1a;Ubuntu 22.04.5 LTS…

新手向:Python數據處理Excel報表自動化生成與分析

Python實現Excel報表自動化系統全流程指南本文將詳細介紹如何使用Python實現一個完整的Excel報表自動化系統&#xff0c;涵蓋從數據清洗、分析到可視化報表生成的全流程。本教程面向Python初學者&#xff0c;通過實際案例講解pandas和openpyxl庫的核心用法。系統概述Excel報表自…

【第六節】docker可視化工具portainer安裝

該文章參考了這篇文章https://zhuanlan.zhihu.com/p/27740131259portainer是一個基于網頁的docker可視化管理工具&#xff0c;試想一下我們怎么登錄路由器管理界面的&#xff0c;異曲同工。那么就需要在服務器的docker內安裝portainer&#xff0c;然后在我們的開發機或者說工作…