深入解析 ArrayList
一、线性表与顺序表基础
线性表是数据元素的有限序列,逻辑上连续(如排队),物理存储可连续(数组)或不连续(链表)。常见类型:
- 顺序表(数组实现)
- 链表(指针连接)
- 栈/队列(受限线性表)
顺序表是用连续内存空间存储元素的线性结构,核心特点:
// 顺序表伪代码结构
class SeqList {private int[] array; // 连续存储空间private int size; // 有效元素个数void add(int data) { /* 尾部插入 */ }void remove(int index) { /* 删除需移动后续元素 */ }
}
二、ArrayList 核心特性
作为动态顺序表实现,核心特点:
- 动态扩容:容量不足时自动扩容(默认10→15→22...)
- 快速随机访问:实现
RandomAccess
接口 - 泛型支持:
ArrayList<String>
类型安全 - 非线程安全:多线程需用
Vector
或同步包装
三、ArrayList 使用详解
1. 构造方法
// 示例1:默认构造(初始容量10)
List<Integer> list1 = new ArrayList<>();// 示例2:指定初始容量
List<String> list2 = new ArrayList<>(100); // 示例3:从已有集合构造
List<Integer> list3 = new ArrayList<>(Arrays.asList(1,2,3));
2. 核心操作(附时间复杂度)
方法 | 功能 | 时间复杂度 | 示例 |
---|---|---|---|
add(E e) | 尾部插入 | O(1) | list.add("Java") |
add(index, E) | 指定位置插入 | O(n) | list.add(0, "First") |
get(index) | 获取元素 | O(1) | String s = list.get(0) |
set(index, E) | 修改元素 | O(1) | list.set(1, "Python") |
remove(index) | 按索引删除 | O(n) | list.remove(0) |
remove(Object) | 按元素删除 | O(n) | list.remove("Java") |
contains(Object) | 是否包含 | O(n) | if(list.contains("C++")) |
subList(from, to) | 获取子列表 | O(1) | List sub = list.subList(1,3) |
注意:
subList()
返回视图,修改会影响原列表!
3. 三种遍历方式
List<String> langs = Arrays.asList("Java", "Python", "C++");// 方式1:for+索引(最快)
for(int i=0; i<langs.size(); i++) {System.out.println(langs.get(i));
}// 方式2:foreach
for(String lang : langs) {System.out.println(lang);
}// 方式3:迭代器(统一集合访问)
Iterator<String> it = langs.iterator();
while(it.hasNext()) {System.out.println(it.next());
}
四、动态扩容机制(源码级解析)
扩容触发条件:添加元素时 size + 1 > capacity
// 简化版扩容流程
public boolean add(E e) {ensureCapacityInternal(size + 1); // 1. 检查容量elementData[size++] = e; // 2. 添加元素return true;
}private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(10, minCapacity); // 首次扩容到10}if (minCapacity > elementData.length) {grow(minCapacity); // 需要扩容}
}private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容if (newCapacity < minCapacity) {newCapacity = minCapacity; // 特殊需求扩容}elementData = Arrays.copyOf(elementData, newCapacity); // 数据拷贝
}
扩容规律:
- 初始容量:10
- 第一次扩容:10 → 15(10 + 10/2)
- 第二次扩容:15 → 22(15 + 15/2)
- 最大容量:
Integer.MAX_VALUE - 8
五、实战案例:扑克牌游戏
1. 扑克牌类设计
class Card {public int rank; // 牌面值public String suit; // 花色@Overridepublic String toString() {return "[" + suit + " " + rank + "]";}
}
2. 洗牌算法实现
public class CardGame {private static final String[] SUITS = {"♠", "♥", "♣", "♦"};// 创建一副牌private static List<Card> buyDeck() {List<Card> deck = new ArrayList<>(52);for (int i = 0; i < 4; i++) {for (int j = 1; j <= 13; j++) {Card card = new Card();card.suit = SUITS[i];card.rank = j;deck.add(card);}}return deck;}// 洗牌(Fisher-Yates算法)private static void shuffle(List<Card> deck) {Random random = new Random();for (int i = deck.size() - 1; i > 0; i--) {int j = random.nextInt(i + 1);// 交换两张牌Card temp = deck.get(i);deck.set(i, deck.get(j));deck.set(j, temp);}}public static void main(String[] args) {List<Card> deck = buyDeck();System.out.println("新牌: " + deck);shuffle(deck);System.out.println("洗牌后: " + deck);// 发牌给3个玩家List<List<Card>> players = new ArrayList<>();for(int i=0; i<3; i++) {players.add(new ArrayList<>());}for(int i=0; i<5; i++) {for(int j=0; j<3; j++) {// 从牌堆顶部取牌players.get(j).add(deck.remove(0));}}System.out.println("玩家1手牌: " + players.get(0));System.out.println("玩家2手牌: " + players.get(1));System.out.println("玩家3手牌: " + players.get(2));System.out.println("剩余牌堆: " + deck);}
}
六、ArrayList 的优缺点
优点:
- 随机访问效率高(O(1)时间复杂度)
- 尾部插入效率高(O(1)均摊时间)
- 内存连续存储,CPU缓存友好
缺点:
插入/删除代价高:中间位置操作需移动元素(O(n)时间)
// 在索引2处插入元素
list.add(2, "New");
// 需要移动 [2, size-1] 的所有元素
内存浪费:扩容后可能有闲置空间
- 示例:100容量→扩容到150→实际只用105个→浪费45空间
扩容性能开销:数据拷贝消耗资源
七、使用建议
预分配容量:已知数据量时指定初始容量
尾部操作优先:避免中间插入/删除
替代方案选择:
- 频繁插入删除 →
LinkedList
- 线程安全需求 →
CopyOnWriteArrayList
- 固定大小集合 →
Arrays.asList()
- 频繁插入删除 →