📚 目录
-
[5. 最后代码汇总](#5. 最后代码汇总)
1. 什么是顺序表?
顺序表:物理顺序上连续,逻辑上也连续的存储数据的线性表。(可以理解为一个能随时扩容的数组,连续存储,我们找的时候就非常方便。)一般情况下使用数组来实现顺序表。
这篇文章我们不用 Java 自带的 ArrayList,从零手搓一个顺序表,实现完整的增删改查功能。
[🔙 返回目录](#🔙 返回目录)
2. 顺序表核心设计思路
首先我们得知道一个顺序表有什么属性,有什么功能。
首先我们先实现一个泛型接口:里面有自己写的顺序表的方法。
java
public interface IList<T> {
//默认尾插
void add(T val);
//选择插入
void add(int pos,T val);
//判断是否包含这个元素
boolean contains(T val);
//给想要位置修改别的值
void set(int pos,T val);
//获取当前位置的元素
T get(int pos);
//打印数组内内容
void display();
//清空顺序表
void clear();
}
** 此时我们可以定义一个专门储存常量的类:Constant**
java
public class Constant {
public static final int LIST_LENS_BIG_FIRST = 5;//数组的默认大小
}
** 同时需要一个泛型类来实现ILink 接口,同时我们需要自己定义一个泛型数组**
java
public class MyArrayList<T> implements IList<T> {
private Object[] array;
//数组真实大小
private int size;
public MyArrayList() {
//默认大小为5
this.array = new Object[Constant.LIST_LENS_BIG_FIRST];
}
//-----------------默认尾插
@Override
public void add(T val) {
}
//------------------选择插入
@Override
public void add(int pos, T val) {
}
//------------------判断是否包含这个元素
@Override
public boolean contains(T val) {
return false;
}
//------------------给想要位置修改别的值
@Override
public void set(int pos, T val) {
}
//------------------获取当前位置的元素
@Override
public T get(int pos) {
return null;
}
//------------------打印数组内内容
@Override
public void display() {
}
//------------------//清空顺序表
@Override
public void clear() {
}
//------------------删除指定位置的值
@Override
public void remove(int pos) {
}
}
[🔙 返回目录](#🔙 返回目录)
3. 增删改查的实现
增
添加元素:

我们在添加的时候就要考虑到很多因素:
顺序表满了吗?
顺序表为空吗?
顺序表满了就需要扩容,顺序表为空就让第一个元素为要添加的值
此时我们创建一个专门为add服务的方法:arrayFull和grow方法。
java
public void add(T val) {
if(arrayFull()) {
grow();
}
array[size] = val;
size++;//每次添加都让大小+1
}
//扩容
private void grow() {
array = Arrays.copyOf(array,2*array.length);//2倍扩容
}
//判断当前顺序表是否为满值
private boolean arrayFull() {
return this.size == array.length;
}
顺序表满的时候,就会进入扩容。
** Java官方给的是1.5倍扩容,我们使用2倍扩容。**
** 任意位置添加:**
同理我们需要对数组进行判断是否为满值:如果满了就需要我们进行扩容。
判断下标pos的合法性:如果大于size或者小于0下标都不合法。

我们就需要自定义一个异常:判断下标是否合法,不合法则抛出异常:
java
public class AddposExp extends RuntimeException{
public AddposExp() {
}
public AddposExp(String message) {
super(message);
}
}
判断下标是否合法:
我们需要在常量类中添加:
java
public class Constant {
public static final int LIST_LENS_BIG_FIRST = 5;
public static final String ADD_POS_NOT_Legal = "下标不合法";
}
判断当前位置是否合法:专门用于服务添加的检查。
java
private void posLegal(int pos,String msg) {
if(pos>size||pos<0) {
throw new AddposExp(msg);
}
}
** add任意位置添加实现:**
java
public void add(int pos, T val) {
if(arrayFull()) {
grow();
}
//判断下标是否合法
posLegal(pos,Constant.ADD_POS_NOT_Legal);
//此时下标合法,空间足够
//判断当前位置是否在末尾
if(pos == size) {
array[pos] = val;
size++;
return;
}
//此时就不在尾部,需要将后面的数据移动到后面一格
for (int i = size-1; i >=pos ; i--) {
array[i+1] = array[i];
}
array[pos] = val;
size++;
}
删
** 顺序表中我们使用的方法就是让删除的元素的后面的元素往前盖,最后让顺序表实际大小-1,最后一个元素置为null**
需要在Constant 中添加String SET_POS_NOT_Legal = "设置当前位置下标不合法";
java
public class Constant {
public static final int LIST_LENS_BIG_FIRST = 5;
public static final String ADD_POS_NOT_Legal = "下标不合法";
public static final String REMOVE_POS_NOT_Legal = "删除位置下标不合法";
}
同时也应该有一个专门服务于删改查的方法:防止空指针异常
java
private void checkGetSetRemovePos(int pos, String msg) {
if (pos < 0 || pos >= size) {
throw new RuntimeException(msg);
}
}
java
public void remove(int pos) {
//判断顺序表是否为空
if(array == null||size==0) {
return;
}
//判断下标是否合法 如果不合法抛出移除下标不合法这个异常
checkGetSetRemovePos(pos,Constant.REMOVE_POS_NOT_Legal);//与add的判断方法一致
//让要删除的位置的后面的元素往前盖
for (int i = pos; i <size-1 ; i++) {
array[i] = array[i+1];//让后面的元素往前面覆盖
}
size--;//有效容量-1
array[size] = null;让顺序表最后一个元素置为null
}
改
原理:我们需要判断顺序表是否为空同时判断pos位置的合法性。
同时我们需要在Constant 添加String SET_POS_NOT_Legal = "设置当前位置下标不合法";
java
public class Constant {
public static final int LIST_LENS_BIG_FIRST = 5;
public static final String ADD_POS_NOT_Legal = "下标不合法";
public static final String REMOVE_POS_NOT_Legal = "删除位置下标不合法";
public static final String SET_POS_NOT_Legal = "设置当前位置下标不合法";
}
java
public void set(int pos, T val) {
//判断当前顺序表为不为空
if(array == null || size==0) {
return;
}
//判断当前要修改的位置是否合法
checkGetSetRemovePos(pos,Constant.SET_POS_NOT_Legal);
this.array[pos] = val;
}
查
查找是比较简单的,判断之前需要检查顺序表的合法性;
我们只需要判断要查找的值在不在里面通过equals进行比较(equals可以重写,也可以不重写)。
java
public boolean contains(T val) {
if(array==null||size==0) {
System.out.println("顺序表为空");
return false;
}
//遍历元素查找
for (int i = 0; i < size; i++) {
if (val == null) {
if (array[i] == null) {
return true;
}
} else {
if (val.equals(array[i])) {
return true;
}
}
}
return false;
}
得到指定下标位置的元素
此时我们只需要判断当前下标是否合法,顺序表是否为空,后直接返回即可。
同时我们需要在常量里面定义降低耦合性。
java
public class Constant {
public static final int LIST_LENS_BIG_FIRST = 5;
public static final String ADD_POS_NOT_Legal = "下标不合法";
public static final String REMOVE_POS_NOT_Legal = "删除位置下标不合法";
public static final String SET_POS_NOT_Legal = "设置当前位置下标不合法";
public static final String ARRAY_IS_NULL = "顺序表为空";
public static final String POSITION_IS_ILLEGAL = "下标越界";
}
自定义一个专属于获取的异常:让逻辑更加严谨。
java
public class GetposExp extends RuntimeException{
public GetposExp() {
}
public GetposExp(String message) {
super(message);
}
}
java
public T get(int pos) {
// 1. 先判断数组是否为空
if (array == null) {
throw new GetposExp(Constant.ARRAY_IS_NULL);
}
// 2. 再判断下标是否合法
if (pos < 0 || pos >= size) {
throw new GetposExp(Constant.POSITION_IS_ILLEGAL);
}
return (T) array[pos];
}
清空顺序表
让遍历数组让每一个元素都置为null即可:

java
public void clear() {
if(size==0) {
System.out.println("顺序表为空");
return;
}
for (int i = 0; i < size; i++) {
array[i] = null;
}
}
打印
根据我们有效数组长度遍历数组即可:
java
public void display() {
//判断顺序表为不为空
if(array==null) {
return;
}
for (int i = 0; i < size; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
检查
java
public class Test {
public static void main(String[] args) {
MyArrayList<Integer> list = new MyArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.display(); // 打印
list.add(1, 99);
list.display();
list.remove(1);
list.display();
System.out.println(list.get(0));
list.set(0, 100);
list.display();
System.out.println(list.contains(100));
}
}

[🔙 返回目录](#🔙 返回目录)
4. .顺序表与ArrayList的对比总结
- 相同点
- 底层都是使用数组实现。
- 都支持随机访问,通过下标获取元素的时间复杂度为 O (1)。
- 插入、删除元素时,都需要移动后面的元素,平均时间复杂度为 O (n)。
- 容量满了都会自动扩容,不需要手动管理大小。
- 都属于线性表的顺序存储结构。
- 不同点
- 功能完整度不同
- 我们实现的顺序表:只实现了核心的增删改查、扩容等简单的方法。
- ArrayList:提供了迭代器、子列表、批量操作、序列化等完整功能。
线程安全不同
我们的顺序表:线程不安全。
ArrayList:也是线程不安全,但官方提供了对应的安全包装类。 - 扩容机制不同
我们的顺序表:简单 2 倍扩容或固定扩容。
ArrayList:严格的 1.5 倍扩容,且有大量的边界与溢出保护。 - 泛型与类型安全
我们的顺序表:简单泛型实现。
ArrayList:完整的泛型支持,类型检查更严格。 - 异常处理与健壮性
我们的顺序表:简单的下标判断。
ArrayList:对空指针、下标越界、非法参数都做了极其严格的校验。 - 效率优化
ArrayList 使用了更高效的数组拷贝 System.arraycopy()。
我们使用简单的 for 循环移动元素,效率较低。
[🔙 返回目录](#🔙 返回目录)
1. 最后代码汇总
java
public interface IList<T> {
//默认尾插
void add(T val);
//选择插入
void add(int pos,T val);
//判断是否包含这个元素
boolean contains(T val);
//给想要位置修改别的值
void set(int pos,T val);
//获取当前位置的元素
T get(int pos);
//打印数组内内容
void display();
//清空顺序表
void clear();
void remove(int pos);
}
java
public class MyArrayList<T> implements IList<T> {
private Object[] array;
//数组真实大小
private int size;
public MyArrayList() {
//默认大小为5
this.array = new Object[Constant.LIST_LENS_BIG_FIRST];
}
//-----------------默认尾插
@Override
public void add(T val) {
if(arrayFull()) {
grow();
}
array[size] = val;
size++;
}
//扩容
private void grow() {
array = Arrays.copyOf(array,2*array.length);
}
private boolean arrayFull() {
return this.size == array.length;
}
//------------------选择插入
@Override
public void add(int pos, T val) {
if(arrayFull()) {
grow();
}
//判断下标是否合法
posLegal(pos,Constant.ADD_POS_NOT_Legal);
//此时下标合法,空间足够
//判断当前位置是否在末尾
if(pos == size) {
array[pos] = val;
size++;
return;
}
//此时就不在尾部,需要将后面的数据移动到后面一格
for (int i = size-1; i >=pos ; i--) {
array[i+1] = array[i];
}
array[pos] = val;
size++;
}
private void posLegal(int pos,String msg) {
if(pos>size||pos<0) {
throw new AddposExp(msg);
}
}
private void checkGetSetRemovePos(int pos, String msg) {
if (pos < 0 || pos >= size) {
throw new RuntimeException(msg);
}
}
//------------------判断是否包含这个元素
@Override
public boolean contains(T val) {
if(array==null||size==0) {
System.out.println("顺序表为空");
return false;
}
//遍历元素查找
for (int i = 0; i < size; i++) {
if (val == null) {
if (array[i] == null) {
return true;
}
} else {
if (val.equals(array[i])) {
return true;
}
}
}
return false;
}
//------------------给想要位置修改别的值
@Override
public void set(int pos, T val) {
//判断当前顺序表为不为空
if(array == null || size==0) {
return;
}
//判断当前要修改的位置是否合法
checkGetSetRemovePos(pos,Constant.SET_POS_NOT_Legal);
this.array[pos] = val;
}
//------------------获取当前位置的元素
@Override
public T get(int pos) {
// 1. 先判断数组是否为空
if (array == null) {
throw new GetposExp(Constant.ARRAY_IS_NULL);
}
// 2. 再判断下标是否合法
if (pos < 0 || pos >= size) {
throw new GetposExp(Constant.POSITION_IS_ILLEGAL);
}
return (T) array[pos];
}
//------------------打印数组内内容
@Override
public void display() {
//判断顺序表为不为空
if(array==null) {
return;
}
for (int i = 0; i < size; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
//------------------//清空顺序表
@Override
public void clear() {
if(size==0) {
System.out.println("顺序表为空");
return;
}
for (int i = 0; i < size; i++) {
array[i] = null;
}
}
//------------------删除指定位置的值
@Override
public void remove(int pos) {
//判断顺序表是否为空
if(array == null||size==0) {
return;
}
//判断下标是否合法
checkGetSetRemovePos(pos,Constant.REMOVE_POS_NOT_Legal);
//让要删除的位置的后面的元素往前盖
for (int i = pos; i <size-1 ; i++) {
array[i] = array[i+1];
}
size--;
array[size] = null;
}
}
java
public class GetposExp extends RuntimeException{
public GetposExp() {
}
public GetposExp(String message) {
super(message);
}
}
java
public class AddposExp extends RuntimeException{
public AddposExp() {
}
public AddposExp(String message) {
super(message);
}
}
java
public class Constant {
public static final int LIST_LENS_BIG_FIRST = 5;
public static final String ADD_POS_NOT_Legal = "下标不合法";
public static final String REMOVE_POS_NOT_Legal = "删除位置下标不合法";
public static final String SET_POS_NOT_Legal = "设置当前位置下标不合法";
public static final String ARRAY_IS_NULL = "顺序表为空";
public static final String POSITION_IS_ILLEGAL = "下标越界";
}
通过这次手写顺序表,我们不仅搞懂了顺序表的底层原理,也掌握了线性表的核心思想:连续存储、随机访问、扩容与元素移动。后续我们还会一起手写链表、栈、队列,继续深挖数据结构~
[🔙 返回目录](#🔙 返回目录)