Java集合源码--ArrayList的可视化操作过程
<blockquote><p>关于ArrayList的元素插入、检索、修改、删除、扩容等可视化操作过程</p>
<p>还有关于ArrayList的迭代器、线程安全和时间复杂度</p>
</blockquote>
<h2 id="1-底层数据结构">📝1. 底层数据结构</h2>
<p>基于动态数组实现,内部维护一个<code>Object[]</code>数组。本质是<strong>数组数据结构</strong>,底层通过拷贝扩容使得数组具备了动态增大的特性。</p>
<p><strong>数组</strong>所具备的一些<strong>特性</strong>,<code>ArrayList</code>也同样具备,比如、插入元素的有序性、访问元素的地址计算等。<code>ArrayList</code>与普通数组的本质区别就在于它的动态扩容特性。</p>
<p><strong>集合内可以保存什么类型元素?保存的是什么?</strong> 这点必须明确知道<strong>集合必须保存引用类型</strong>的元素,对于基本类型是无法保存的,比如、int、long类型,但可以保持对应的基本类型的封装类,比如,<code>Integer</code>、<code>Long</code>。集合内保存的是<strong>对象的引用</strong>,而非对象本身。</p>
<h3 id="11-arraylist的特性">1.1. ArrayList的特性</h3>
<p>有底层数据结构所决定的特性</p>
<ul>
<li>
<p>插入元素的有序性,而非排序,不会自动根据值排序;</p>
</li>
<li>
<p>元素访问:通过数组“<strong>首地址+下标</strong>”来计算元素的存储地址,再通过元素地址<strong>直接访问</strong>,时间复杂度都是O(1);</p>
</li>
<li>
<p><strong>数组</strong>一但申请空间就确定<strong>不可变</strong>,所以ArrayList需要在添加元素操作时,实现<strong>自动扩容</strong>。</p>
</li>
</ul>
<h3 id="12-如何设计的数据结构">1.2. 如何设计的数据结构</h3>
<p>以下是<code>ArrayList</code>类结构与字段</p>
<pre><code class="language-java">public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable {
private static final long serialVersionUID = 8683452581122892189L;
/** 默认初始容量 */
private static final int DEFAULT_CAPACITY = 10;
/** 空实例数组(无延迟扩容) */
private static final Object[] EMPTY_ELEMENTDATA = {};
/** 默认构造后首次扩容使用的空数组 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/** 阈值:最大数组大小 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/** 真正存储元素的数组;构造时或赋予 EMPTY_* 共享数组 */
transient Object[] elementData;
/** 当前元素个数 */
private int size;
/** 用于 Fail-Fast 的修改计数器 */
protected transient int modCount;
// …
}
</code></pre>
<p>真正存储元素的成员变量<code>Object[] elementData</code>和保存数组大小的<code>size</code>,其它字段多半服务于<strong>动态扩容</strong>。</p>
<p><img src="https://img2024.cnblogs.com/blog/1209017/202506/1209017-20250608124810760-1826254169.jpg" alt="image" loading="lazy"></p>
<p><code>MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8</code> 为什么减8?因为数组头需要占用一些空间,8位刚好一个字节,故最小减8,使得ArrayList尽可能存储更多数据,但正常开发不可能保存这么大数据集合,<code>Integer.MAX_VALUE</code>可以保持十亿级了,你减个1024都没问题。</p>
<p><code>modCount</code>用于记录扩容次数,在迭代器中若存在并发修改,则快速失效抛出异常。</p>
<p>我们从本质去学习技术:<strong>集合的作用是什么?</strong></p>
<p>集合的作用是将数据以特定结构<strong>存储</strong>在内存中,并且方便开发者进行<strong>操作</strong>。</p>
<ul>
<li>
<p><strong>存储</strong>:开辟内存空间,写入数据;在Java语言中无需开发者手动申请内存空间,只需要关注数据写入即可;</p>
</li>
<li>
<p><strong>操作</strong>:无非就是<strong>增删查改</strong>,只不过现在操作的是内存中的数据罢了。</p>
</li>
</ul>
<h2 id="2-元素插入增">🚀2. 元素插入(增)</h2>
<p>方法分类</p>
<p><code>ArrayList</code> 的 <code>add</code> 方法有两个重载版本,对应不同的用法:</p>
<ul>
<li>
<p><code>add(E e)</code> —— <strong>在数组末尾插入</strong>;</p>
</li>
<li>
<p><code>add(int index, E element)</code> —— <strong>在指定索引下标插入</strong>。</p>
</li>
</ul>
<h3 id="21-在数组末尾插入">2.1. 在数组末尾插入</h3>
<p>使用方法 <code>add(E e)</code>,以下是jdk8的源码</p>
<pre><code class="language-java">public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData = e;
return true;
}
</code></pre>
<p>插入数据的过程</p>
<ol>
<li>
<p>调用 <code>ensureCapacityInternal</code> 确保数组足够大;</p>
</li>
<li>
<p>将元素写入 <code>elementData</code>;</p>
</li>
<li>
<p><code>size++</code> 并返回。</p>
</li>
</ol>
<p>可视化感受下:无参构造创建ArrayList集合,然后插入五个元素,首次add需要扩容数组为10(详细的扩容流程看后面章节),效果如图</p>
<p><img src="https://img2024.cnblogs.com/blog/1209017/202506/1209017-20250608124826160-1842514666.gif" alt="image" loading="lazy"></p>
<h3 id="22-指定索引下标插入">2.2. 指定索引下标插入</h3>
<p>使用方法: <code>add(int index, E element)</code></p>
<pre><code class="language-java">public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);// Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData = element;
size++;
}
</code></pre>
<p>插入数据的过程</p>
<ol>
<li>
<p>检查是否越界;</p>
</li>
<li>
<p>调用 <code>ensureCapacityInternal</code> 确保数组足够大;</p>
</li>
<li>
<p>将坐标<code>index</code>后的元素都往后移动一位;</p>
</li>
<li>
<p>将元素写入 <code>elementData</code>;</p>
</li>
<li>
<p><code>size++</code> 。</p>
</li>
</ol>
<p>演示在索引下标插入元素,效果如图:</p>
<p><img src="https://img2024.cnblogs.com/blog/1209017/202506/1209017-20250608124841367-1862326572.gif" alt="image" loading="lazy"></p>
<h3 id="23-ensurecapacityinternal-与-grow-扩容流程">2.3. ensureCapacityInternal 与 grow 扩容流程</h3>
<p>为了好查阅源码,简单调整了下,jdk源码基本如下</p>
<pre><code class="language-java">private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 延迟初始化:第一次调用 ensureCapacity 时,将容量至少设置为 DEFAULT_CAPACITY(10)
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 用于迭代时快速失败
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 旧容量
int oldCapacity = elementData.length;
// 新容量 = 旧容量 + 旧容量>>1 (1.5 倍扩容)
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 最大容量检查(防止溢出)
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
</code></pre>
<ul>
<li>
<p><strong>延迟初始化</strong>:无参构造后 <code>elementData</code> 引用一个长度为 0 的共享常量数组。</p>
</li>
<li>
<p><strong>modCount</strong>:每次结构修改(如扩容、增删)都会自增,配合迭代器检查并发修改。</p>
</li>
<li>
<p><strong>扩容策略</strong>:<code>oldCapacity + (oldCapacity >> 1)</code>,右移一位等于除于2,所以<code>newCapacity</code>为原来的1.5 倍,以权衡空间和拷贝成本。</p>
</li>
</ul>
<p><code>ArrayList</code>如果不指定大小初始大小为0,首次<code>add</code>才进行首次扩容,扩容大小为10,这个默认的初始容量<code>DEFAULT_CAPACITY</code>在首层插入数据才会使用到。故此,在创建<code>ArrayList</code>时最好指定大小,最佳情况是创建时就知道集合的大小。</p>
<p>详细可见构造方法源码</p>
<pre><code class="language-java">public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 指定初始容量大于0时
this.elementData = new Object;
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
// 无参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
</code></pre>
<p>扩容可视化过程:插入第11个元素的扩容过程</p>
<p><img src="https://img2024.cnblogs.com/blog/1209017/202506/1209017-20250608124859776-183520420.gif" alt="image" loading="lazy"></p>
<h3 id="24-扩容时才对modcount-自增合理吗">2.4. 扩容时才对modCount 自增合理吗?</h3>
<p>不合理。因为你插入新的数据没有扩容的情况下,集合申请的内存空间不变,但是集合保存元素的大小发生了变化,这和移除元素一样,集合也是发生了变化的,所以在后续的jdk版本中,<code>add</code>操作加入了<code>modCount++;</code>。</p>
<p>以下是jdk11的<code>add</code>源码:首行就对<code>modCount</code>做了自增</p>
<pre><code class="language-java">public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
</code></pre>
<h3 id="25-允许null和可重复插入">2.5. 允许null和可重复插入?</h3>
<p><code>ArrayList</code>集合内保存的是<strong>对象的引用</strong>,在Java语言中,引用是可以指向null的,故<code>ArrayList</code>集合可以保存null。在元素插入时并没有对元素进行重复检查,故可以保存重复数据,包括重复的null。</p>
<p><code>List</code> 接口在 javadoc 里就明确说了:</p>
<blockquote>
<p>允许所有元素(包括 <code>null</code>),允许重复插入;<br>
如果你需要“元素唯一”或“禁止空值”,Java 提供了其他集合类型(如实现了 <code>Set</code> 接口的 <code>HashSet</code>/<code>LinkedHashSet</code>/<code>TreeSet</code>,或者在 Java 9+ 可以用 <code>List.of(...)</code> 构造的不可空、不可变的列表)</p>
</blockquote>
<p><strong>实现简单高效</strong><br>
<code>ArrayList</code> 底层用一块连续的 <code>Object[]</code> 数组存储元素:</p>
<ul>
<li>
<p>插入 <code>null</code> 只不过是往数组里赋一个 <code>null</code>,跟存任何其他对象没区别;</p>
</li>
<li>
<p>重复插入只是把同一个引用赋给不同索引,也没有额外开销;<br>
如果强行在 <code>add()</code> 里做“非空检查”或“去重”,不仅会增加每次插入的开销,还会破坏它作为通用、轻量列表的设计初衷。</p>
</li>
</ul>
<h2 id="3-修改元素改">📌3. 修改元素(改)</h2>
<p>根据指定索引进行覆盖<code>E set(int index, E element)</code></p>
<pre><code class="language-java">public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData = element;
return oldValue;
}
</code></pre>
<p>这个很简单,检查是否越界,暂存旧值,覆盖数组对应下标的值,返回旧值。</p>
<p>修改索引元素可视化过程:</p>
<p><img src="https://img2024.cnblogs.com/blog/1209017/202506/1209017-20250608124757138-817042345.gif" alt="image" loading="lazy"></p>
<h2 id="4-移除元素删">📍4. 移除元素(删)</h2>
<p>方法分类</p>
<p><code>ArrayList</code> 的 <code>remove</code> 方法有两个重载版本,对应不同的用法:</p>
<ol>
<li>
<p><code>remove(int index)</code> —— <strong>按索引删除</strong>;</p>
</li>
<li>
<p><code>remove(Object o)</code> —— <strong>按对象值删除</strong>。</p>
</li>
</ol>
<h3 id="41-指定索引删除">4.1. 指定索引删除</h3>
<p>使用到的方法:<code>remove(int index)</code></p>
<pre><code class="language-java">public E remove(int index) {
rangeCheck(index); // 检查索引是否合法
modCount++; // 修改次数+1,支持 fail-fast
E oldValue = elementData(index); // 获取旧值
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
</code></pre>
<p><strong>分析</strong>:</p>
<ul>
<li>
<p>使用 <code>System.arraycopy()</code> 将后面所有元素向前移动一位;</p>
</li>
<li>
<p>最坏情况是删除索引 0,移动 n-1 个元素;</p>
</li>
<li>
<p>修改 size,清除最后一个元素引用。</p>
</li>
</ul>
<p>删除索引元素的可视化过程:</p>
<p><img src="https://img2024.cnblogs.com/blog/1209017/202506/1209017-20250608124915189-1908897366.gif" alt="image" loading="lazy"></p>
<h3 id="42-按照对象值删除">4.2. 按照对象值删除</h3>
<p>使用到的方法:<code>remove(Object o)</code></p>
<pre><code class="language-java">public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData)) {
fastRemove(index);
return true;
}
}
return false;
}
</code></pre>
<p>配套的 <code>fastRemove(int index)</code> 实现如下:</p>
<pre><code class="language-java">private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
elementData[--size] = null;
}
</code></pre>
<p><strong>分析</strong>:</p>
<ul>
<li>
<p>先线性查找目标元素,最多比较 <code>n</code> 次;</p>
</li>
<li>
<p>然后移除元素,最多移动 <code>n-1</code> 个;</p>
</li>
<li>
<p>所以总操作是一次“<strong>线性查找 + 线性移动</strong>”。</p>
</li>
</ul>
<p>避免在大列表中频繁删除中间元素(尤其在循环中删除),否则容易退化为 O(n²)。</p>
<p>对比按照索引删除,多了一步<strong>元素查找对比</strong>,其它基本一致。</p>
<p>按照对象值删除元素的可视化过程:</p>
<p><img src="https://img2024.cnblogs.com/blog/1209017/202506/1209017-20250608124933157-80637064.gif" alt="image" loading="lazy"></p>
<h2 id="5-获取和检索元素查">🎯5. 获取和检索元素(查)</h2>
<h3 id="51-获取元素">5.1. 获取元素</h3>
<p>根据索引获取元素:<code>get(int index)</code></p>
<pre><code class="language-java">public E get(int index) {
rangeCheck(index);
return elementData(index);
}
</code></pre>
<p>检查是否越界,然后根据下标索引获取元素。</p>
<h3 id="52-检索元素">5.2. 检索元素</h3>
<p>在 <code>ArrayList</code> 中,<strong>检索某个元素</strong>(不是通过索引,而是查找某个值是否存在,或其位置)主要通过以下两个方法完成:</p>
<h4 id="1-判断是否包含某个元素">1) 判断是否包含某个元素</h4>
<p>根据对象值判断集合中是否存在:<code>contains(Object o)</code></p>
<pre><code class="language-java">public boolean contains(Object o) {
return indexOf(o) >= 0;
}
</code></pre>
<p>这个方法内部调用了 <code>indexOf</code> 方法。它返回一个布尔值,表示某个元素是否存在于列表中。</p>
<h4 id="2-返回元素首次出现的索引">2) 返回元素首次出现的索引</h4>
<p>根据对象值检索首次出现的位置:<code>indexOf(Object o)</code></p>
<p>跟按照对象值删除的检索过程一致,可查看上面的可视化过程动图。</p>
<pre><code class="language-java">public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData == null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData))
return i;
}
return -1;
}
</code></pre>
<p>如果查找的是 <code>null</code>,就用 <code>==</code> 比较;如果是非 <code>null</code> 对象,则用 <code>equals()</code> 方法逐个比较。从头遍历,找到第一个匹配项的索引。</p>
<h4 id="3-返回元素最后一次出现的索引">3) 返回元素最后一次出现的索引</h4>
<p>根据对象值检索最后出现的位置:<code>lastIndexOf(Object o)</code></p>
<pre><code class="language-java">public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size - 1; i >= 0; i--)
if (elementData == null)
return i;
} else {
for (int i = size - 1; i >= 0; i--)
if (o.equals(elementData))
return i;
}
return -1;
}
</code></pre>
<p>与 <code>indexOf</code> 类似,但从<strong>尾部开始</strong>向前查找。</p>
<h2 id="6-arraylist-的迭代器iterator">6. ArrayList 的迭代器(Iterator)</h2>
<h3 id="61-什么是迭代器">6.1. 什么是迭代器?</h3>
<p>迭代器(<code>Iterator</code>)是 Java 集合框架中用于遍历集合元素的工具。<br>
<code>ArrayList</code> 提供了两种主要的迭代方式:</p>
<ul>
<li>
<p><code>Iterator<E></code>:基础的迭代器接口(只支持单向遍历)</p>
</li>
<li>
<p><code>ListIterator<E></code>:是 <code>Iterator</code> 的子接口,<strong>支持双向遍历、修改元素、获取索引等高级功能</strong></p>
</li>
</ul>
<h3 id="62-iterator迭代器">6.2. iterator迭代器</h3>
<p>源码(位于 <code>ArrayList.java</code>):</p>
<pre><code class="language-java">public Iterator<E> iterator() {
return new Itr();
}
</code></pre>
<p>这会返回一个内部类 <code>Itr</code> 的实例。</p>
<h4 id="1-itr-内部类的核心源码简化版">1) Itr 内部类的核心源码(简化版):</h4>
<pre><code class="language-java">private class Itr implements Iterator<E> {
int cursor = 0; // 下一个要返回的元素索引
int lastRet = -1; // 上一个返回的元素索引,若没有则为 -1
int expectedModCount = modCount;// 用于检测并发修改
public boolean hasNext() {
return cursor != size;
}
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
cursor = i + 1;
return (E) elementData;
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
</code></pre>
<h4 id="2-modcount-与并发修改检查fail-fast">2) <code>modCount</code> 与并发修改检查(fail-fast)</h4>
<ul>
<li>
<p><code>modCount</code> 是 <code>ArrayList</code> 中用于记录结构性修改(如添加、删除元素)次数的字段。</p>
</li>
<li>
<p>迭代器创建时保存了当前的 <code>modCount</code> 到 <code>expectedModCount</code>。</p>
</li>
<li>
<p>如果在迭代期间集合发生结构性修改,<code>modCount != expectedModCount</code>,就会抛出 <code>ConcurrentModificationException</code>。</p>
</li>
</ul>
<p>这就是所谓的 <strong>fail-fast机制</strong>。</p>
<h4 id="3-示例代码">3) 示例代码</h4>
<pre><code class="language-java">List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
</code></pre>
<h3 id="63-listiterator更强大的双向迭代器">6.3. ListIterator更强大的双向迭代器</h3>
<h4 id="1-增强版迭代器简述">1) 增强版迭代器简述</h4>
<p>通过 <code>list.listIterator()</code> 或者<code>listIterator(int index)</code>来获取,本质都是返回<code>new ListItr(index)</code>对象。<code>ListItr</code>是<code>Itr</code>的子类)<code>private class ListItr extends Itr implements ListIterator<E></code>,是增强版迭代器。</p>
<pre><code class="language-java">ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
System.out.println(it.next());
}
while (it.hasPrevious()) {
System.out.println(it.previous());
}
</code></pre>
<p>额外支持:</p>
<ul>
<li>
<p><code>hasPrevious()</code>, <code>previous()</code></p>
</li>
<li>
<p><code>add(E e)</code>, <code>remove()</code>, <code>set(E e)</code></p>
</li>
<li>
<p><code>nextIndex()</code>, <code>previousIndex()</code></p>
</li>
</ul>
<h4 id="2-示例代码">2) 示例代码</h4>
<p>反序遍历案例</p>
<pre><code class="language-java">import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorReverseDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Dog");
list.add("Cat");
list.add("Bird");
list.add("Fish");
System.out.println("原始列表: " + list);
ListIterator<String> iterator = list.listIterator(list.size()); // 从末尾开始
while (iterator.hasPrevious()) {
String animal = iterator.previous();
// 替换元素
if (animal.equals("Cat")) {
iterator.set("Tiger"); // 将 Cat 替换为 Tiger
}
// 删除元素
if (animal.equals("Bird")) {
iterator.remove(); // 删除 Bird
}
// 在 Fish 前插入一个元素
if (animal.equals("Fish")) {
iterator.add("Whale"); // 插入 Whale(在 Fish 之前)
}
}
System.out.println("修改后的列表: " + list);
}
}
</code></pre>
<h3 id="64-时间复杂度">6.4. 时间复杂度</h3>
<ul>
<li>
<p>每个迭代器的 <code>next()</code>、<code>hasNext()</code> 操作时间复杂度都是 <strong>O(1)</strong>。</p>
</li>
<li>
<p>但是若在 <code>remove()</code> 中触发 <code>ArrayList</code> 的 <code>remove(index)</code>,那是 <strong>O(n)</strong>,因为后面的元素要移动。</p>
</li>
</ul>
<h3 id="65-注意事项">6.5. 注意事项</h3>
<ul>
<li>
<p>在使用 <code>for-each</code> 或 <code>iterator</code> 遍历时,<strong>不要直接修改原始集合</strong>(如调用 <code>add()</code>、<code>remove()</code>),否则会抛出 <code>ConcurrentModificationException</code>。</p>
</li>
<li>
<p>如果需要在遍历中安全地修改集合,可以使用 <strong><code>ListIterator</code> 的 <code>remove()</code> 或 <code>add()</code> 方法</strong>,它是支持修改的。</p>
</li>
</ul>
<h2 id="7-线程安全问题">7. 线程安全问题</h2>
<p><code>ArrayList</code>是线程不安全的。</p>
<p>举个例子</p>
<pre><code class="language-java">List<Integer> list = new ArrayList<>();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
list.add(i);// 非线程安全
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终大小: " + list.size()); // 不一定是 2000
</code></pre>
<p>不过只有 <code>ArrayList</code> 作为<strong>共享变量</strong>时,才需要<strong>考虑线程安全问题</strong>,当 <code>ArrayList</code> 集合作为方法内的局部变量时,无需考虑线程安全的问题。</p>
<h3 id="解决安全问题的一些方法">解决安全问题的一些方法</h3>
<p>类注释中推荐我们使用 <code>Collections#synchronizedList</code> 来保证线程安全,SynchronizedList 是通过在每个方法上面加上锁来实现,虽然实现了线程安全,但是性能大大降低。</p>
<p>使用方式</p>
<pre><code class="language-java">List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
// 遍历时手动加锁
synchronized(syncList) {
for (Integer i : syncList) {
// 安全遍历
}
}
</code></pre>
<p>也可以使用这个集合:<code>CopyOnWriteArrayList</code>(推荐用于读多写少)</p>
<pre><code class="language-java">List<String> cowList = new CopyOnWriteArrayList<>();
</code></pre>
<p>特性:</p>
<ul>
<li>
<p>所有写操作(add/remove/set)都会复制一份数组再修改;</p>
</li>
<li>
<p>不会抛出 <code>ConcurrentModificationException</code>;</p>
</li>
<li>
<p>非常适合<strong>读多写少</strong>的场景。</p>
</li>
</ul>
<p>缺点:</p>
<ul>
<li>写操作开销大,性能比 <code>ArrayList</code> 差很多。</li>
</ul>
<h2 id="8-时间复杂度汇总">8. 时间复杂度汇总</h2>
<table>
<thead>
<tr>
<th>操作</th>
<th>时间复杂度</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>插入元素</td>
<td>O(1),扩容<strong>单次</strong>为 O(n)</td>
<td><code>add(E)</code></td>
</tr>
<tr>
<td>随机插入</td>
<td>O(n),元素拷贝</td>
<td><code>add(index,E)</code></td>
</tr>
<tr>
<td>指定索引删除</td>
<td>O(n),指定索引删除</td>
<td><code>remove(index)</code></td>
</tr>
<tr>
<td>指定对象值删除</td>
<td>O(n),查找 + 移动</td>
<td><code>remove(Object)</code></td>
</tr>
<tr>
<td>指定索引修改</td>
<td>O(1)</td>
<td><code>set(index,E)</code></td>
</tr>
<tr>
<td>获取元素</td>
<td>O(1)</td>
<td><code>get(index)</code></td>
</tr>
<tr>
<td>检索元素</td>
<td>都为<code>O(n)</code></td>
<td><code>indexOf(Object)</code>/ <code>lastIndexOf(Object)</code></td>
</tr>
</tbody>
</table>
<h2 id="9-总结">9. 总结</h2>
<p>在使用ArrayList集合时,需要关注以下特性:随机获取/修改快、插入/删除慢、扩容性能问题、并发线程安全问题。</p>
<p>关于元素插入、检索、修改、删除、扩容过程等操作过程,完整的视频链接:https://www.bilibili.com/video/BV1KET2zGEm4/</p>
<p><img src="https://img2024.cnblogs.com/blog/1209017/202506/1209017-20250608124619317-1056953506.gif" alt="image" loading="lazy"></p>
<p>掌握设计模式的两个秘籍</p>
<p>查看往期设计模式文章的:设计模式</p>
<p>超实用的SpringAOP实战之日志记录</p>
<p>2023年下半年软考考试重磅消息</p>
<p>通过软考后却领取不到实体证书?</p>
<p>计算机算法设计与分析(第5版)</p>
<p>Java全栈学习路线、学习资源和面试题一条龙</p>
<p>软考证书=职称证书?</p>
<p>软考中级--软件设计师毫无保留的备考分享</p>
<p>觉得还不错的,三连支持:点赞、分享、推荐↓</p><br><br>
来源:https://www.cnblogs.com/dennyLee2025/p/18919268
頁:
[1]