Java-数据结构-顺序表(ArrayList)

在之前的博客中,我们大部分都在学习数据结构相关的理论知识,而今天我们要学到的ArrayList便有所不同了,ArrayList这部分算是重要的知识,所以大家打起精神,让我们一起学习~

在学习ArrayList之前,我们需要先认识一下List:

一、认识 List 接口

① List 的定义

在Java中,List是一个接口,它继承自 Collection 接口。List 接口代表一个有序的元素序列,允许元素重复。也就是说我们可以按照添加顺序存储一组元素,并且同一元素可以多次出现。List 接口为我们提供了许多方法来控制列表中的元素。

我们可以通过这张关系图看出来,List在其中所处的位置,以及它继承什么,它的子类都有什么。

② List 的基本方法

List 接口包括 Collection 接口的所有方法,因为Collection是List的超级接口。

③ List 的使用

首先我们由刚刚的图能够知道,List 是一个接口,它不能直接用来实例化!

而想要具体使用 List 就需要我们实例化 List 的实现类~所以就引出我们本篇博客的主要内容:ArrayList。

二、认识 ArrayList

① 线性表

📚 ++线性表的定义++:

线性表是由n个具有相同数据类型的元素构成的有限序列,其中元素之间存在一对一的前后关系。常见的线性表有:顺序表、链表、栈、队列...

📕 元素类型 :线性表中的元素具有相同的数据类型,可以是基本数据类型(如整数、浮点数等)或者自定义的对象类型。

📕 元素的数量 :线性表中的元素个数是有限的,通过计数器或者链式结构进行统计。

📕 元素顺序 :线性表中元素的顺序是有序的 ,每个元素都唯一对应一个前驱元素和一个后继元素(除了第一个元素没有前驱,最后一个元素没有后继)。

(注:线性表在逻辑上是线性结构,也就是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。)

② 顺序表

📚 ++顺序表的定义++:

顺序表使用数组或者类似数组的存储结构,元素在内存中连续存储,通过数组下标访问元素。顺序表的插入和删除操作需要移动大量元素,但是随机访问效率较高。

③ ArrayList 的定义

在上图中,我们可以知道 ArrayList 就是 List 的实现类 ,而 ArrayList 同时也是顺序表的一种。

ArrayList 与我们之前所学习过的"数组"很像,在逻辑上基本是一致的但是 ArrayList 由于是 List 接口的实现,所以也就理所应当的拥有很多的方法,比如 ArrayList 相比于"数组",则会更为灵活,因为当我们创建"数组"时,必须声明"数组"的大小才能够使用它,一旦声明了数组的大小就很难去更改它。而 ArrayList 中所拥有的方法就能够解决这个问题,ArrayList 允许我们创建可调整大小的数组,并会自动扩容~所以 ArrayList 也被称为 "动态数组"。

(注:ArrayList 的自动扩容为原来的1.5倍,就是旧容量加上旧容量值右移一位得到的)

所以在学习 ArrayList 之后,我们以后无论是做题还是工作,大部分时候都是优先选择 ArrayList 的~

④ ArrayList 的创建

📚 ++在Java中创建数组列表的方法++:

java 复制代码
//向上转型 利用多态使用 List 创建一个 ArrayList
List<Integer> list = new ArrayList<>();
//直接创建 ArrayList
ArrayList<Integer> arrayList = new ArrayList<>();

(注:Integer 也可以换成其他的"基本数据类型对应包装类"或其他自定义类~)

三、ArrayList 的方法

++接下来我将边为大家讲解示范 ArrayList 的各种方法,同时模拟实现一个 MyArrayList 以便大家更好的理解~++

① ArrayList 基本框架

上面我们提到了,ArrayList 其实就是"动态数组",所以我们可以用数组来作为 MyArrayList 中存储数据的载体,并且为了后续能够使 MyArrayList 拥有自动扩容的能力,我们还需要创建出一个"有效大小 usedSize" 和 "默认容量 DEFAULT_SIZE"以协助我们后续编写方法

java 复制代码
public class MyArrayList {
    //创建数组作为顺序表
    public int[] elem;
    //数组的有效大小
    public int usedSize;
    //初始容量
    private static final int DEFAULT_SIZE = 10;

    public MyArrayList() {
        this.elem = new int[DEFAULT_SIZE];
    }
}

② ArrayList.add(int data)

这个很好理解,就是往顺序表中添加新的元素 ,但是,这与数组的" arr[0] = in.nextInt() "不太一样因为数组元素未初始化时,本身就有默认值 0 ,这样的操作只是将新的数据把 0 替换掉了而已并不是添加进去而 ArrayList 在逻辑上就是添加了新的元素,而并不是替换~

📚 ++那么我们再尝试一下,在刚刚创建的 MyArrayList 中也实现一个 add() 方法++:

📌 为防止越界访问,该方法需要做到能够识别顺序表是否已满

📌 若此时已满,该方法则发生自动扩容

java 复制代码
    public void add(int data) {
        if(isFull()){
            //1.5倍扩容
            elem = Arrays.copyOf(elem,elem.length + (elem.length >> 1));
        }
        elem[usedSize] = data;
        usedSize++;
    }
java 复制代码
    public boolean isFull() {
        if(usedSize >= elem.length) {
            return true;
        }
        return false;
    }

我们的顺序表初始大小是10,那么我们多输入几个数据试试,看看有没有自动扩容

是不是觉得有些晕晕的?不要忘了,ArrayList 是可以直接打印的,但我们的 MyArrayList 以数组作为载体,直接打印的话当然出现的是地址啦 ~所以我们接下来还得再实现一下 toString() 的重写才行~

③ ArrayList 的打印

打印数组各个元素时,我们需要将数组遍历一遍,但是 ArrayList 是能够直接打印其中元素的,其打印格式为:

于是我们也按照该格式重写 toString() 就好了:

java 复制代码
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for(int i = 0;i < usedSize;i++){
            sb.append(elem[i]);
            if(i != usedSize - 1){
                sb.append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }

📚 ++借此我们也顺便测试一下上面的 add() 方法编写的是否有误++:

看来格式是没有问题的,并且**"检查顺序表是否已满"** 和**"自动扩容"**的功能目前看来也没有问题~

④ ArrayList.add(int pos,int data)

上面我们了解到了 ArrayList 的 add(int data) 方法 ,作用是直接在顺序表的末尾添加一个新元素 ,而还有另一个带下标参数的 add(int pos,int data) 方法 ,它的作用就是在顺序表的指定位置(pos)添加一个元素~

(注:并不是在 pos 之后插入数据,而是将数据放在 pos 的位置上,其余位置向后移动)
就像赛跑的时候,你超越了第二名,但你并不是第一名,只是第二名~

📚 ++还是让我们看看这个功能在 MyArrayList 中是如何实现的++:

📌 该方法需要判断 pos 的合法性

📌 该方法需要将位于 pos 以及之后的元素后移一位

📌 该方法仍需要判断顺序表是否已满,并自动扩容

首先为了判断 pos 的合法性,我们需要自定义一个异常类,用于在 pos 访问不合法时报错并给予提示

java 复制代码
public class PosIllegal extends RuntimeException{
    public PosIllegal(){

    }
    public PosIllegal(String message){
        super(message);
    }
}

随后我们还需要判断 pos 是否合法:

java 复制代码
    private boolean checkPosInAdd(int pos) {
        if(pos > usedSize + 1 || pos < 0){
            throw new PosIllegal("pos的位置不合法");
        }
        return true;//合法
    }

(注:pos 是可以等于 usedSize 的,当 pos = usedSize 时,此时为尾插)

java 复制代码
    // 在 pos 位置新增元素
    public void add(int pos, int data) {
        if(checkPosInAdd(pos)){
            usedSize++;
            if(isFull()) {
                elem = Arrays.copyOf(elem, elem.length << 1);
            }
            for (int i = usedSize - 2; i >= pos; i--) {
                elem[i + 1] = elem[i];
            }
            elem[pos] = data;
        }
    }

📚 ++让我们检验一下++:

📕pos 不合法

📕pos 合法

⑤ ArrayList.contains(int toFind)

ArrayList 的 contains 方法的作用:用于判断ArrayList中是否包含指定的元素。

📕能查找到则返回 true

📕 查找不到则返回 false

📚 ++接下来我们模拟实现 contains(int toFind)方法++

只需要拥有查找指定元素的功能就行了 ~想查找到一个元素的方法很多,最简单的方式就是遍历数组 ,但既然是自己实现,就多写写自己不熟悉的代码嘛~不然怎么能起到练习的作用呢,所以我打算使用二分来实现 contains 方法

二分就需要我们先定义一个 left 和一个 right 表示起始点和终点(查找范围),并且每一步都求出两者的中间值 mid ,通过判断"顺序表 mid 位置的元素大小" 和 "toFind" 的大小,从而移动 left 和 right 的位置,以此慢慢缩短距离,找到 toFind

java 复制代码
    // 判定是否包含某个元素
    public boolean contains(int toFind) {
        int l = 0;
        int r = usedSize - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(toFind <= elem[mid]){
                r = mid;
            }else {
                l = mid + 1;
            }
        }
        if(elem[l] == toFind){
            return true;
        }
        return false;
    }

⑥ ArrayList.indexOf(int toFind)

ArrayList 的 indexOf 方法的作用是:用于返回指定元素在ArrayList中首次出现的索引位置。如果ArrayList中不包含该元素,则返回-1。

上面我所写的二分代码,就是能够遇到重复数字时,只查到第一次出现的,所以我们只需要稍微改动即可:

java 复制代码
    // 查找某个元素对应的位置
    public int indexOf(int toFind) {
        int l = 0;
        int r = usedSize - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(toFind <= elem[mid]){
                r = mid;
            }else {
                l = mid + 1;
            }
        }
        if(elem[l] == toFind){
            return l;
        }
        return -1;
    }

⑦ ArrayList.get(int pos)

ArrayList的get方法用于返回指定索引位置上的元素。

这样用起来看着没什么问题,也很好实现,但不妨让我们在写两行代码呢:

📚 ++那么让我们试着实现一下 get(int pos) 方法++:

📌 判断是否为空表

📌 判断 pos 是否合法

java 复制代码
public class PosIllegal extends RuntimeException{
    public PosIllegal(){

    }
    public PosIllegal(String message){
        super(message);
    }
}
java 复制代码
    //判断是否为空表
    public boolean isEmpty() {
        if(usedSize == 0) {
            return true;
        }
        return false;
    }
java 复制代码
    public int get(int pos) {
        if(isEmpty()){
            throw new ListIsEmpty("顺序表为空!");
        }
        if(pos > usedSize - 1 || pos < 0){
            throw new PosIllegal("pos的位置不合法!");
        }
        return elem[pos];
    }

让我们再测试一下

⑧ ArrayList.set(int pos,int value)

ArrayList的set方法用于将指定索引位置上的元素替换为新的元素。

该方法与上面的 add(int pos,int data) 方法类似,就不过多介绍了,大家一定能看懂的~

java 复制代码
    // 给 pos 位置的元素设为【更新为】 value
    public void set(int pos, int value) {
        if(checkPosInAdd(pos)){
            elem[pos] = value;
        }
    }

⑨ ArrayList.remove(int key)

ArrayList的remove方法用于从列表中删除指定位置的元素或指定元素的第一个匹配项。

实现这个方法我们只需要使用上面用到的 indexOf() 方法找到指定元素后,将其删除,将之后的元素一一向前移动即可~(还需要注意检查是否为空表)代码:

java 复制代码
    public void remove(int key) {
        int index = indexOf(key);
        if(isEmpty()){
            throw new ListIsEmpty("顺序表为空!");
        }
        if(index < 0){
            return;
        }
        for(int i = index ;i < usedSize;i++){
            elem[i] = elem[i + 1];
        }
        usedSize--;
    }

⑩ size()与clear()

ArrayList.size() 方法的作用是返回 ArrayList 中元素的数量。

ArrayList.clear() 的作用是清空 ArrayList 中的所有元素,将其变为空列表。

java 复制代码
    // 获取顺序表长度
    public int size() {
        return usedSize;
    }

    // 清空顺序表
    public void clear() {
        usedSize = 0;
    }

四、模拟ArrayList完整代码

📖 MyArrayList.java

java 复制代码
import java.util.Arrays;

public class MyArrayList {
    public int[] elem;
    public int usedSize;
    private static final int DEFAULT_SIZE = 10;

    public MyArrayList() {
        this.elem = new int[DEFAULT_SIZE];
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for(int i = 0;i < usedSize;i++){
            sb.append(elem[i]);
            if(i != usedSize - 1){
                sb.append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }

    // 新增元素,默认在数组最后新增
    public void add(int data) {
        if(isFull()){
            elem = Arrays.copyOf(elem,elem.length + (elem.length >> 1));
        }
        elem[usedSize] = data;
        usedSize++;
    }

    /**
     * 判断当前的顺序表是不是满的!
     *
     * @return true:满   false代表空
     */
    public boolean isFull() {
        if(usedSize >= elem.length) {
            return true;
        }
        return false;
    }

    private boolean checkPosInAdd(int pos) {
        if(pos > usedSize + 1 || pos < 0){
            throw new PosIllegal("pos的位置不合法");
        }
        return true;//合法
    }

    // 在 pos 位置新增元素
    public void add(int pos, int data) {
        if(checkPosInAdd(pos)){
            usedSize++;
            if(isFull()) {
                elem = Arrays.copyOf(elem, elem.length << 1);
            }
            for (int i = usedSize - 2; i >= pos; i--) {
                elem[i + 1] = elem[i];
            }
            elem[pos] = data;
        }
    }

    // 判定是否包含某个元素
    public boolean contains(int toFind) {
        int l = 0;
        int r = usedSize - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(toFind <= elem[mid]){
                r = mid;
            }else {
                l = mid + 1;
            }
        }
        if(elem[l] == toFind){
            return true;
        }
        return false;
    }

    // 查找某个元素对应的位置
    public int indexOf(int toFind) {
        int l = 0;
        int r = usedSize - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(toFind <= elem[mid]){
                r = mid;
            }else {
                l = mid + 1;
            }
        }
        if(elem[l] == toFind){
            return l;
        }
        return -1;
    }

    // 获取 pos 位置的元素
    public int get(int pos) {
        if(isEmpty()){
            throw new ListIsEmpty("顺序表为空!");
        }
        if(pos > usedSize - 1 || pos < 0){
            throw new PosIllegal("pos的位置不合法!");
        }
        return elem[pos];
    }

    public boolean isEmpty() {
        if(usedSize == 0) {
            return true;
        }
        return false;
    }

    // 给 pos 位置的元素设为【更新为】 value
    public void set(int pos, int value) {
        if(checkPosInAdd(pos)){
            elem[pos] = value;
        }
    }

    /**
     * 删除第一次出现的关键字key
     */
    public void remove(int key) {
        int index = indexOf(key);
        if(isEmpty()){
            throw new ListIsEmpty("顺序表为空!");
        }
        if(index < 0){
            return;
        }
        for(int i = index ;i < usedSize;i++){
            elem[i] = elem[i + 1];
        }
        usedSize--;
    }

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

    // 清空顺序表
    public void clear() {
        usedSize = 0;
    }
}

📖 PosIllegal.java

java 复制代码
public class PosIllegal extends RuntimeException{
    public PosIllegal(){

    }
    public PosIllegal(String message){
        super(message);
    }
}

📖 ListIsEmpty.java

java 复制代码
public class ListIsEmpty extends RuntimeException{
    public ListIsEmpty(){

    }
    public ListIsEmpty(String message){
        super(message);
    }
}

那么关于ArrayList(顺序表)的知识就为大家分享到这里啦~作者能力有限,如果有讲得不清晰或者不正确的地方,还请大家在评论区多多指出,我也会虚心学习的!那我们下次再见哦~

相关推荐
考虑考虑20 小时前
Jpa使用union all
java·spring boot·后端
用户37215742613520 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊21 小时前
Java学习第22天 - 云原生与容器化
java
渣哥1 天前
原来 Java 里线程安全集合有这么多种
java
间彧1 天前
Spring Boot集成Spring Security完整指南
java
间彧1 天前
Spring Secutiy基本原理及工作流程
java
Java水解1 天前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆1 天前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学1 天前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole1 天前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端