Java手搓数据结构:从零模拟实现顺序表增删改查

📚 目录

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的对比总结

  1. 相同点
  • 底层都是使用数组实现。
  • 都支持随机访问,通过下标获取元素的时间复杂度为 O (1)。
  • 插入、删除元素时,都需要移动后面的元素,平均时间复杂度为 O (n)。
  • 容量满了都会自动扩容,不需要手动管理大小。
  • 都属于线性表的顺序存储结构。
  1. 不同点
  • 功能完整度不同
  • 我们实现的顺序表:只实现了核心的增删改查、扩容等简单的方法。
  • 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 = "下标越界";
}
复制代码
通过这次手写顺序表,我们不仅搞懂了顺序表的底层原理,也掌握了线性表的核心思想:连续存储、随机访问、扩容与元素移动。后续我们还会一起手写链表、栈、队列,继续深挖数据结构~

[🔙 返回目录](#🔙 返回目录)


相关推荐
成为你的宁宁2 小时前
【Kubernetes Secret 安全配置指南:从创建配置到环境变量、数据卷使用及私有镜像仓库实践】
java·安全·kubernetes
没有羊的王K2 小时前
机器学习指标解析:AUC与KS值
开发语言·python
千江明月2 小时前
Ollama安装的详细步骤以及Python调用Qwen
开发语言·python·ollama·qwen模型
小凡子空白在线学习2 小时前
工作中设计模式内容
java·后端·spring
高林雨露2 小时前
Java开发转kotlin
java·kotlin
Wenzar_2 小时前
# 发散创新:SwiftUI 中状态管理的深度实践与重构艺术 在 SwiftUI 的世界里,**状态驱动 UI 是核心哲学**。但随
java·python·ui·重构·swiftui
我不是懒洋洋2 小时前
【数据结构】二叉树-堆(树的概念、二叉树的概念、顺序结构的结构及实现、堆的实现、堆排序、TopK问题)
c语言·数据结构·c++·经验分享·算法·青少年编程
楼田莉子2 小时前
仿muduo库的高并发服务器——正则表达式与any类介绍及其简单模拟实现
linux·服务器·c++·学习·设计模式
xiaoxiaoxiaolll2 小时前
《Nature Communications》:集成热光调制与3D空间并行的光子神经网络芯片
学习