Java【顺序表】详细图解模拟实现 + 【ArrayList】常用方法介绍

本文详细介绍了如何模拟实现顺序表,包括构造方法、添加元素、判断是否包含元素、查找元素位置等成员方法。同时,讲解了Java中ArrayList类的使用,包括实例化、插入、删除、获取元素等常用操作,强调了ArrayList的动态扩容和线程不安全特性。
摘要由CSDN通过智能技术生成

📕各位读者好, 我是小陈, 这是我的个人主页
📗小陈还在持续努力学习编程, 努力通过博客输出所学知识
📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽
📙 希望我的专栏能够帮助到你:
JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等
Java数据结构: 顺序表, 链表, 堆, 二叉树, 二叉搜索树, 哈希表等
JavaEE初阶: 多线程, 网络编程, TCP/IP协议, HTTP协议, Tomcat, Servlet, Linux, JVM等(正在持续更新)


前言

本篇将分享Java集合类当中的ArrayList的常见方法和使用,在这之前需要了解 [什么是线性表]

👉线性表 :是 n 个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列…
🙋🏼线性表在逻辑上是连续的 ,也就说是连续的一条直线
🙋🏼但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组链式结构的形式存储。


提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎评论区指点~ 废话不多说,直接发车~

一、模拟实现顺序表

👉认识顺序表
顺序表是用 一段物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用 数组 存储。在数组上完成数据的增删查改。

为什么要模拟实现:
自己模拟实现 简易版的 顺序表的增删查改等主要功能,大致理解顺序表的设计思想
再对比学习 Java 提供的集合类当中的 ArrayList ,在学习 Java 的 ArrayList 常用方法的同时,也能学习源码的思想

成员属性

Java 中的 ArrayList 是集合框架中的一个类,要模拟实现顺序表,也得自己实现一个类,首先要考虑这个类中的成员属性

已经说过,顺序表底层是基于数组实现的(不完全对,暂时这样理解),那么成员属性就需要:
1️⃣数组 array:来存放数据
2️⃣变量 capacity :来记录数组的容量,当数组满了就需要增大容量
3️⃣变量 useSize:来记录数组已经存放了几个数据

体现封装思想,成员属性全部设置为 private

public class SeqList {
    private int[] array;// 数组
    private int capacity;// 容量
    private int useSize;// (使用过的)长度
}

⚠️模拟实现重在理解理想,为了简便,不使用泛型🙅,数组中存放 int 类型


成员方法

// 新增元素,默认在数组最后新增
public void add(int data) { }

// 在 pos 位置新增元素
public void add(int pos, int data) { }

// 判定是否包含某个元素
public boolean contains(int toFind) { return true; }

// 查找某个元素对应的位置
public int indexOf(int toFind) { return -1; }

// 获取 pos 位置的元素
public int get(int pos) { return -1; }

// 给 pos 位置的元素设为 value
public void set(int pos, int value) { }

//删除第一次出现的关键字key
public void remove(int toRemove) { }

// 获取顺序表长度
public int size() { return 0; }

// 清空顺序表
public void clear() { }

// 打印顺序表,注意:ArrayList 没有这个方法,为了方便看测试结果给出的
public void display() { }

1,构造方法

构造方法的作用:初始化成员属性,useSize 无需初始化,编译器默认初始化为 0

    // 将顺序表的底层容量设置为capacity
    public SeqList(int capacity){
        this.capacity = capacity;
        // 为数组开辟内存空间 
        this.array = new int[this.capacity];
    }

2,add——新增元素,默认在数组末尾新增

❗️❗️在末尾新增数据之前,必须考虑
顺序表是否已🈵️,如果满了,则需要扩容之后再添加元素


分别写出 判满扩容 这两个方法:

2.1, isFull——判断顺序表是否已满
    public boolean isFull() {
        return this.useSize == this.capacity;
    }

用户也可能 / 可以使用这个方法,所以我们可以设置为 public

2.2, expandCapacity——扩容

利用 copyOf 方法,拷贝数组并指定容量,让 this.array 引用新的数组

    private void expandCapacity() {
        // 太长了,中间换行
        this.array = Arrays.copyOf(this.array,
                this.capacity * 2);
    }

这个方法不需要用户访问,只在类内使用,所以可以设置为 private


✅所以 add 方法的正确写法为:

    public void add(int data) {
        // 判满
        if (isFull()) {
            // 满了就扩容
            expandCapacity();
        }
        this.array[this.useSize] = data;
        this.useSize++;
    }

最后记得,增加数据之后,**useSize 也要++**❗️❗️


3,add——在 pos 位置新增元素

❗️❗️这个方法是:在指定位置新增元素,新增之前必须考虑
1️⃣顺序表是否已满
2️⃣pos 位置是否合法


判满的方法以及写过了,现在我们需要补充:判断 pos 位置合法性 的方法
需要思考,pos 在什么位置才是合法呢?

📃分析:
在这里插入图片描述
在这种情况下,pos < 0 或 pos > 3 都是不合法❌的

⚠️数组没有负数下标
⚠️数组内数据必须连续

3.1, judgeAddPos——判断 add 时 pos 位置合法性
    private void judgeAddPos(int pos) {
        if (pos < 0 || pos > this.useSize) {
            // 自定义异常
            throw new ArrayListIndexOutOfException
                    (" add 时 pos 位置不合法!");
        }
    }

这个方法不需要用户访问,只在类内使用,所以可以设置为 private

如果 pos 参数不合法,就不能执行下面的代码,所以我们可以自定义一个异常类

3.3,ArrayListIndexOutOfException——自定义下标不合法异常
// 继承于运行时异常
public class ArrayListIndexOutOfException 
                 extends RuntimeException {
    public ArrayListIndexOutOfException(String str) {
        super(str);
    }
}

这样,当我们 add 时的 pos 参数不合法时,就会抛出这样的异常并终止程序🈲
在这里插入图片描述


“准备工作” 做足之后,我们需要考虑,如何实现在 pos 位置新增,也就是插入呢❓

🚗🚗🚗
就是把 pos 下标以及之后 的数据 向后依次 覆盖,最终把 pos 位置“空出来”,放入新数据:

在这里插入图片描述

    public void add(int pos, int data) {
        if (isFull()) {
        // 判满 + 扩容
            expandCapacity();
        }
        // 判断 pos 位置合法性
        judgeAddPos(pos);
        // 挪动数据
        for (int i = this.useSize; i > pos; i--) {
            this.array[i] = this.array[i - 1];
        }
        // 插入数据
        this.array[pos] = data;
        this.useSize++;
    }

最后不要忘记 useSzie++ ❗️❗️

⚠️注意:
要先移动 3,再移动 2 ——从后往前的顺序移动
如果先移动 2 ,则会把 3 覆盖掉,丢失数据


4,contains——判定是否包含某个元素

比较简单,遍历这个数组即可

    public boolean contains(int toFind) {
        for (int i = 0; i < this.useSize; i++) {
            if (this.array[i] == toFind) {
                return true;
            }
        }
        return false;
    }

因为这里我们存放的是 int 类型的变量,但 ArrayList 当中可以存放引用数据类型的
⚠️⚠️⚠️当表中是引用类型时,就不可以用“等号”比较,应该用 equals 方法


5, indexOf——查找某个元素对应的位置

还是遍历数组

    public int indexOf(int toFind) {
        for (int i = 0; i < this.useSize; i++) {
            if (this.array[i] == toFind) {
                return i;
            }
        }
        return -1;
    }

因为这里我们存放的是 int 类型的变量,但 ArrayList 当中可以存放引用数据类型的
⚠️⚠️⚠️当表中是引用类型时,就不可以用“等号”比较,应该用 equals 方法


6,get——获取 pos 位置的元素

在获取 pos 之前必须保证 pos 位置合法性,但此时的 pos 判断合法性和 add 时的判断规则不一样咯❗️❗️


🚗🚗🚗
获取 pos 位置的元素,前提是 pos 位置上有数据
此时 pos 的合法性判断规则是:pos 不能小于 0 或 不能大于 useSize - 1

6.1,judgePos——判断 pos 位置合法性
    private void judgePos(int pos) {
        if (pos < 0 || pos > this.useSize - 1) {
        // 自定义异常
            throw new ArrayListIndexOutOfException
                    (" pos 位置不合法!");
        }
    }

这个方法不需要用户访问,只在类内使用,所以可以设置为 private

如果pos 不合法,我们让程序终止,抛出异常❌


做好 “准备工作” 之后, get 方法就很简单咯

    public int get(int pos) {
        // 首先判断 pos 位置合法性
        judgePos(pos);
        return this.array[pos];
    }

7, set——给 pos 位置的元素设为 value

老规矩,pos 作为参数时,就要判断合法性❗️❗️

    public void set(int pos, int value) {
        judgePos(pos);
        this.array[pos] = value;
    }

8,remove——删除第一次出现的数据

我们前面分析过了 add 方法的执行原理,那么删除的原理恰好是和 add 的操作相反:在数组中要 “删除” 一个数,让后面的数据依次向前覆盖即可:

在这里插入图片描述
可以看到最后还剩一个 3,没有必要处理,useSize- - 即可✅
别忘了,怎么找到待删除数据的位置呢❓——调用前面写过的 indexOf 方法❗️

    public void remove(int toRemove) {
        int pos = indexOf(toRemove);
        if (pos == -1) {
        // 找不到的情况
            System.out.println("不存在该数据");
        }else {
            // 注意这里的循环条件
            for (int i = pos; i < this.useSize - 1; i++) {
                this.array[i] = this.array[i + 1];
            }
            this.useSize--;
        }
    }

⚠️注意:
i < this.useSize - 1 这里不能写成 <=,当数组正好是满的情况下
this.array[i] = this.array[i + 1]; 这里访问 i+1 下标就会数组越界


9,size——获取顺序表长度

直接返回 useSize 即可

    public int size() {
        return this.useSize;
    }

10,clear——清空顺序表

我们的操作都是利用 useSize 所以直接把 useSize 置为 0 即可

    public void clear() {
        this.useSize = 0;
    }

11,display——打印顺序表

注意:顺序表中不存在该方法,为了方便看测试结果

    public void display() {
        for (int i = 0; i < this.useSize; i++) {
            System.out.print(this.array[i] + " ");
        }
        System.out.println();
    }

二、Java提供的ArrayList

1,ArrayList 的说明

🙋🏼Java官方的集合框架中,ArrayList 是一个普通的类,继承了List接口

⚠️特殊说明:
1️⃣ArrayList 是以泛型的方式实现的类,使用时必须先实例化
2️⃣ArrayList 实现了 RandomAccess 接口,表明 ArrayList 支持随机访问
3️⃣ArrayList 实现了 Cloneable 接口,表明 ArrayList 是可以 clone 的
4️⃣ArrayList 实现了 Serializable 接口,表明 ArrayList 是支持序列化的
5️⃣ArrayList 不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector 或者 CopyOnWriteArrayList
6️⃣ArrayList 底层是一段连续的空间(可以理解为数组),并且可以动态扩容,是一个动态类型的顺序表


2,使用ArrayList

2.1,ArrayList 实例化方式

1️⃣无参构造法

        ArrayList<Integer> arrayList1 = new ArrayList<>();

2️⃣有参数——参数表示容量

        ArrayList<Integer> arrayList2 = new ArrayList<>(10);

此时的 arrayList2 的容量被指定为 10

3️⃣有参数——参数是其他 Collection
Collection是集合框架中的一个接口,实现了这个接口的类的对象就可以作为参数,说白了就是
可以把其他的顺序表,链表,栈,队列等等 作为参数传参,例如:

		LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.addLast(1);
        linkedList.addLast(2);
        ArrayList<Integer> arrayList3 = new ArrayList<>(linkedList);

我先 new 了一个链表对象 linkedList,在这个链表中插入了 “1” “2” 两个数据,链表中的数据是链式存储❗️的
当 linkedList 作为参数传递时,arrayList3 中就有了 linkedList 中的所有数据,arrayList3 中的数据是顺序存储❗️的


2.2,ArrayList 常用方法

        ArrayList<Integer> arrayList = new ArrayList<>();
        // 1,插入(尾插)
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(4);
        arrayList.add(5);
        System.out.println("插入数据后:" + arrayList);

        // 2,在任意位置插(下标)插入
        arrayList.add(0, -1);
        System.out.println("在0下标插入-1后:" + arrayList);

        // new 一个链表对象并尾插“1”,“2”
        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.addLast(1);
        linkedList.addLast(2);
        // 3,插入 linklist 的所有元素
        arrayList.addAll(linkedList);
        System.out.println("插入linklist后:" + arrayList);

        // 4,删除任意位置(下标)数据
        arrayList.remove(0);
        System.out.println("删除0下标数据后:" + arrayList);

        // 5,删除指定数据
        arrayList.remove(new Integer(1));// 参数为Object(类)类型的对象
        System.out.println("删除第一个1后:" + arrayList);
        // 上面两个remove()方法构成了重载
        // remove()方法的参数输入之后会被自动识别为index,
        // 因为index是int类型,而顺序表中的数据是Object(类)类型
        // 比如在插入数据时,输入的是1,基本类型,但会自动装箱,变成Integer类型
        // 所以要删除数据,应该输入类类型的对象,而不是基本类型的数据

        // 6,获取任意位置(下标)数据
        int ret = arrayList.get(0);
        System.out.println("得到0下标的数据:" + ret);

        // 7,更改任意位置(下标)数据
        arrayList.set(0,100);
        System.out.println("把0下标数据改成100后:" + arrayList);

        // 8,判断是否存在该数据
        boolean bl = arrayList.contains(100);
        System.out.println("判断是否存在100这个数据:" + bl);

        // 9,返回第一个key的位置(下标)
        int index = arrayList.indexOf(2);
        System.out.println("第一个2的下标:" + index);

        // 10,返回最后一个key的位置(下标)
        int lastIndex = arrayList.lastIndexOf(2);
        System.out.println("最后一个2的下标:" + lastIndex);

        // 11,获取顺序表长度
        int size = arrayList.size();
        System.out.println("顺序表长度为:" + size);

        // 12,截取
        List<Integer> list  = arrayList.subList(1, 3);// [1,3)左闭右开
        System.out.println(list);

        // 13,清空顺序表
        arrayList.clear();
        System.out.println("清空顺序表后:" + arrayList);

总结

以上就是今天分享的关于数据结构中【顺序表】的内容,一方面介绍了如何模拟实现简易的顺序表,一方面介绍了Java集合框架中的【ArrayList】类的基本使用【ArrayList】

如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦🤪🤪🤪


上山总比下山辛苦
下篇文章见

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灵魂相契的树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值