Java集合:關于 ArrayList 的內容盤點

本篇內容包括:ArrayList 概述、ArrayList 的擴容機制(包含源碼部分)、如何在遍歷 ArrayList 時正確的移除一個元素、ArrayList 的構造方法及常用方法、關于 Array 與 ArrayList 的區別、關于 CopyOnWriteArrayList、關于 Fail Fast 與 Fail Safe 機制!


文章目錄

    • 一、ArrayList 概述
    • 二、ArrayList 的擴容
        • 1、ArrayList 的擴容機制(源碼)
        • 2、在遍歷 ArrayList 時移除一個元素
    • 三、ArrayList 的使用
        • 1、構造方法
        • 2、常用方法
    • 四、相關知識點
        • 1、關于 Array 與 ArrayList 的區別
        • 2、關于 CopyOnWriteArrayList
        • 3、關于 Fail Fast
        • 4、關于 Fail Safe


一、ArrayList 概述

ArrayList 是最常用的 List 實現類,內部是通過數組實現的,它允許對元素進行快速隨機訪問。數組的缺點是每個元素之間不能有間隔,當數組大小不滿足時需要增加存儲能力,就要將已經有數組的數據復制到新的存儲空間中。當從 ArrayList 的中間位置插入或者刪除元素時,需要對數組進行復制、移動、代價比較高。因此,它適合隨機查找和遍歷,不適合插入和刪除。

ArrayList 是基于數組實現的,相當于動態數組,相當于動態數組,其容量能動態增長,類似于 C 語言中的動態申請內存,動態增長內存。

ArrayList 的每個實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。它總是大于等于列表的大小。隨著向 ArrayList 中不斷添加元素,其容量也自動增長。自動增長會帶來數據向新數組的重新拷貝,因此,如果可預知數據量的多少,可在構造 ArrayList 時指定其容量。

ArrayList 在被添加大量元素前,應用程序可以使用 ensureCapacity() 操作來指定 ArrayList 實例的容量,這可以減少遞增式再分配的數量。

ArrayList 是非線程安全的,只能在單線程環境下使用,多線程環境下可以考慮用 Collections.synchronizedList(List l) 函數返回一個線程安全的 ArrayList 類,也可以使用 java.util.concurrent 并發包下的 CopyOnWriteArrayList 類。


二、ArrayList 的擴容

1、ArrayList 的擴容機制(源碼)

ArrayList 底層是一個 Object 數組 elementData,用于存放插入的數據:

private transient Object[] elementData;		// 存儲ArrayList中的元素
/*** 定義元素個數*/
private int size();

我們知道,數組需要使用著一塊連續的內存空間,因此數組的大小一旦被規定就無法改變。那如果我們不斷的往里面添加數據的話,ArrayList 是如何進行擴容的呢 ?

public boolean add(E e) {// 確認elementData容量是否足夠ensureCapacityInternal(size + 1);  // 第一次調用add()方法時,size=0elementData[size++] = e;return true;}

ArrayList 添加元素時會先調用 ensureCapacityInternal(int minCapacity) 方法,對數組容量進行檢查,判斷剩余空間是否足夠,不夠時則進行擴容

private void ensureCapacityInternal(int minCapacity) {// 如果elementData為"{}"即第一次調用add(E e),重新定義minCapacity的值,賦值為DEFAULT_CAPACITY=10// 即第一次調用add(E e)方法時,定義底層數組elementData的長度為10if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}// 判斷是否需要擴容ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {modCount++;// 第一次進入時,minCapacity=10,elementData.length=0,對數組進行擴容// 之后再進入時,minCapacity=size+1,elementData.length=10(每次擴容后會改變),// 需要minCapacity>elementData.length成立,才能擴容if (minCapacity - elementData.length > 0){grow(minCapacity);}
}

ArrayList 通過 grow(minCapacity) 方法對數組進行擴容

private void grow(int minCapacity) {// 將數組長度賦值給oldCapacityint oldCapacity = elementData.length;// 將oldCapacity右移一位再加上oldCapacity,即相當于newCapacity=1.5oldCapacity(不考慮精度損失)int newCapacity = oldCapacity + (oldCapacity >> 1);// 如果newCapacity還是小于minCapacity,直接將minCapacity賦值給newCapacityif (newCapacity - minCapacity < 0)newCapacity = minCapacity;// 特殊情況:newCapacity的值過大,直接將整型最大值賦給newCapacity,// 即newCapacity=Integer.MAX_VALUEif (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 將elementData的數據拷貝到擴容后的數組elementData = Arrays.copyOf(elementData, newCapacity);
}// 如果大于臨界值,進行整型最大值的分配
private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) {// overflowthrow new OutOfMemoryError();}return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

總結:ArrayList 在添加元素時,會進行一個判斷,當「元素個數+1> 當前數組長度(size + 1 > elementData.length)」時,進行擴容,擴容后的數組大小是原大小的 1.5 倍(oldCapacity + (oldCapacity >> 1))。最后將舊數組進行復制(調用 Arrays.copyof(),再調用 System.arraycopy() ),達到擴容的目的,此時新舊列表的 size 大小相同,但 elementData 的長度即容量不同。

2、在遍歷 ArrayList 時移除一個元素

在遍歷 ArrayList 時移除一個元素,這是一個比較經典的面試題,這里最常用的有 2 種方式:

方式一:在 for 循環中使用倒序遍歷 remove 刪除元素.

假設按照從 0size-1 下標來刪有相鄰且相同的兩個元素,刪除第一個,數組長度會 -1 并且所有元素往前移動一位,那么第二個就到第一個元素的位置,此時控值 for 循環的下標 i 已經 +1 ,相當于直接就跳過了第二個重復元素,而倒敘可以避免此類情況。

方式二:使用迭代器遍歷 ArrayList 并刪除元素(推薦)。

Eg:

List<String> strs = new ArrayList<>();
strs.add("1")
strs.add("2")
strs.add("3")
strs.add("4")
strs.add("5")
strs.add("6")Iterator<String> iter = strs.iterator();
while(iter.hasNext()) {if (iter.next().toString().equals("1")) {iter.remove();}
}
System.out.println(strs);

三、ArrayList 的使用

1、構造方法

方法名方法說明
public ArrayList()無參構造函數,此構造函數用于創建一個空列表,其初始容量足以容納10個元素
public ArrayList(int initialCapacity)此構造函數用于創建具有初始容量的空列表
public ArrayList(Collection<? extends E> c)此構造函數用于創建包含指定集合的元素的列表

2、常用方法

方法名方法說明
boolean add(E e)此方法將指定的元素追加到此列表末尾
void add(int index, E element)此方法將指定的元素插入此列表中的指定位置
boolean addAll(Collection<? extends E> c)此方法按指定集合迭代器的返回順序將指定集合中所有元素加到列表末尾
boolean addAll(int index, Collection<? extends E> c)此方法從指定位置開始將指定集合中的所有元素插入此列表
E get(int index)此方法返回此列表中指定位置的元素
E set(int index, E element)此方法返回此列表中指定位置的元素,并使用參數中的元素進行替換
E remove(int index)此方法返回此列表中指定位置的元素,并刪除此指定位置的元素
boolean remove(Object o)此方法從該列表中刪除指定元素的第一個匹配項(如果存在)
void clear()此方法將從此列表中刪除所有元素
Object clone()此方法返回此ArrayList實例的淺表副本
boolean contains(Object o)如果此列表包含指定的元素,則此方法返回true
boolean isEmpty()如果此列表為空,則此方法返回true
void ensureCapacity(int minCapacity)此方法增加了此列表的容量
int size()此方法返回此列表中的元素數
Object[] toArray()此方法以適當的順序(從第一個元素到最后一個元素)返回包含此列表中所有元素的數組
<T> T[] toArray(T[] a)此方法以適當的順序(從第一個元素到最后一個元素)返回包含此列表中所有元素的數組; 返回數組的運行時類型是指定數組的運行時類型
void trimToSize()此方法將此ArrayList實例的容量修剪為列表的當前大小
void sort(Comparator<? super E> c)此方法對列表內對象,以指定方式進行排序
List<E> subList(int fromIndex, int toIndex)此方法將截取集合的一部分并返回一個List集合

四、相關知識點

1、關于 Array 與 ArrayList 的區別

  • (包含類型)Array 既可以包含基本類型,也可以包含對象類型;而 ArrayList 只能包含對象類型。
  • (實例聲明)Array 作為變量在聲明的時必須進行實例化(至少得初始化數組的大小),而 ArrayList 可以只是先聲明。
  • (初始大小)Array 對象創建后的數組大小是固定的,而 ArrayList 的大小可以動態指定,也就是說該對象的空間可以任意增加。
  • (方法特性)Arraylist 提供了更多的方法和特性,比如添加全部addAll(),刪除全部removeAll(),返回迭代器iterator()等等。

2、關于 CopyOnWriteArrayList

Java 并發包中的并發 List 只有 CopyOnWriteArrayList。CopyOnWriteArrayList 是一個線程安全的 ArrayList,對其進行的修改操作都是在底層的一個復制數組(快照)上進行的,也就是使用了寫時復制策略。

寫時復制(CopyOnWrite,簡稱 COW)思想是計算機程序涉及領域中的一種優化策略。其核心思想是,如果多個調用者(Callers)同時要求相同的資源(如內存或者磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者視圖修改資源內容時,系統才會真正復制一份專用的副本給調用者,而其他調用者所見到的最初的資源仍然保持不變。這個過程對其他調用者都是透明的。樣做的好處就是可以對 CopyOnWrite 容器進行并發的讀而不需要加鎖,因為當前容器不會被修改。

從 Jdk1.5 開始 Java 并發包里提供了兩個使用 CopyOnWrite 機制實現的并發容器,它們是 CopyOnWriteArrayList 和 CopyOnWriteArraySet。

CopyOnWriteArrayList 中 add 方法添加的時候是需要加鎖的,保證同步,避免了多線程寫的時候復制出多個副本。讀的時候不需要加鎖,如果讀的時候有其他線程正在向 CopyOnWriteArrayList 添加數據,還是可以讀到舊的數據。

寫時復制的缺點:

  • 內存占用問題。由于 CopyOnWrite 的寫時復制機制,在進行寫操作的時候,內存里會同時駐扎兩個對象的內存。
  • CopyOnWrite 容器不能保證數據的實時一致性,可能讀取到舊數據。

3、關于 Fail Fast

Fail Fast 是 Java 集合的一種錯誤機制。當多個線程對同一個集合進行操作時,就有可能會產生 fast-fail 事件。例如:當線程 A 正通過 iterator 遍歷集合,另一個線程 B 修改了集合的內容,此時 modCount(記錄集合操作過程的修改次數)會加 1,不等于 expectedModCount,那么線程 A 訪問集合的時候,就會拋出 Concurrent Modification Exception,產生 fast-fail 事件。邊遍歷邊修改集合也會產生 fast-fail 事件。

解決方法:

  • 使用 Colletions.synchronizedList 方法或在修改集合內容的地方加上 synchronized。這樣的話,增刪集合內容的同步鎖會阻塞遍歷操作,缺點是會影響性能。
  • 使用 CopyOnWriteArrayList 來替換 ArrayList。在對 CopyOnWriteArrayList 進行修改操作的時候,會拷貝一個新的數組,對新的數組進行操作,操作完成后再把引用移到新的數組。

4、關于 Fail Safe

Fail Safe 也是 Java 集合的一種機制,采用安全失敗機制的集合容器(Eg:CopyOnWriteArrayList)在遍歷時不是直接在集合內容上訪問的,而是先復制原有集合內容,在拷貝的集合上進行遍歷。

  • 原理:由于迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改并不能被迭代器檢測到,所以不會觸發 Concurrent Modification Exception
  • 缺點:基于拷貝內容的優點是避免了 Concurrent Modification Exception,但同樣地,迭代器并不能訪問到修改后的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。

Ps:java.util.concurrent 包下的容器都是 Fail Safe 的,可以在多線程下并發使用,并發修改。

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

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

相關文章

Java集合:關于 LinkedList 的內容盤點

本篇內容包括&#xff1a;LinkedList 的概述、LinkedList 的結構既雙向鏈表實現與LinkedList-Node 結構、LinkedList 的使用&#xff08;構造方法&常用方法&#xff09;、關于 Queue 隊列的介紹、關于 ArrayList 和 LinkedList 的區別以及算法&#xff1a;翻轉鏈表&#xf…

shell自動化巡檢

#!/bin/bash #主機信息每日巡檢IPADDR$(ifconfig eth0|grep inet addr|awk -F [ :] {print $13}) #環境變量PATH沒設好&#xff0c;在cron里執行時有很多命令會找不到 export PATH/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin source /etc/profile…

Java集合:關于 Vector 的內容盤點

Vector 與 ArrayList 一樣&#xff0c;也是通過數組實現的&#xff0c;不同的是它支持線程的同步&#xff0c;即某一時刻只有一個線程能夠寫 Vector&#xff0c;避免多線程同時寫而引起的不一致性&#xff0c;但實現同步需要很高的花費&#xff0c;因此&#xff0c;訪問它比訪問…

memcached 的基本命令

memcached 的基本命令(安裝、卸載、啟動、配置相關)&#xff1a; -p 監聽的端口 -l 連接的 IP 地址, 默認是本機 -d start 啟動 memcached 服務 -d restart 重起 memcached 服務 -d stop|shutdown 關閉正在運行的 memcached 服務 -d install 安裝 memcached 服務 -d uninstall …

Java集合:關于 HashSet 的內容盤點

哈希表存放的是哈希值&#xff0c; HashSet 存儲元素的順序并不是按照存入時的順序&#xff08;和 List 顯然不同&#xff09; 而是按照哈希值來存的所以取數據也是按照哈希值取得。 &#xff5e; 本篇內容包括&#xff1a;HashSet 概述、HashSet 與 HashMap 的關系以及HashSet…

mysql備份腳本

#!/bin/bash #保留備份個數&#xff0c;會刪除時間較早的.dump備份 number3 #設置備份保存路徑&#xff0c;yourpath替換成自己的備份保存路徑 backup_diryourpath #日期格式 dddate %Y%m%d #備份工具 toolmysqldump #數據庫用戶名 usernameroot #數據庫密碼&#xff0c;由于密…

Java集合:關于 TreeSet 的內容盤點

TreeSet() 是使用二叉樹的原理對新 add() 的對象按照指定的順序排序&#xff08;升序、降序&#xff09;&#xff0c;每增加一個對象都會進行排序&#xff0c;將對象插入的二叉樹指定的位置&#xff1b; ~ 本篇內容包括&#xff1a;TreeSet 概述、TreeSet 的使用以及其他知識點…

python求素數

口求100內的素數 -個數能被從2開始到自己的平發根的正整數整數整除,就是合數 import math n100 for X in range(2, n): for i in range(2, math.ceil(math.sqrt(x))): if x %i 0: break else: print(x)口求100內的素數 合數一定可以分解為幾個質數的乘積 import math n100 pri…

svn鉤子腳本

REP0S"$1" REV"$2"export LANGen_US.UTF-8 LOGPATH"/app/log" [ !-d ${LOGPATH}] && mkdir $[LOGPATH) -p #update content from svn↓14 SVN/usr/bin/svn↓ SVN update --username test --password test /data/ if[ $? -eq ] then /us…

shell判斷字符串是否為數字

#1.組合語法判斷1: [ -n "echo $num|sed s/[0-9]//g" -a -n "echo $2|sed s/[0-9]//g"] &&\echo”兩個參數都必須為數字”&& exit 1#2.組合語法判斷2:[ -n "echo $num|sed s/[0-9]//g" -a -n "echo $2|sed s/[0-9]//g&…

MySQL:DQL 數據查詢語句盤點

本篇內容包括&#xff1a;DQL 的簡介、SELECT 語句、WHERE 條件語句、JOIN 連接查詢(多表查詢)和分組、過濾、排序、分頁、子查詢的使用。 一、DQL 簡介 DQL&#xff08;Data QueryLanguage&#xff09;語句&#xff0c;即數據查詢語句 常用的語句關鍵字有&#xff1a;SELECT…

MySQL:DML 數據操作語句盤點

本篇內容包括&#xff1a;DML 的簡介、INSERT 命令、UPDATE 命令、DELETE 命令以及 TRUNCATE 命令的使用。 一、DML 簡介 DML&#xff08;Data Manipulation Language&#xff09;語句&#xff0c;即數據操作語句&#xff0c;用于操作數據庫對象中所包含的數據。 常用關鍵字包…

MySQL:DDL 數據定義語句盤點

本篇內容包括&#xff1a;DDL 的簡介、SHOW 查看語句、CREATE 創建語句、ALTER 修改語句以及 DROP 刪除語句的使用。 一、DDL 簡介 DDL&#xff08;Data Definition Language&#xff09;&#xff0c;即數據定義語句&#xff0c;功能就是定義數據庫DATabase、表table、索引ind…

MySQL:DCL 數據控制語句盤點

本篇內容包括:DCL 簡介、GRANT、REVOKE、COMMIT、ROLLBACK、SAVEPOINT、LOCK命令的使用。 一、DCL 簡介 DCL&#xff08;Data Control Language&#xff09;語句&#xff0c;即數據控制語句&#xff0c;用于設置或更改數據庫用戶或角色權限的語句 常用關鍵字包括&#xff1a;…

oracle遷移父子數據

現有需求如下&#xff0c;業務組織單元表中id字段數據在另外一個系統全部重復&#xff0c;但需要將此業務單元組織導入另一系統 業務組織單元表Isc_Specialorg_Unit 表中存在ID字段為子節點數據&#xff0c;parent_id為父節點數據&#xff0c;orgpath為組織路徑 現在做如下操…

批量更新數據庫數據

"update isc22.isc_user t set t.saphrid "&E1&"where t.id "&B1&";"

oracle控制文件

控制文件是數據庫里面非常重要的一類文件,它記錄了當前實例連接的數據庫的結構和行為&#xff0c;并維護數據庫的一致性。 初始化參數文件中描述其位置&#xff0c;很小的:二進制文件,一般不要超過100mmount讀open一直在用 控制文件只能連接一個database丟失要恢復 …

oracle表空間

概念 表空間和數據文件 ●表空間是邏輯存儲概念&#xff0c;一個表空間是一個或多個數據文件的邏輯集合 ●存儲對象(表、索引)邏輯的存儲在表空間上&#xff0c;而存儲對象的數據物理的存放在數據文件上 ●數據庫至少需要一個叫做system的表空間&#xff0c;也就是系統表空間 ●…

oracle日志

日志分類 redo log files聯機日志或重做日志 archived log files歸檔日志 1184198alert log files 告警日志 trace files user_ _dump_ _dest 用戶信息日志如跟蹤會話日志 background dump_ dest進程日志還有其他一-些不常用的日志 v$database的log_mode 數據庫歸檔模式…

MySQL:分庫分表知識點盤點

本篇內容包括&#xff1a;數據庫瓶頸、分庫分表以及分庫分表相關問題 一、數據庫瓶頸 不管是IO瓶頸&#xff0c;還是CPU瓶頸&#xff0c;最終都會導致數據庫的活躍連接數增加&#xff0c;進而逼近甚至達到數據庫可承載活躍連接數的閾值。在業務Service來看就是&#xff0c;可用…