数据结构(Java)—— ArrayList

1.线性表

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

2. ArrayList 简介

在集合框架中, ArrayList 是一个普通的类,实现了 List 接口,具体框架图如下:
说明:
1. ArrayList 是以泛型方式实现的,使用时必须要先实例化
2. ArrayList 实现了 RandomAccess 接口,表明 ArrayList 支持随机访问
3. ArrayList 实现了 Cloneable 接口,表明 ArrayList 是可以 clone
4. ArrayList 实现了 Serializable 接口,表明 ArrayList 是支持序列化的
5. Vector 不同, ArrayList 不是线程安全的,在单线程下可以使用,在多线程中可以选择 Vector 或者 CopyOnWriteArrayList
6. ArrayList 底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表

3. ArrayList 的使用

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

3.1 ArrayList 的构造方法

方法说明
ArrayList()
无参构造
ArrayList (Collection<? extends E> c)
利用其他 Collection 构建 ArrayList
ArrayList (int initialCapacity)
指定顺序表初始容量

代码示例:

public static void main(String[] args) {
        // ArrayList创建,推荐写法
        // 构造一个空的列表
        List<Integer> list = new ArrayList<>();
        ArrayList<Integer> a1 = new ArrayList<>();
        a1.add(1);
        a1.add(2);
        a1.add(3);
        System.out.println(a1);
        System.out.println("==============");
        // 构造一个具有10个容量的列表
        ArrayList<Integer> a2 = new ArrayList<>(10);
        a2.add(11);
        a2.add(22);
        a2.add(33);
        System.out.println(a2);
        System.out.println("===============");
        // a3构造好之后,与a2中的元素一致
        ArrayList<Integer> a3 = new ArrayList<>(a2);
        System.out.println(a3);
    }

运行结果:

注意:
1.List 是一个接口,通过list访问的是接口中的方法,是常用的构造方法
2.ArrayList的底层逻辑就是一个数组;
3. 第一种构造方法虽然没有定义表的容量,只是定义了一个数组引用;但是Add()方法会对没有容量的表进行定容;
4. 第二种构造方法定义了表的容量;
5.第三种构造方法是实现了 Collection 接口的,并且参数的类型时当前 a3 指定的泛型类型本身

3.2 ArrayList常见操作

ArrayList虽然提供的方法比较多,但是常用方法如下所示,需要用到其他方法时,兄弟们可以自行查看ArrayList的帮助文档。

方法说明
boolean add(E e)
尾插 e
void add(int index, E element) e 插入到 index 位置
boolean addAll(Collection<? extends E> 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 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List<E> subList(int fromIndex, int toIndex)截取部分 list

代码示例:

 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);
        System.out.println("==================");
// 获取list中有效元素个数
        System.out.println("size: "+list.size());
        System.out.println("==================");
// 获取和设置index位置上的元素,注意index必须介于[0, size)间
        System.out.println(list.get(1));
        list.set(1, "JavaWEB");
        System.out.println("set and get:" + list.get(1));
        System.out.println("==================");
// 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
        list.add(1, "Java数据结构");
        System.out.println("add(index,element):"+list);
        System.out.println("==================");
// 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
        list.remove("JVM");
        System.out.println("remove"+list);
        System.out.println("==================");
// 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
        list.remove(list.size()-1);
        System.out.println("remove(index)"+list);
        // 检测list中是否包含指定元素,包含返回true,否则返回false
        if(list.contains("测试课程")){
            list.add("测试课程");
            System.out.println("contains");
            System.out.println("==================");
        }
// 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
        list.add("JavaSE");
        System.out.println("list.indexOf:"+list.indexOf("JavaSE"));
        System.out.println("list.lastIndexOf:"+list.lastIndexOf("JavaSE"));
        System.out.println("==================");
// 使用list中[0, 4)之间的元素构成一个新的SubList返回,但是和ArrayList共用一个elementData数组
        List<String> ret = list.subList(0, 4);
        System.out.println("subList:"+ret);
        System.out.println("==================");
        list.clear();
        System.out.println("clear:"+list.size());

    }

运行结果如下:

3.3 ArrayList的遍历

ArrayList 可以使用三种方式遍历: for 循环 + 下标、 foreach 、使用迭代器
代码如下:
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
         // 使用下标+for遍历
        System.out.println("=====fori====");
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
        System.out.println();
     // 借助foreach遍历
        System.out.println("=====foreach====");
        for (Integer integer : list) {
            System.out.print(integer + " ");
        }
        System.out.println();
        System.out.println("=====Iterator====");
        Iterator<Integer> it = list.listIterator();
        while(it.hasNext()){
            System.out.print(it.next() + " ");
        }
    }

运行结果如下:

注意
 1. ArrayList最常使用的遍历方式是:for
循环+下标 以及 foreach
 2. 迭代器是设计模式的一种

3.4 ArrayList的扩容机制

ArrayList 是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。以下是 ArrayList 源码中扩容方式(以JDK8为例):
Object[] elementData; // 存放元素的空间
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 默认空间
private static final int DEFAULT_CAPACITY = 10; // 默认容量大小
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// 获取旧空间大小
int oldCapacity = elementData.length;
// 预计按照1.5倍方式扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用copyOf扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
// 如果minCapacity小于0,抛出OutOfMemoryError异常
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

总结:
1. 检测是否真正需要扩容,如果是调用grow准备扩容
2. 预估需要库容的大小

  • 初步预估按照1.5倍大小扩容
  • 如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容
  • 真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
3. 使用  copyOf  进行扩容

4. ArrayList的模拟实现(以整型为例)

4.1 定义接口

定义ArrayList的基本功能

// 新增元素,默认在数组最后新增
    void add(int data);
    // 在 pos 位置新增元素
    void add(int pos, int data);
    // 判定是否包含某个元素
    boolean contains(int toFind);
    // 查找某个元素对应的位置
    int indexOf(int toFind);
    // 获取 pos 位置的元素
    int get(int pos);
    // 给 pos 位置的元素设为 value -> 更新
    void set(int pos, int value);
    //删除第一次出现的关键字key
    void remove(int toRemove);
    // 获取顺序表长度
    int size();
    // 清空顺序表
    void clear();
    // 打印顺序表,
    // 注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    void display();

    boolean isFull();

4.2 实现功能

    public int[] elem;
    public int usedSize;

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

    @Override
    public void add(int data) {
        //如果满了 能放吗?
        if(isFull()) {
            //扩容
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        this.elem[usedSize] = data;
        this.usedSize++;
    }

    @Override
    public boolean isFull() {
        return usedSize == elem.length;
    }

    @Override
    public void add(int pos, int data) {
        try {
            checkPosOfAdd(pos);
        }catch (PosNotLegalException e) {
            e.printStackTrace();
        }
        if(isFull()) {
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        //移动元素
        for (int i = usedSize-1; i >= pos; i--) {
            elem[i+1] = elem[i];
        }
        //插入元素
        elem[pos] = data;
        usedSize++;
    }

    /*
    该方法来 判断 添加元素时 pos是否合法
     */
    private void checkPosOfAdd(int pos) throws PosNotLegalException{
        if(pos < 0 || pos > usedSize) {
            throw new PosNotLegalException("pos位置不合法!");
        }
    }

    @Override
    public boolean contains(int toFind) {
        //只需要寻找usedSize次
        for (int i = 0; i < usedSize; i++) {
            if(elem[i] == toFind) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int indexOf(int toFind) {
        for (int i = 0; i < usedSize; i++) {
            if(elem[i] == toFind) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public int get(int pos) {
        try {
            checkPosOfGetAndSet(pos);
        }catch (PosNotLegalException e) {
            e.printStackTrace();
        }
        return elem[pos];
    }

    private void checkPosOfGetAndSet(int pos) throws PosNotLegalException{
        if(pos < 0 || pos >= usedSize) {
            throw new PosNotLegalException("get/set获取元素的时候" +
                    "pos位置不合法!");
        }
    }


    @Override
    public void set(int pos, int value) {
        try {
            checkPosOfGetAndSet(pos);
        }catch (PosNotLegalException e) {
            e.printStackTrace();
        }

        elem[pos] = value;
    }

    @Override
    public void remove(int toRemove) {
        //1、要查找是否存在要删除的关键字 toRemove
        int pos = indexOf(toRemove);
        if(pos == -1) {
            System.out.println("没有要删除的数字!");
            return;
        }
        for (int i = pos; i < usedSize-1; i++) {
            elem[i] = elem[i+1];
        }
        usedSize--;
    }

    @Override
    public int size() {
        return usedSize;
    }

    @Override
    public void clear() {
//类型为包装类时使用
        /*for (int i = 0; i < usedSize; i++) {
            elem[i] = null;
        }*/
        usedSize = 0;
    }

    @Override
    public void display() {
        for (int i = 0; i < usedSize; i++) {
            System.out.print(elem[i] +" ");
        }
        System.out.println();
    }

//自定义异常类
public class PosNotLegalException extends RuntimeException {
    public PosNotLegalException() {

    }
    public PosNotLegalException(String msg) {
        super(msg);
    }
}

注意:

1. 数组长度不代表有效数值个数,使用userdSize来记录有效数值个数

2. 对表进行增删查改时,先确认表是否为空或者满了,再进行操作

3.对表进行指定位置的删查改时,要确认该位置是否合法

4.进行clear时,整型逐一置为0,其他包装类逐一置为null

5.可根据自己需求,自定义异常类进行抛出

4.3 代码测试

MyArrayList myArrayList = new MyArrayList();
    myArrayList.add(1);
    myArrayList.add(2);
    myArrayList.add(3);
        myArrayList.display();
        System.out.println("=======================");
        myArrayList.add(1,4);
      myArrayList.display();
        System.out.println("=======================");
        System.out.println("contains(3):"+myArrayList.contains(3));
        System.out.println("=======================");
        System.out.println("indexOf(2):"+myArrayList.indexOf(2));
        System.out.println("=======================");
        System.out.println("get(1):"+myArrayList.get(1));
        System.out.println("=======================");
        myArrayList.set(0,5);
       myArrayList.display();
        System.out.println("=======================");
        myArrayList.remove(2);
         myArrayList.display();
        System.out.println("=======================");
        System.out.println("size:"+myArrayList.size());
        System.out.println("=======================");
        myArrayList.clear();
        System.out.println("clear:"+myArrayList.size());

运行结果如下:

 

5. ArrayList的优缺点

5.1 优点

  •  随机访问高效:由于ArrayList内部是一个数组,所以通过索引直接访问元素非常快,时间复杂度为O(1)。
  • 增删元素方便:可以在任意位置插入和删除元素,不过插入和删除操作的时间复杂度分别为O(n)(因为需要移动其他元素)。
  • 大小可变:ArrayList可以自动调整容量,无需预先指定固定大小。

   5.2 缺点

  • 空间效率:每次元素增加时,如果当前容量不足以容纳新元素,ArrayList会创建一个新的更大的数组并将所有元素复制过去,这可能导致内存浪费。
  • 插入和删除效率较低:对于频繁的插入和删除操作,尤其是在列表的头部或尾部,ArrayList的性能不如LinkedList。
  • 不适合大量读取而少修改的情况:当数据主要是用于查找而非频繁修改时,像HashMap这样的数据结构可能更合适。

本文是作者学习后的总结,如果有什么不恰当的地方,欢迎大佬指正!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/924160.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Qt】重写QComboBox下拉展示多列数据

需求 点击QComboBox时&#xff0c;下拉列表以多行多列的表格展示出来。 实现 直接上代码&#xff1a; #include <QComboBox> #include <QTableWidget> #include <QVBoxLayout> #include <QWidget> #include <QEvent> #include <QMouseEve…

NVR录像机汇聚管理EasyNVR多个NVR同时管理基于B/S架构的技术特点与能力应用

EasyNVR视频融合平台基于云边端协同设计&#xff0c;能够轻松接入并管理海量的视频数据。该平台兼容性强、拓展灵活&#xff0c;提供了视频监控直播、录像存储、云存储服务、回放检索以及平台级联等一系列功能。B/S架构使得EasyNVR实现了视频监控的多元化兼容与高效管理。 其采…

了解网络威胁情报:全面概述

网络威胁情报 CTI 是指系统地收集和分析与威胁相关的数据&#xff0c;以提供可操作的见解&#xff0c;从而增强组织的网络安全防御和决策过程。 在数字威胁不断演变的时代&#xff0c;了解网络威胁情报对于组织来说至关重要。复杂网络攻击的兴起凸显了制定强有力的策略以保护敏…

linux运行vue编译后的项目

如果你的 Vue 项目使用了 history 模式&#xff08;而非默认的 hash 模式&#xff09;&#xff0c;在纯静态服务器中会出现类似的问题。因为 Vue Router 的 history 模式要求所有未匹配的路径都重定向到 index.html&#xff0c;以便 Vue 前端处理路径。 首先在本地执行npm run…

基础入门-Web应用架构类别源码类别镜像容器建站模版编译封装前后端分离

知识点&#xff1a; 1、基础入门-Web应用-搭建架构上的技术要点 2、基础入门-Web应用-源码类别上的技术要点 一、演示案例-架构类别-模版&分离&集成&容器&镜像 1、套用模版型 csdn / cnblog / github / 建站系统等 安全测试思路上的不同&#xff1a; 一般…

使用Github Action将Docker镜像转存到阿里云私有仓库,供国内服务器使用,免费易用

文章目录 一、前言二、 工具准备&#xff1a;三、最终效果示例四、具体步骤第一大部分是配置阿里云1. 首先登录阿里云容器镜像服务 [服务地址](https://cr.console.aliyun.com/cn-hangzhou/instances)2. 选择个人版本3. 创建 命名空间4. 进入访问凭证来查看&#xff0c;用户名字…

Goland或Idea启动报错

Goland或Idea启动不了 报错如图&#xff1a; 原因&#xff1a;破解导致 解决方案 环境变量中有关Goland的全部删除

keepalived+lVS(dr)高可用集群

keepalivedlVS(dr)高可用集群 规划 服务器名称IP描述masterkeepalivedlvsVIP:192.168.238.100DIP:192.168.238.151keepalived的master节点和lvs负载均衡backupkeepalivedlvsVIP:192.168.238.100DIP:192.168.238.152keepalived的备份节点和lvs负载均衡server1VIP:192.168.238.…

探索.NET世界的无限可能——带你轻松了解.NET

前言 由于目前用到的技术栈有C#&#xff0c;而学习C#离不开.NET框架&#xff0c;正如学习Java离不开学习Spring框架一样。 .NET是微软开发的一个非常强大的框架&#xff0c;它不仅擅长桌面和移动开发&#xff0c;而且还能够支持Web开发和游戏引擎开发&#xff0c;在现在热门的…

web3.js + Ganache 模拟以太坊账户间转账

转账前&#xff1a; 转账后&#xff1a; async function interact() {const web3 new Web3(new Web3.providers.HttpProvider(http://127.0.0.1:7545))web3.eth.Contract.handleRevert trueconst accounts await web3.eth.getAccounts()console.log(accounts)let balance1, …

题解 洛谷 Luogu P1182 数列分段 Section II 二分答案 C/C++

题目传送门&#xff1a; P1182 数列分段 Section II - 洛谷 | 计算机科学教育新生态https://www.luogu.com.cn/problem/P1182思路&#xff1a; 二分答案&#xff0c;每次以区间 [l, r] 中点 m 为每段和的阈值 判断在此前提下&#xff0c;划分段数是否不大于 M 是就记录答案…

26.100ASK_T113-PRO 测试摄像头 输出信息

1.测试代码 读到摄象头参数 输出 video_test.c #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <linux/type…

git使用文档手册

创建一个本地代码工作空间&#xff0c;比如这里使用test目录作为工作目录 针对仓库地址 http://192.168.31.125:9557/poxiaoai-crm/project-crm.git。 1. 安装 Git 确保您的系统已经安装了 Git。如果未安装&#xff0c;请根据操作系统访问 Git 官网 下载并安装。 验证安装 …

HTML5和CSS3新增特性

HTML5的新特性 HTML5新增的语义化标签 HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。 这些新特性都有兼容性问题&#xff0c;基本是 IE9 以上版本的浏览器才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量…

BUUCTF—Reverse—不一样的flag(7)

是不是做习惯了常规的逆向题目&#xff1f;试试这道题&#xff0c;看你在能不能在程序中找到真正的flag&#xff01;注意&#xff1a;flag并非是flag{XXX}形式&#xff0c;就是一个’字符串‘&#xff0c;考验眼力的时候到了&#xff01; 注意&#xff1a;得到的 flag 请包上 f…

通信与网络安全之IPSEC

IPSec&#xff08;IP Security&#xff09;是IETF制定的为保证在Internet上传送数据的安全保密性能的三层隧道加密协议。IPSec在网络层对IP报文提供安全服务。IPSec协议本身定义了如何在IP数据包中增加字段来保证IP包的完整性、 私有性和真实性&#xff0c;以及如何加密数据包。…

树莓派搭建NextCloud:给数据一个安全的家

前言 NAS有很多方案&#xff0c;常见的有 Nextcloud、Seafile、iStoreOS、Synology、ownCloud 和 OpenMediaVault &#xff0c;以下是他们的特点&#xff1a; 1. Nextcloud 优势&#xff1a; 功能全面&#xff1a;支持文件同步、共享、在线文档编辑、视频会议、日历、联系人…

AWS账户注册未完成会收费吗?

在当今云计算的时代&#xff0c;亚马逊网络服务&#xff08;AWS&#xff09;已经成为众多企业和开发者的首选平台。然而&#xff0c;对于许多刚接触云服务的人来说&#xff0c;关于AWS账户注册的费用问题常常引发疑虑&#xff1a;如果我在注册过程中未能完成操作&#xff0c;是…

在线音乐播放器 —— 测试报告

自动化脚本源代码&#xff1a;Java: 利用Java解题与实现部分功能及小项目的代码集合 - Gitee.com 目录 前言 一、项目简介 1.项目背景 2.应用技术 &#xff08;1&#xff09;后端开发 &#xff08;2&#xff09;前端开发 &#xff08;3&#xff09;数据库 二、项目功能…

TCP/IP协议攻击与防范

一、TCP/IP协议攻击介绍 1.1 Internet的结构​ LAN&#xff1a;局域网 WAN&#xff1a;广域网 WLAN&#xff1a;无线局域网 私有IP地址与公有IP地址&#xff1f; 私有地址&#xff1a;A类&#xff1a;10.0.0.0~10.255.255.255 B类&#xff1a;172.16.0.0~172.31.255.255…