数据结构之ArrayList与顺序表(上)

news/2024/7/5 2:46:01

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程(ಥ_ಥ)-CSDN博客

所属专栏:数据结构(Java版)

顺序表的学习,点我

上面这篇博文是关于顺序表的基础知识,以及顺序表的实现。

目录

手动实现顺序表的源码:

分析Java 8 的 ArrayList 的源码 

字段:

构造方法:

ArrayList本身的扩容机制和分配内存机制

ArrayList常见操作

ArrayList的遍历 

普通for循环遍历

for-each遍历 

toString方法遍历 

迭代器遍历 


手动实现顺序表的源码:

下面是Java版的顺序表源码:

public class MyArraylist {
    public int[] elem;
    public int usedSize;//0
    //默认容量
    private static final int DEFAULT_SIZE = 10;

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

    /**
     * 打印顺序表:
     *   根据usedSize判断即可
     */
    public void display() {
        for (int i = 0; i < this.usedSize; i++) {
            System.out.print(this.elem[i]+" ");
        }
        System.out.println();
    }

    // 新增元素,默认在数组最后新增
    public void add(int data) {
        // 增加元素之前得先判断数组是否已满
        if (isFull()) {
            expansion();
        }
        // 尾插数据不需要判断 pos 位置是否合法
        elem[this.usedSize++] = data;
    }

    // 扩容
    private void expansion() {
        // 原来数组的2倍扩容
        this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
    }

    /**
     * 判断当前的顺序表是不是满的!
     * @return true:满   false代表空
     */
    public boolean isFull() {
        // 判断这个数组的有效元素个数是否等于数组的长度
        return this.usedSize == this.elem.length;
    }


    private boolean checkPosInAdd(int pos) {
        if (pos < 0 || pos > this.usedSize) {
            return false;
        }
        return true;//合法
    }

    // 在 pos 位置新增元素
    public void add(int pos, int data) throws PosofAddException{
        if (!checkPosInAdd(pos)) {
            throw new PosofAddException("add(int pos, int data) " +
                    "pos位置不合法");
        }
        // 判断是否为尾插
        if (pos == this.usedSize) {
            add(data);
        }else {
            // 开始移除元素(从后往前移除)
            for (int i = this.usedSize; i > pos; i--) {
                this.elem[i] = this.elem[i-1];
            }
            // 开始插入数据
            this.elem[pos] = data;
            this.usedSize++;
        }
    }

    // 判定是否包含某个元素
    public boolean contains(int toFind) {
        // 先判断这个数组是否有元素
        if (isEmpty()) {
            return false;
        }
        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;
            }
        }
        return -1;
    }

    // 获取 pos 位置的元素
    public int get(int pos) throws PosofGetException{
        if (pos < 0 || pos > this.usedSize) {
            throw new PosofGetException("get(int pos)" +
                    "pos位置不合法");
        }
        return this.elem[pos];
    }

    private boolean isEmpty() {
        // 有效数据个数为0,就是为空
        return this.usedSize == 0;
    }

    // 给 pos 位置的元素设为【更新为】 value
    public void set(int pos, int value) throws PosofSetException{
        // 先得判断这个 pos 位置是否合法
        if (pos < 0 || pos >= this.usedSize) {
            throw new PosofSetException("set(int pos, int value)" +
                    "要修改的位置不合法");
        }
        this.elem[pos] = value;
    }

    /**
     * 删除第一次出现的关键字key
     * @param key
     */
    public void remove(int key) throws PosofRemoveException{
        int pos = indexOf(key); // 下标
        if (pos < 0) {
            throw new PosofRemoveException("remove(int key)" +
                    "没有您要删除的数据");
        }
        for (int i = pos; i < this.usedSize - 1; i++) {
            // 后一个位置往前覆盖
            this.elem[i] = this.elem[i+1];
        }
        this.usedSize--;
    }

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

    // 清空顺序表
    public void clear() {
        // 由于存放的是基本数据类型,直接清空即可
        this.usedSize = 0;
        // 如果存放的是引用数据类型就得通过for循环遍历把数组的内容置为null
        // 注意:如果直接把数组置为null的话,就会存在安全问题,而且源码也是遍历的方式
    }
}

异常:

PosofAddException:

public class PosofAddException extends RuntimeException{
    public PosofAddException() {

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

 PosofGetException:

public class PosofGetException extends RuntimeException{
    public PosofGetException() {

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

PosofSetException: 

public class PosofSetException extends RuntimeException{
    public PosofSetException() {

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

PosofRemoveException: 

public class PosofRemoveException extends RuntimeException{
    public PosofRemoveException() {

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

分析Java 8 的 ArrayList 的源码 

实现了咱们自己写的顺序表了之后,就该来看Java本身的源码是怎么写的以及与我们的有什么不同?(注意:由于Java 17封装性太强,不好观看源码,因此下面的源码来自Java 8。)

字段:

elementData 就是我们自己实现的 elem 数组。

size 就是 usedSize ,也就是这个数组的有效元素的个数。 

构造方法:

Java 8 实现的顺序表有三个构造方法。 

带有一个 int 类型的参数的构造方法: 

 不带参数的构造方法:

带一个 Collection 类型的参数的构造方法:

下面的是其源码: 

首先,我们得知道Collection 到底是这个啥? 

根据上面这个图可以得知:Collection 是一个接口。

上面的参数先不看泛型,那就是Collection c  这个意味着只要实现了Collection 接口,就可以被当成实参传过来。而从上图的关系可知:ArrayList 继承了 AbstractLIst 这个抽象类 ,并且实现了LIst这个接口,而 LIst 这个接口拓展了 Collection 这个接口的功能。也就意味着 ArrayList 这个类实现了 Collection 这个接口。那么 ArrayList 就可以被当成参数传过来。

接下来,就是了解 <? extends E> ,?就可以当成是被传过来的 ArrayList 中的泛型,也就是说被传过来的 ArrayList 中的泛型要是 E 或者 E的子类。例如:

了解了这些,我们就来看源码吧。这个源码的大概意思是把传入的 ArrayList 中的元素给全部拷贝到原来的 ArrayList 的后面。

ArrayList本身的扩容机制和分配内存机制

既然我们在创建一个顺序表的时候,原本是空,那么当我们去增加元素的时候,扩容的机制又是如何呢?

上面是分析过程(可忽略),总之,得出了下面这个结论: 

当顺序表为空时,我们如果去增加元素,就会初始化一个大小10的数组。并且数组在满了之后,会按照1.5倍的扩容方式来扩容。如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容。

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)判断是否在顺序表中
int indexOf(Object o)返回第一个o所在下标
int lastlndexOf(Object o)返回最后一个o的下标
List<E> subList(int fromlndex, int tolndex)截取部分list

大部分方法,我们都很熟悉。因此这里只展示 addAll()方法 和 subList 方法。

addAll () 方法可以实现 ArrayList 的带Collection 类型的参数的构造方法。

从上面打印的结果可以看出来,ArrayList 是重写了toString 方法的。

从这里我们就可以看出来,被分割的数组应该是下面这样:

ArrayList的遍历 

普通for循环遍历

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        for (int i = 0; i < arrayList.size(); i++) {
            System.out.print(arrayList.get(i)+" ");
        }
    }
}

for-each遍历 

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        for (Integer x : arrayList) {
            System.out.print(x+" ");
        }
    }
}

toString方法遍历 

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        System.out.println(arrayList.toString());
    }
}

迭代器遍历 

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        // 调用iterator方法生成一个迭代器,用Iterator<E>来接收
        Iterator<Integer> integerIterator = arrayList.iterator();
        // 如果下一个元素有值话,就进入while循环,并且打印下一个值,最后自己往后走
        while (integerIterator.hasNext()) {
            System.out.print(integerIterator.next()+" ");
        }
    }
}
        

只要是实现了 Iterator 接口就可以使用迭代器的方式来遍历。就是下面这张图:

好啦!本期 数据结构之ArrayList与顺序表(上)的学习就到此结束啦!我们下一期再一起学习吧!


http://lihuaxi.xjx100.cn/news/2196551.html

相关文章

国标GB/T 28181详解:国标GBT28181-2022的客户端主动发起历史视音频回放流程

目录 一、定义 二、作用 1、提供有效的数据回顾机制 2、增强监控系统的功能性 3、保障数据传输与存储的可靠性 4、实现精细化的操作与控制 5、促进监控系统的集成与发展 三、历史视音频回放的基本要求 四、命令流程 1、流程图 2、流程描述 五、协议接口 1、会话控…

短视频直播教学课程小程序的作用是什么

只要短视频/直播做的好&#xff0c;营收通常都不在话下&#xff0c;近些年&#xff0c;线上自媒体行业热度非常高&#xff0c;每条细分赛道都有着博主/账号&#xff0c;其各种优势条件下也吸引着其他普通人冲入。 然无论老玩家还是新玩家&#xff0c;面对平台不断变化的规则和…

易语言QQ机器人2.0源码

易语言QQ机器人2.0 效果图源码说明领取源码下期更新预报 效果图 源码说明 .程序集 Smessage, VJ_DirectUI .程序集变量 Format, StringFormat.子程序 _初始化, , , 当基于本类的对象被创建后&#xff0c;此方法会被自动调用.子程序 _销毁, , , 当基于本类的对象被销毁前&#x…

持续总结中!2024年面试必问 20 道分布式、微服务面试题(二)

上一篇地址&#xff1a;持续总结中&#xff01;2024年面试必问 20 道分布式、微服务面试题&#xff08;一&#xff09;-CSDN博客 三、CAP定理是什么&#xff1f; CAP定理是分布式系统理论中的一个基本概念&#xff0c;由计算机科学家Eric Brewer在2000年提出&#xff0c;并由…

Java内部类、枚举类、注解类

Java 是一种面向对象的编程语言&#xff0c;它支持多种类型的类&#xff0c;包括内部类、枚举类和注解类 一、内部类&#xff08;Inner Class&#xff09;&#xff1a; 内部类是定义在另一个类内部的类。它可以访问外部类的成员&#xff08;包括私有成员&#xff09;&#xff…

关于RDMA传输的基本流量控制

Basic flow control for RDMA transfers | The Geek in the Corner (wordpress.com) 文心一言 已经介绍了使用发送/接收操作和RDMA读写操作&#xff0c;那么现在是一个很好的机会来结合这两种方法的元素&#xff0c;并讨论一般的流量控制。还会稍微谈谈RDMA带有立即数据的写操…

【操作系统】进程与线程的区别及总结(非常非常重要,面试必考题,其它文章可以不看,但这篇文章最后的总结你必须要看,满满的全是干货......)

目录 一、 进程1.1 PID(进程标识符)1.2 内存指针1.3 文件描述符表1.4 状态1.5 优先级1.6 记账信息1.7 上下文 二、线程三、总结&#xff1a;进程和线程之间的区别&#xff08;非常非常非常重要&#xff0c;面试必考题&#xff09; 一、 进程 简单来介绍一下什么是进程&#xf…

vue3路由详解,从0开始手动配置路由(vite,vue-router)

创建一个不含路由的vue项目 &#xff08;查看路由配置可以直接跳过这一段&#xff09; 输入npm指令&#xff0c;然后写一个项目名称&#xff0c;之后一路回车即可 npm create vuelatest 注意这里我们不选引入vue router&#xff0c;成功后可以 查看目录 然后按提示信息输入指…