我第一次认真研究“顺序表”,其实是吃了面试的亏。对方问“ArrayList 的 add(int index, E element) 底层到底做了什么”,我张口就想说“就移动一下元素嘛”,结果追问到扩容系数、System.arraycopy 为什么快、头插尾插复杂度,直接露馅。这些年带过不少实习生,发现大家普遍卡在同一个地方:数组用得很熟,但一旦涉及“顺序表”这个概念、各种方法的底层实现,就又开始发虚。这篇就好好聊透顺序表的定义和常用方法,顺便把索引表的顺序查找、顺序表和链表的选型这些高频考点一并讲清楚,适合刚学数据结构的人,也适合要面 Java 后端岗位的同学拿来当复习索引。
1. 顺序表定义:本质还是一块连续内存,关键看你怎么用
1.1 线性表、顺序存储两个词拆开看
数据结构教材给的定义很简洁:顺序表(Sequential List)是线性表的一种存储实现方式,用一组地址连续的存储单元依次存放线性表中的数据元素。这句话里真正要紧的是两个限定词——线性表、顺序存储。
线性表强调元素之间是一对一的前后关系,每个元素(除了头尾)有且只有一个直接前驱和一个直接后继。这个逻辑关系必须被完整保留下来。顺序存储则决定了物理实现:元素在内存里一个挨着一个排,第 i 个元素的存储位置,可以由起始地址直接算出来:Loc(ai) = Loc(a0) + i * sizeof(E)。这个公式看着简单,但它就是顺序表一切特性的根源——随机访问是 O(1),插入删除是 O(n),全由它决定。
我当年听老师用储物柜打比方,到现在还觉得特别贴切:顺序表就是一条标好编号的连续储物柜,你想取第 5 个格子里的东西,直接走过去就行,不用从头数;但如果你要在第 3 格和第 4 格之间硬塞一件东西,对不起,后面所有东西都得挨个挪一挪。挪一次无所谓,要是数据量到了百万级还频繁中间插入,那性能就是灾难。
1.2 顺序表和普通数组到底差在哪
很多人会脱口而出:顺序表不就是数组吗?这句话只对了一半。数组是语言层面给你的连续内存语法,而顺序表是基于数组这种存储结构实现出来的一种抽象数据类型。换句话说,数组是“原材料”,顺序表是“加工后的产品”。
普通数组的硬伤有三点:第一,长度固定,装满了只能自己 new 一个更大的数组,然后手动拷贝旧数据,这个逻辑每用一次就要重写一遍;第二,插入和删除时元素搬移的循环,散落在业务代码里,又丑又容易越界;第三,没有任何边界保护,arr[len] 一越界,程序直接崩给你看。
顺序表把这些脏活全部封装进 add、remove、get 这些方法内部,外面的人只关心“往哪里插、删哪个、取哪个”,不需要操心底层空间怎么腾。Java 里的 ArrayList 就是这个思路最成熟的产品级实现。所以面试如果只回答“顺序表就是数组”,基本会被判定为理解停留在表面;准确的说法是:顺序表是用连续存储的方式实现线性表,数组只是它的地基。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 手写顺序表:把各种方法逐行拆开讲
2.1 类框架:泛型、数组、size 三者缺一不可
先上一份完整的简化版实现,后面所有细节都围绕它展开:
java复制public class SeqList<E> {
private Object[] data; // 底层存储数组
private int size; // 当前元素个数
private static final int DEFAULT_CAPACITY = 10;
public SeqList() {
this.data = new Object[DEFAULT_CAPACITY];
this.size = 0;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
private void checkIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("index: " + index + ", size: " + size);
}
}
private void grow(int minCapacity) {
int oldCapacity = data.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5 倍
if (newCapacity < minCapacity) {
