数据结构-基于ArrayList的源码模拟

文章目录

      • 继承关系 :
      • 1. 构造方法的模拟
      • 2. 扩容机制的分析
      • 3. 查找方法的模拟
      • 4. 获取,修改元素的方法模拟
      • 5. 添加元素的模拟
      • 6. 删除元素的模拟
      • 7. removeAll与retainAll的模拟
      • 总结: 边缘方法以及总代码

继承关系 :

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1. 构造方法的模拟

源码中我们的ArrayList的构造方法给出了三种实现
分别是不带参数的,带一个参数的,还有一个参数是类的
模拟实现如下

 /**
     * 在我们ArrayList源码里面,提供了三种构造方法(简单模拟一下)
     */
    public MyArrayList() {
        elementData = EMPTY_ELEMENTDATA;
    }

    public MyArrayList(int ininCapacity) {
        if (ininCapacity < 0) {
            throw new RuntimeException("不是哥们,大小不能是负数啊");
        }
        this.elementData = new Object[ininCapacity];
    }

    public MyArrayList(MyArrayList<? extends T> c) {
        Object[] cArr = c.toArray();
        this.elementData = new Object[cArr.length];
        System.arraycopy(cArr, 0, this.elementData, 0, cArr.length);
        this.size = cArr.length;
    }

2. 扩容机制的分析

有些人很好奇,为什么我创建对象的时候没有容量,但却可以添加元素呢,下面就是我们扩容方法模拟,其实源码底层基于的是grow()函数,其实JDK17关于扩容这一块是比较复杂的(底层又去调用了ArraySupport类中的相关方法),如果想了解的话还是自行查看源码吧
代码实现如下

/**
* 扩容机制解析
* 这个方法getNewLength我们解释一下到底是咋回事,我们想要获得一个最适合的扩容机制
* 第一个参数是原有的容量,第二个参数是最小的容量增长量,第三个参数是最合适的增长量(1.5倍率)
* 那就有问题了,为什么不直接用这个最合适的1.5倍率呢?
* 思考,当我的oldCapacity很小的时候,比如1 , 1>>1 == 0
* 那就意味着此时如果直接用1.5倍率增长是无法完成的
*/

public void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newLength = getNewLength(oldCapacity, minCapacity - oldCapacity, oldCapacity >> 1);
        this.elementData = Arrays.copyOf(this.elementData, newLength);
    }

    public void grow() {
        grow(size + 1);
    }

    private int getNewLength(int oldCapacity, int minGrowCapacity, int preferCpacity) {
        int newLength = oldCapacity + Math.max(minGrowCapacity, preferCpacity);
        return newLength;
    }

3. 查找方法的模拟

这个就没什么可说的了,注意一点就是引用类型比较的时候是要用我们的equals方法进行比较的,还有就是源码这里实现的时候是通过先进行null的查找
代码实现如下
从前向后查找和从后向前查找

public int indexOf(Object o) {
        return indexOfRange(o, 0, size);
    }

    private int indexOfRange(Object o, int fromIndex, int toIndex) {
        Object[] es = elementData;
        //为什么要把这二者区分开的原因就是,引用数据类型不可以直接相等
        if (es == null) {
            for (int i = fromIndex; i < toIndex; i++) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = fromIndex; i < toIndex; i++) {
                if (es[i].equals(o)) {
                    return i;
                }
            }
        }
        return -1;
    }

    public int lastIndexOf(Object o) {
        return lastIndexOfRange(o, size, 0);
    }

    public int lastIndexOfRange(Object o, int fromIndex, int toIndex) {
        Object[] es = elementData;
        if (o == null) {
            for (int i = fromIndex - 1; i >= toIndex; --i) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = fromIndex - 1; i >= toIndex; --i) {
                if (es[i].equals(o)) {
                    return i;
                }
            }
        }
        return -1;
    }

4. 获取,修改元素的方法模拟

代码实现如下

/**
     * 获取元素,修改元素的方法
     */
    private void checkIndexRange(int index) {
        if (index < 0 || index > size) {
            throw new RuntimeException("下标不合法");
        }
    }

    public T get(int index) {
        checkIndexRange(index);
        return (T) elementData[index];
    }

    public void set(int index, T elem) {
        checkIndexRange(index);
        elementData[index] = elem;
    }

5. 添加元素的模拟

这个有一个比较坑的点就是我们在进行Array.copyOf调用的时候,会修改源数组空间的指向,也就是如果在这之前进行过数组指向的约定,要重写修改指向

/**
     * 添加元素的方法
     * 请注意这里有一个小点是比较坑的,就是数组完成扩容之后,原来的数组指向是要进行修改的
     */
    private void add(T elem, Object[] elementData, int sz) {
        if (elementData.length == sz) {
            grow();
        }
        this.elementData[size] = elem;
        size++;
    }

    public boolean add(T elem) {
        add(elem, this.elementData, this.size);
        return true;
    }

    public boolean add(int index, T elem) {
        checkIndexRange(index);
        if (this.elementData.length == this.size) {
            grow();
        }
        //这里我们源代码的移动元素的操作是借助System.arraycopy完成的(final不会修改数组指向)
        final Object[] es = this.elementData;
        System.arraycopy(es, index, es, index + 1, this.size - index);
        es[index] = elem;
        this.size++;
        return true;
    }

    public boolean addAll(MyArrayList<? extends T> c) {
        Object[] arr = c.toArray();
        int numsLength = arr.length;
        if (numsLength == 0) {
            return false;
        }
        Object[] es = this.elementData;
        grow(c.size + this.size);
        //重新改变es的指向
        es = this.elementData;
        System.arraycopy(arr, 0, es, this.size, arr.length);
        this.size = this.size + arr.length;
        return true;
    }

6. 删除元素的模拟

源码的这里使用一个fastRemove方法进行操作的,其实也就是System.arraycopy,这个方法底层是cpp/c代码(其实我怀疑是memmove)…
代码实现如下

/**
     * 删除元素的操作(源码里面是借助一个fastRemove的方法完成的,其实也就是arraycopy)
     */
    public T remove(int index) {
        checkIndexRange(index);
        final Object[] es = elementData;
        int newSize = size - 1;
        T oldVal = (T) es[index];
        //这里主要考虑的是尾删除的弊端(尾部删除无法直接进行覆盖)
        if (newSize > index) {
            System.arraycopy(es, index + 1, es, index, newSize - index);
        }
        es[size = newSize] = null;
        return oldVal;
    }

    public boolean remove(Object o) {
        final Object[] es = elementData;
        int index = -1;
        if (o == null) {
            for (int i = 0; i < size; ++i) {
                if (es[i] == null) {
                    index = i;
                    break;
                }
            }
        } else {
            for (int i = 0; i < size; ++i) {
                if (o.equals(es[i])) {
                    index = i;
                    break;
                }
            }
        }
        if (index == -1) {
            return false;
        }
        //到这里说明已经找到了
        int newSize = size - 1;
        if (newSize > index) {
            System.arraycopy(es, index + 1, es, index, newSize - index);
        }
        es[size = newSize] = null;
        return true;
    }

7. removeAll与retainAll的模拟

这两个方法比较特殊所以单拎出来

/**
* 源码中关于下面两个方法的解释可以自己去看,机制相对复杂,但是跟我下面模拟的思路是差不多的
* 这个题的最坑的点就是,切记! ! !,不要在一遍删除元素的同时去改变我们的空间大小界限(一般都会出错)
* 这两个方法就是第一个removeAll是清除掉c中含有的元素
* 第二个方法就是保留下来c中的元素,其他都进行删除
*/
代码实现如下

 public void removeAll(MyArrayList<? extends T> c) {
        final Object[] es = this.elementData;
        int sz = this.size;
        for (int i = 0; i < this.size; ++i) {
            while (c.contains(es[i])) {
                System.arraycopy(es, i + 1, es, i, this.size - 1 - i);
                sz--;
                if (sz < 0) {
                    break;
                }
                es[sz] = null;
            }
        }
        this.size = sz;
    }

    public void retainAll(MyArrayList<? extends T> c) {
        Object[] temp = Arrays.copyOf(c.elementData, c.elementData.length);
        final Object[] es = c.elementData;
        int sz = c.size;
        for (int i = 0; i < c.size; ++i) {
            while (c.contains(es[i])) {
                System.arraycopy(c, i + 1, c, i, this.size - 1 - i);
                sz--;
                if (sz < 0) {
                    break;
                }
                c.elementData[sz] = null;
            }
        }
        this.size = sz;
        this.elementData = c.elementData;
        c.elementData = temp;
    }

总结: 边缘方法以及总代码

边缘方法都夹在总代码实现里面了,自己查看即可


class MyArrayList<T> {
    private Object[] elementData;
    private int size;
    private static final int DEAFULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 在我们ArrayList源码里面,提供了三种构造方法(简单模拟一下)
     */
    public MyArrayList() {
        elementData = EMPTY_ELEMENTDATA;
    }

    public MyArrayList(int ininCapacity) {
        if (ininCapacity < 0) {
            throw new RuntimeException("不是哥们,大小不能是负数啊");
        }
        this.elementData = new Object[ininCapacity];
    }

    public MyArrayList(MyArrayList<? extends T> c) {
        Object[] cArr = c.toArray();
        this.elementData = new Object[cArr.length];
        System.arraycopy(cArr, 0, this.elementData, 0, cArr.length);
        this.size = cArr.length;
    }

    public void trimToSize() {
        //注意,通过System.arraycopy拷贝是不能直接改变空间大小的
        if (size < elementData.length) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }

    /**
     * 扩容机制解析
     * 这个方法getNewLength我们解释一下到底是咋回事,我们想要获得一个最适合的扩容机制
     * 第一个参数是原有的容量,第二个参数是最小的容量增长量,第三个参数是最合适的增长量(1.5倍率)
     * 那就有问题了,为什么不直接用这个最合适的1.5倍率呢?
     * 思考,当我的oldCapacity很小的时候,比如1 , 1>>1 == 0
     * 那就意味着此时如果直接用1.5倍率增长是无法完成的
     */
    public void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newLength = getNewLength(oldCapacity, minCapacity - oldCapacity, oldCapacity >> 1);
        this.elementData = Arrays.copyOf(this.elementData, newLength);
    }

    public void grow() {
        grow(size + 1);
    }

    private int getNewLength(int oldCapacity, int minGrowCapacity, int preferCpacity) {
        int newLength = oldCapacity + Math.max(minGrowCapacity, preferCpacity);
        return newLength;
    }

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

    public boolean isEmpty() {
        return this.size == 0;
    }

    /**
     * 查找方法的分析
     * 查找方法就是找到从前或者从后开始的第一个满足查找条件的数据类型的下标
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    public int indexOf(Object o) {
        return indexOfRange(o, 0, size);
    }

    private int indexOfRange(Object o, int fromIndex, int toIndex) {
        Object[] es = elementData;
        //为什么要把这二者区分开的原因就是,引用数据类型不可以直接相等
        if (es == null) {
            for (int i = fromIndex; i < toIndex; i++) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = fromIndex; i < toIndex; i++) {
                if (es[i].equals(o)) {
                    return i;
                }
            }
        }
        return -1;
    }

    public int lastIndexOf(Object o) {
        return lastIndexOfRange(o, size, 0);
    }

    public int lastIndexOfRange(Object o, int fromIndex, int toIndex) {
        Object[] es = elementData;
        if (o == null) {
            for (int i = fromIndex - 1; i >= toIndex; --i) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = fromIndex - 1; i >= toIndex; --i) {
                if (es[i].equals(o)) {
                    return i;
                }
            }
        }
        return -1;
    }

    //toArray方法就是将这个东西转换为数组
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    /**
     * 获取元素,修改元素的方法
     */
    private void checkIndexRange(int index) {
        if (index < 0 || index > size) {
            throw new RuntimeException("下标不合法");
        }
    }

    public T get(int index) {
        checkIndexRange(index);
        return (T) elementData[index];
    }

    public void set(int index, T elem) {
        checkIndexRange(index);
        elementData[index] = elem;
    }

    /**
     * 添加元素的方法
     * 请注意这里有一个小点是比较坑的,就是数组完成扩容之后,原来的数组指向是要进行修改的
     */
    private void add(T elem, Object[] elementData, int sz) {
        if (elementData.length == sz) {
            grow();
        }
        this.elementData[size] = elem;
        size++;
    }

    public boolean add(T elem) {
        add(elem, this.elementData, this.size);
        return true;
    }

    public boolean add(int index, T elem) {
        checkIndexRange(index);
        if (this.elementData.length == this.size) {
            grow();
        }
        //这里我们源代码的移动元素的操作是借助System.arraycopy完成的(final不会修改数组指向)
        final Object[] es = this.elementData;
        System.arraycopy(es, index, es, index + 1, this.size - index);
        es[index] = elem;
        this.size++;
        return true;
    }

    public boolean addAll(MyArrayList<? extends T> c) {
        Object[] arr = c.toArray();
        int numsLength = arr.length;
        if (numsLength == 0) {
            return false;
        }
        Object[] es = this.elementData;
        grow(c.size + this.size);
        //重新改变es的指向
        es = this.elementData;
        System.arraycopy(arr, 0, es, this.size, arr.length);
        this.size = this.size + arr.length;
        return true;
    }

    /**
     * 删除元素的操作(源码里面是借助一个fastRemove的方法完成的,其实也就是arraycopy)
     */
    public T remove(int index) {
        checkIndexRange(index);
        final Object[] es = elementData;
        int newSize = size - 1;
        T oldVal = (T) es[index];
        //这里主要考虑的是尾删除的弊端(尾部删除无法直接进行覆盖)
        if (newSize > index) {
            System.arraycopy(es, index + 1, es, index, newSize - index);
        }
        es[size = newSize] = null;
        return oldVal;
    }

    public boolean remove(Object o) {
        final Object[] es = elementData;
        int index = -1;
        if (o == null) {
            for (int i = 0; i < size; ++i) {
                if (es[i] == null) {
                    index = i;
                    break;
                }
            }
        } else {
            for (int i = 0; i < size; ++i) {
                if (o.equals(es[i])) {
                    index = i;
                    break;
                }
            }
        }
        if (index == -1) {
            return false;
        }
        //到这里说明已经找到了
        int newSize = size - 1;
        if (newSize > index) {
            System.arraycopy(es, index + 1, es, index, newSize - index);
        }
        es[size = newSize] = null;
        return true;
    }

    /**
     * 源码中关于下面两个方法的解释可以自己去看,机制相对复杂,但是跟我下面模拟的思路是差不多的
     * 这个题的最坑的点就是,切记! ! !,不要在一遍删除元素的同时去改变我们的空间大小界限(一般都会出错)
     * 这两个方法就是第一个removeAll是清除掉c中含有的元素
     * 第二个方法就是保留下来c中的元素,其他都进行删除
     */
    public void removeAll(MyArrayList<? extends T> c) {
        final Object[] es = this.elementData;
        int sz = this.size;
        for (int i = 0; i < this.size; ++i) {
            while (c.contains(es[i])) {
                System.arraycopy(es, i + 1, es, i, this.size - 1 - i);
                sz--;
                if (sz < 0) {
                    break;
                }
                es[sz] = null;
            }
        }
        this.size = sz;
    }

    public void retainAll(MyArrayList<? extends T> c) {
        Object[] temp = Arrays.copyOf(c.elementData, c.elementData.length);
        final Object[] es = c.elementData;
        int sz = c.size;
        for (int i = 0; i < c.size; ++i) {
            while (c.contains(es[i])) {
                System.arraycopy(c, i + 1, c, i, this.size - 1 - i);
                sz--;
                if (sz < 0) {
                    break;
                }
                c.elementData[sz] = null;
            }
        }
        this.size = sz;
        this.elementData = c.elementData;
        c.elementData = temp;
    }
}

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

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

相关文章

基于SpringBoot+Vue的便利店管理系统 免费获取源码

项目源码获取方式放在文章末尾处 项目技术 数据库&#xff1a;Mysql5.7/8.0 数据表&#xff1a;11张 开发语言&#xff1a;Java(jdk1.8) 开发工具&#xff1a;idea 前端技术&#xff1a;vue 后端技术&#xff1a;SpringBoot 功能简介 (有文档) 项目获取关键字&#…

tcp-learner 数据包分析 20240420

输入输出&#xff1a; 数据包分析&#xff1a; learner和Adapter建立连接。 Learner让Adapter发送RST Adapter没有从SUT抓到任何回复&#xff0c;于是向learner发送timeout learner给adapter发送reset命令&#xff0c;让SUT重置。 这是第一次初始化&#xff0c;由于Adapter和…

7. DAX 时间函数-- DATE 日期--TOTALMTD、TOTALQTD、TOTALYTD

函数名目的语法返回值TOTALMTD计算当前上下文中该月份至今的表达式的值 。TOTALMTD ( <表达式>, <日期列>, [<筛选器>] )标量 表示表达式的标量值&#xff0c;在“日期”中给定日期&#xff0c;计算当前月份至今的日期 。TOTALQTD计算当前上下文中该季度至今…

452. 用最少数量的箭引爆气球[排序+贪心]

https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xst…

C++ 内存分区管理

一、栈区&#xff08;Stack&#xff09; 栈区用来存储函数的参数值、局部变量的值等数据。栈区是自动分配和释放的&#xff0c;函数执行时会在栈区分配空间&#xff0c;函数执行结束时会自动释放这些空间。栈区的数据是连续分配的&#xff0c;由系统自动管理。 注意事项&…

大话设计模式-依赖倒转原则

依赖倒转原则 在大话设计模式这本书中&#xff0c;作者通过电话修电脑这个例子引入了面向对象设计的基本原则之一&#xff1a;依赖倒转原则。 概念 依赖倒转原则是面向对象设计的基本原则之一&#xff0c;它用于减少类之间的耦合&#xff0c;提高系统的灵活性和可维护性。在…

明道云HAP合作伙伴计划全解析:开辟业务增长新路径

什么是明道云HAP合作伙伴计划&#xff1f; 明道云采纳的是增值伙伴商业模式。在这个模式下&#xff0c;合作伙伴通过平台型产品为终端客户提供定制应用、行业解决方案、赋能培训等增值活动&#xff0c;从而在大幅降低交付成本的同时获得多来源的收入&#xff0c;提高经营绩效水…

PLC中连接外部现场设备和CPU的桥梁——输入/输出(I/O)模块

输入&#xff08;Input&#xff09;模块和输出&#xff08;Output&#xff09;模块简称为I/O模块&#xff0c;数字量&#xff08;Digital&#xff0c;又称为开关量&#xff09;输入模块和数字量输出模块简称为DI模块和DQ模块&#xff0c;模拟量&#xff08;Analog&#xff09;输…

RK3568 android11 修改关机弹窗界面

需要修改关机弹窗界面&#xff0c;当前界面我已经按照客户需求去掉emergency 但是客户需要按其他区域可以实现返回&#xff0c;也就是点击黑色背景取消dialog 嗑代码发现黑色布局为&#xff1a; <node index"0" text"" resource-id"com.android.…

【Redis】string数据类型

文章目录 常用命令setsetnx & NXXXsetex & EXpsetex & PX msetget & mgetincr & decrincrby & decrbyincrbyfloatappendgetrangesetrangestrlen 内部编码 字符串类型是 Redis 最基础的数据类型。 在redis中所有的键都是 string 类型&#xff0c;其他的…

oracle操作系统OS认证和密码文件认证

1 说明 1.1 常见认证方式 Oracle登录认证方式主要涉及到如何验证用户身份以访问数据库。Oracle数据库提供了多种认证机制来确保数据的安全性和访问控制&#xff0c;每种方式都有其特定的使用场景和安全性考虑。以下是Oracle中常见的登录认证方式&#xff1a; 1、基于操作系统…

Vue-鼠标悬浮在缩略图图片上,弹出原图

使用Popover 弹出框实现 <template><div><el-popoverplacement"right"width"400"trigger"hover"><img src"https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x36…

三维天地低代码平台实现客户需求的快速交付与灵活定制

— 款合格的低代码平台应具备架构稳定、 产品质量高、 交付速度快、 运维简便的特点, 能快速实现业务需求到系统功能落地。 二十余年来, 北京三维天地科技股份有限公司一直专注于实验室信息化管理 领域, 旗下 SW- LIMS 已在化工、 环保、 食品、 科研等二十余个行业广泛应用,服…

PyTorch and Stable Diffusion on FreeBSD

Stable Diffusion在图像生成领域具有广泛的应用和显著的优势。它利用深度学习和扩散模型的原理&#xff0c;能够从随机噪声中生成高质量的图像。 官网&#xff1a;GitHub - verm/freebsd-stable-diffusion: Stable Diffusion on FreeBSD with CUDA support FreeBSD下难度主要…

docker安装并跑通QQ机器人实践(4)-bs-cqhttp搭建

go-cqhttp&#xff0c;基于 Mirai 以及 MiraiGo 的 OneBot Golang 原生实现&#xff0c;只需简单的配置, 就可以基于 go-cqhttp 使用框架开发&#xff0c;具有轻量, 原生, 高并发, 低占用, 跨平台等特点。 1 go-cqhttp 官网及可执行文件下载链接 go-cqhttp 官网&#xff1a;ht…

项目实践---贪吃蛇游戏的实现

上一章&#xff0c;我们已经分析了贪吃蛇的具体内容&#xff0c;包括它是如何实现的&#xff0c;怎样完成这个项目的&#xff0c;其中就提到了 贪吃蛇有三个代码&#xff1a;一个是测试代码&#xff0c;一个是头文件代码&#xff0c;还有一个是主函数代码。那么今天我们就来讲一…

【大数据】TiDB: A Raft-based HTAP Database

文章目录 数据库知识介绍数据库系统的ACID特性分布式系统和CAP理论关系型数据库与非关系型数据库关系型数据库非关系型数据库 OldSQL、NoSQL、NewSQLOldSQLNoSQLNewSQL OLTP、OLAP、HTAP 前言&#xff1a;为什么选择TiDB学习&#xff1f;pingCAP介绍TiDB介绍TiDB的影响力TiDB概…

什么是大语言模型以及如何构建自己的大型语言模型?

一、关于大语言模型 LLM 对于无数的应用程序非常有用&#xff0c;如果我们自己从头开始构建一个&#xff0c;那我们可以了解底层的ML技术&#xff0c;并可以根据特定需求定制LLM&#xff0c;但是对资源的需求巨大。大型语言模型是一种 ML 模型&#xff0c;可以执行各种自然语言…

《QT实用小工具·二十八》基于qt开发的各种曲线

1、概述 源码放在文章末尾 该项目实现了各种曲线的绘制&#xff0c;下面是项目的demo演示&#xff1a; 项目部分代码如下&#xff1a; #include "frmsmoothcurve.h" #include "ui_frmsmoothcurve.h" #include "smoothcurve.h" #include "…

【ThinkPHP框架教程·Part-01】ThinkPHP6.x框架安装教程

文章目录 一、框架介绍1、框架简介和版本选择2、主要新特性 二、安装步骤1、下载并运行Composer-Setup.exe2、安装TP前切换镜像3、安装稳定版4、测试运行 一、框架介绍 1、框架简介和版本选择 Thinkphp是一种基于php的开源web应用程序开发框架ThinkPHP框架&#xff0c;是免费开…