Java语言----动态顺序表(ArrayList)

简介: Java语言----动态顺序表(ArrayList)

😽个人主页: tq02的博客_CSDN博客-C语言,Java,Java数据结构领域博主

🌈梦的目标:努力学习,向Java进发,拼搏一切,让自己的未来不会有遗憾。

🎁欢迎各位→点赞👍 + 收藏⭐ + 评论📝+关注✨

 本章讲解内容:顺序表的讲解


48b44c8bb51a4c12b587a22f8f6c26d1.jpg


使用编译器:IDEA

一.顺序表


879126b6af89446496a80049aaad8b37.png


顺序表(ArrayList):用一段物理地址连续的存储单元依次存储数据元素的线性结构,通常采用数组存储。在数组上完成数据的增删查改


二.顺序表的手动实现

   2.1顺序表的创建


import java.util.Arrays;
public class MyArraysList {
    private int[] elem;
    private int usedSize; // 默认值是0
    private static final int DEFAULT_SIZE = 4; // 定义为常量,更加安全
    // 初始化顺序表
    public MyArraysList() {
        this.elem = new int[DEFAULT_SIZE];
    }
}


 这边是创建顺序表的类,不仅要创建出来,我们还需要学会使用,例如增、删、查、改

   2.2.基本功能的实现

2.2.1扩容顺序表

// 对顺序表进行扩容
    public void expand() {
        this.elem = Arrays.copyOf(this.elem, this.usedSize * 2);
        System.out.println("已经成功扩容至原来的两倍"); // 给用户提醒
    }


注:     在进行增加元素的操作时,当数组满了,则需要扩容,才能容纳下一个元素

 

 2.2.2 判断顺序表是否为满

    /**
     * 判断当前顺序表是不是满了
     * @return true->满了,false->还没满
     */
    public boolean isFull() {
        if (this.usedSize == this.elem.length) return true;
        else return false;
    }


注:扩容前得判断是否数组满了,只有满了,才需要扩容

 

2.2.3 判断顺序表是否为空

     /**
     * 判断当前顺序表是否为空
     * @return true->空的,false->还没空
     */
    public boolean isempty() {
        if (this.usedSize == 0) {
            return true;
        }
        else return false;
    }


注:如果需要删除顺序表的元素,得判断顺序表是否为空,毕竟空的顺序表无法删除元素。

2.2.4打印顺序表

// 打印顺序表
    public void display() {
        for (int i = 0; i < this.elem.length; i++) {
            System.out.print(this.elem[i] + " ");
        }
        System.out.println();
    }
}


注:顺序表存放了数据,得显示出来,展示你存放的数据


2.2.5清空顺序表

 // 清空顺序表
    public void clear() {
        for (int i = 0; i < this.usedSize; i++) {
            this.elem[i] = 0;
        }
        this.usedSize = 0; // 注意有效数组长度也要清零
    }


注:当代码结束之后,需要释放顺序表内存。


2.3四大功能的实现

2.3.1增加元素

头插法:


 // 新增元素,在数组最前面新增
    public void addHead(int data) {
        if (isFull()) {
            System.out.println("数组满了,需要扩容");
            expand();
        }
        else {
            // 从usedSize下标开始,不会数组越界(此时的elem.length > usedSize)
            for (int i = this.usedSize; i > 0; --i) {
                this.elem[i] = this.elem[i - 1]; // 从后往前挪动数据,为的是给顺序表的表头腾出来
            }
            this.elem[0] = data; // 在顺序表开头插入
            this.usedSize++;    // 数组有效长度加一
        }


注:每次增加的元素,都放在顺序表的第一位。

尾插法:

//在数组最后新增
    public void addTail(int data) {
        if (isFull()) {
            System.out.println("数组满了需要扩容");
            expand();
        }
        else this.elem[this.usedSize++] = data;
    }


注:每次插入元素,直接放在顺序表的最后一位。

指定下标值插入:

 // 在 pos 位置新增元素
    public void addPos(int pos, int data) {
        if (pos < 0 || pos > usedSize) {
            System.out.println("pos位置不合法"); return;
        }
        if (isFull()) {
            System.out.println("数组满了需要扩容");
            expand();
        }
        else {
            // 如果插入位置在顺序表的中间,要注意挪动元素,从后向前挪动,这样不会造成元素值的覆盖
            for (int i = this.usedSize - 1; i >= pos; --i) {
                this.elem[i + 1] = this.elem[i];
            }
            this.elem[pos] = data; // 挪动完毕,可以赋值插入
            this.usedSize++;      // 当前元素数加一
        }
    }


若在对应下标插入元素,则下标值后面的所有元素整体向后移一位。

注意:需要判断数组是否满了、插入位置在元素下标范围内。


2.3.2删除元素

头删:

 // 删除表头元素
    public void removeHead() {
        if (isempty()) {
            System.out.println("顺序表为空,删除不合法");
            return;
        }
        // 从第一个元素开始,用后面元素的值覆盖掉前面的值,遍历整个数组就相当于把第一个元素用覆盖的方式抹去了
        for (int i = 1; i < this.usedSize; i++) {
            this.elem[i - 1] = this.elem[i];
        }
        this.elem[this.usedSize - 1] = 0; // 减少了一个元素,所以之前最后一个元素置0
        this.usedSize--; // 不要忘记改变有效数组的长度
    }


注:从第二个元素开始,每一个元素覆盖前面一个,再将最后一个元素清零。

尾删:

    // 删除表尾元素
    public void removeTail() {
        if (isempty()) {
            System.out.println("顺序表为空,删除不合法");
            return;
        }
        this.elem[this.usedSize - 1] = 0; // 直接将最后一个元素置0就完成了尾删
        this.usedSize--; // 不要忘记改变有效数组的长度
    }


注:直接删除末尾元素,将最后一个元素置0

指定下标元素的删除:

 // 指定下标元素的删除
    public void removePos(int pos) {
        if (isempty()) {
            System.out.println("顺序表为空,删除不合法");
            return;
        }
        if (pos < 0 || pos >= this.usedSize) {
            System.out.println("pos下标不合法");
        }
        else {
            for (int i = pos; i < this.usedSize - 1; ++i) {
                this.elem[i] = this.elem[i + 1]; // 从要删除的下标开始,用后边元素的值覆盖掉前面的值,就完成了删除
            }
            this.elem[this.usedSize - 1] = 0; // 最后一个元素置0
            this.usedSize--;// 删除后更改顺序表的有效长度
        }
    }


注:与头删法相似。

删除首次出现的元素:

 //删除第一次出现的关键字key
    public void removeKey(int toRemove) {
        if (isempty()) {
            System.out.println("顺序表为空,删除不合法");
            return;
        }
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toRemove) {
                // 注意是this.usedSize - 1,将此时 i 之后的元素统一往前搬移一个位置
                for (int j = i; j < this.usedSize - 1; ++j) {
                    this.elem[j] = this.elem[j + 1];
                }
                this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空
                this.usedSize--; // 删除后不要忘记更改顺序表的有效长度
                return; // 只删除第一次出现的
            }
        }
    }


注:找到对应下标值,然后再进行覆盖,在删除的后面全体元素向前挪一位


2.3.3查找元素

获取指定位置元素:

 // 获取 pos 位置的元素
    public int getPos(int pos) {
        if (pos < 0 || pos >= this.usedSize) { // 注意这里当pos==this.usedSize也是不合法的,因为此时的pos下标所要获取的是顺序表中第usedSize+1个元素
            System.out.println("pos的位置不合法");
            return -1;
        }
        else {
            return this.elem[pos];
        }
    }


注:考虑要获取的位置是否合法、返回指定位置的元素

获取指定元素所在的位置:

 // 查找某个元素所对应顺序表中的位置
    public int indexOf(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return i;
            }
        }
        System.out.println("在数组中没有找到该元素");
        return -1;
    }


注:返回下标值


2.3.4更改数据

在对应下标值,进行修改:

// 给 pos 位置的元素设为 value
    public void setPos(int pos, int value) {
        if (pos < 0 || pos > this.usedSize) {
            System.out.println("pos位置不合法");
            return;
        }
        if (pos == this.usedSize) { // 对这种情况要单独处理,此时相等于增加元素
            this.elem[pos] = value;
            this.usedSize++;
        }
        else {
            this.elem[pos] = value;
        }
    }


几乎是查找的步骤上,对其修改。


总代码

📝MyArraysList.java

  MyArraysList.java用于顺序表的实现

import java.util.Arrays;
public class MyArraysList {
    private int[] elem;
    private int usedSize; // 默认值是0
    private static final int DEFAULT_SIZE = 4; // 定义为常量,更加安全
    // 初始化顺序表
    public MyArraysList() {
        this.elem = new int[4];
    }
    // 对顺序表进行扩容
    public void expand() {
        this.elem = Arrays.copyOf(this.elem, this.usedSize * 2);
        System.out.println("已经成功扩容至原来的两倍"); // 给用户提醒
    }
    /**
     * 判断当前顺序表是否为空
     * @return true->空的,false->还没空
     */
    public boolean isempty() {
        if (this.usedSize == 0) {
            return true;
        }
        else return false;
    }
    /**
     * 判断当前顺序表是不是满了
     * @return true->满了,false->还没满
     */
    public boolean isFull() {
        if (this.usedSize == this.elem.length) return true;
        else return false;
    }
    // 打印顺序表
    public void display() {
        for (int i = 0; i < this.elem.length; i++) {
            System.out.print(this.elem[i] + " ");
        }
        // System.out.println(Arrays.toString(this.elem));或者用Arrays.toString打印也行
        System.out.println();
    }
    // 新增元素,默认在数组最后新增
    public void addTail(int data) {
        if (isFull()) {
            System.out.println("数组满了需要扩容");
            expand();
        }
        else this.elem[this.usedSize++] = data;
    }
    // 新增元素,在数组最前面新增
    public void addHead(int data) {
        if (isFull()) {
            System.out.println("数组满了,需要扩容");
            expand();
        }
        else {
            // 从usedSize下标开始,不会数组越界(此时的elem.length > usedSize)
            for (int i = this.usedSize; i > 0; --i) {
                this.elem[i] = this.elem[i - 1]; // 从后往前挪动数据,为的是给顺序表的表头腾出来
            }
            this.elem[0] = data; // 在顺序表开头插入
            this.usedSize++;    // 数组有效长度加一
        }
    }
    // 1、判断pos位置是否合法(在顺序表中,数据是连续的,中间不能有空缺)
    // 2、判断顺序表是否满了,如果满了,需要扩容
    // 3、插入数据(可能需要挪作元素)
    // 在 pos 位置新增元素
    public void addPos(int pos, int data) {
        if (pos < 0 || pos > usedSize) {
            System.out.println("pos位置不合法"); return;
        }
        if (isFull()) {
            System.out.println("数组满了需要扩容");
            expand();
        }
        else {
            // 如果插入位置在顺序表的中间,要注意挪动元素,从后向前挪动,这样不会造成元素值的覆盖
            for (int i = this.usedSize - 1; i >= pos; --i) {
                this.elem[i + 1] = this.elem[i];
            }
            this.elem[pos] = data; // 挪动完毕,可以赋值插入
            this.usedSize++;      // 当前元素数加一
        }
    }
    // 删除表头元素
    public void removeHead() {
        if (isempty()) {
            System.out.println("顺序表为空,删除不合法");
            return;
        }
        // 从第一个元素开始,用后面元素的值覆盖掉前面的值,遍历整个数组就相当于把第一个元素用覆盖的方式抹去了
        for (int i = 1; i < this.usedSize; i++) {
            this.elem[i - 1] = this.elem[i];
        }
        this.elem[this.usedSize - 1] = 0; // 现在的最后一个元素是原来的倒数第二个元素, 所以原来的最后一个有效元素要置0
        this.usedSize--; // 不要忘记改变有效数组的长度
    }
    // 删除表尾元素
    public void removeTail() {
        if (isempty()) {
            System.out.println("顺序表为空,删除不合法");
            return;
        }
        this.elem[this.usedSize - 1] = 0; // 直接将最后一个元素置0就完成了尾删
        this.usedSize--; // 不要忘记改变有效数组的长度
    }
    // 指定下标元素的删除
    public void removePos(int pos) {
        if (isempty()) {
            System.out.println("顺序表为空,删除不合法");
            return;
        }
        if (pos < 0 || pos >= this.usedSize) {
            System.out.println("pos下标不合法");
        }
        else {
            for (int i = pos; i < this.usedSize - 1; ++i) {
                this.elem[i] = this.elem[i + 1]; // 从要删除的下标开始,用后边元素的值覆盖掉前面的值,就完成了删除
            }
            this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空
            this.usedSize--;// 删除后不要忘记更改顺序表的有效长度
        }
    }
    //删除第一次出现的关键字key
    public void removeKey(int toRemove) {
        if (isempty()) {
            System.out.println("顺序表为空,删除不合法");
            return;
        }
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toRemove) {
                // 注意是this.usedSize - 1,将此时 i 之后的元素统一往前搬移一个位置
                for (int j = i; j < this.usedSize - 1; ++j) {
                    this.elem[j] = this.elem[j + 1];
                }
                this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空
                this.usedSize--; // 删除后不要忘记更改顺序表的有效长度
                return; // 只删除第一次出现的
            }
        }
    }
    /**
     * /判定是否包含某个元素
     * @param toFind 要查找的元素
     * @return true->包含, false->不包含
     */
    public boolean contains(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind)  return true;
        }
        return false;
    }
    // 查找某个元素对应的位置
    public int indexOf(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return i;
            }
        }
        System.out.println("在数组中没有找到该元素");
        return -1;
    }
    // 获取 pos 位置的元素
    public int getPos(int pos) {
        if (pos < 0 || pos >= this.usedSize) {
            System.out.println("pos的位置不合法");
            return -1;
        }
        else {
            return this.elem[pos];
        }
    }
    // 给 pos 位置的元素设为 value
    public void setPos(int pos, int value) {
        if (pos < 0 || pos > this.usedSize) {
            System.out.println("pos位置不合法");
            return;
        }
        if (pos == this.usedSize) { // 对这种情况要单独处理,此时相等于增加元素
            this.elem[pos] = value;
            this.usedSize++;
        }
        else {
            this.elem[pos] = value;
        }
    }
    // 获取顺序表长度
    public int size() {
        return this.usedSize;
    }
    // 清空顺序表
    public void clear() {
        for (int i = 0; i < this.usedSize; i++) {
            this.elem[i] = 0;
        }
        this.usedSize = 0; // 注意有效数组长度也要清零
    }
}


📝Test.java

Test.java用于顺序表的各个接口的测试

/**
 * 对顺序表进行测试的代码
 */
public class Test {
    public static void main(String[] args) {
        MyArraysList myArraysList = new MyArraysList();
        // 尾插四个数字
        myArraysList.addTail(12);
        myArraysList.addTail(32);
        myArraysList.addTail(17);
        myArraysList.addTail(32);
        // 进行扩容
        myArraysList.expand();
        // 指定在pos位置新增元素
        myArraysList.addPos(1, 777);
        // 打印当前的顺序表
        System.out.print("第一次测试的顺序表为:");
        myArraysList.display();
        // 删除顺序表中首次出现的元素32
        myArraysList.removeKey(32);
        // 获取顺序表中 1下标的元素值
        int tmp = myArraysList.getPos(1);
        System.out.println("顺序表中下标为1的元素的值为:" + tmp);
        // 给顺序表中1下标的元素设为value
        myArraysList.setPos(1, 100);
        // 打印此时的顺序表
        System.out.print("修改后第二次测试的顺序表为:"); // 此时1下标的值为100
        myArraysList.display();
        // 顺序表此时的有效长度
        System.out.println("顺序表的有效长度为:" + myArraysList.size());
        // 清空顺序表
        myArraysList.clear();
        System.out.print("第三次测试的顺序表为:");
        myArraysList.display();
        System.out.println("清空顺序表后,顺序表的有效长度为:" + myArraysList.size());
    }
}


三.ArrayList 讲解


       实现了List接口,底层为动态类型顺序表,也就是说在Java中,已经写好了顺序表,我们可以在了解顺序表的基础原理之后,我们就可以更好的使用该类。

代码实现:

public static void main(String[] args) {
// ArrayList创建,推荐写法
// 构造一个空的列表
List<Integer> list1 = new ArrayList<>();
// 构造一个具有10个容量的列表
List<Integer> list2 = new ArrayList<>(10);
list2.add(1);
list2.add(2);
list2.add(3);
// list2.add("hello"); // 编译失败,List<Integer>已经限定了,list2中只能存储整形元素
// list3构造好之后,与list中的元素一致
ArrayList<Integer> list3 = new ArrayList<>(list2);
// 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
List list4 = new ArrayList();
list4.add("111");
list4.add(100);
}


ArrayList使用的方法


 方法         解释
boolean add(E e) 尾插e
void add(int index,E element) 将e插入index的位置
boolean addAII(Collection <?extendsE>c 尾插c中的元素
E remove(int index) 删除index位置元素
boolean remove(Object o) 删除遇见的第一个 o
E get(int index) 获取下标index 位置元素
E set(int index,E element) 将下标index 位置元素设置为element
void clear() 清空
boolean contains(Object o) 判断o是否在线性表内



代码实现:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("JavaSE");
    list.add("JavaWeb");
    list.add("JavaEE");
    list.add("JVM");
    list.add("测试课程");
    System.out.println(list);
// 获取list中有效元素个数
System.out.println(list.size());
// 获取和设置index位置上的元素,注意index必须介于[0, size)间
System.out.println(list.get(1));
list.set(1, "JavaWEB");
System.out.println(list.get(1));
// 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
list.add(1, "Java数据结构");
System.out.println(list);
// 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
list.remove("JVM");
System.out.println(list);
// 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
list.remove(list.size()-1);
System.out.println(list);
// 检测list中是否包含指定元素,包含返回true,否则返回false
if(list.contains("测试课程")){
list.add("测试课程");
} 
}



四.总结


      数据结构是一门单独的学科,千位千位不要理解为只有Java才有,C语言等其他语言也能够实现此物理逻辑,C语言实现顺序表:C语言的顺序表构建

目录
相关文章
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
90 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
21天前
|
监控 Java API
如何使用Java语言快速开发一套智慧工地系统
使用Java开发智慧工地系统,采用Spring Cloud微服务架构和前后端分离设计,结合MySQL、MongoDB数据库及RESTful API,集成人脸识别、视频监控、设备与环境监测等功能模块,运用Spark/Flink处理大数据,ECharts/AntV G2实现数据可视化,确保系统安全与性能,采用敏捷开发模式,提供详尽文档与用户培训,支持云部署与容器化管理,快速构建高效、灵活的智慧工地解决方案。
|
1月前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
1月前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
48 4
|
1月前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
2月前
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
71 3
|
2月前
|
Java 程序员 编译器
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。本文通过示例详细解析了保留字的定义、作用及与自定义标识符的区别,帮助开发者避免因误用保留字而导致的编译错误,确保代码的正确性和可读性。
56 3
|
2月前
|
移动开发 Java 大数据
深入探索Java语言的核心优势与现代应用实践
【10月更文挑战第10天】深入探索Java语言的核心优势与现代应用实践
85 4
|
2月前
|
存储 Java
数据结构第二篇【关于java线性表(顺序表)的基本操作】
数据结构第二篇【关于java线性表(顺序表)的基本操作】
36 6
|
2月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
26 3