数据结构-基于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;}
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux:常用软件、工具和周边知识介绍

上次也是结束了权限相关的知识&#xff1a;Linux&#xff1a;权限相关知识详解 文章目录 1.yum-管理软件包的工具1.1基本介绍1.2yum的使用1.3yum的周边生态1.4软件包介绍 2.vim-多模式的文本编辑器2.1基本介绍2.2基本模式介绍2.2.1命令模式&#xff08;Normal mode&#xff09;…

Vue【路由】

1&#xff1a;什么是单页应用程序&#xff08;single page application&#xff09; 所有得功能在一个html页面上实现 2&#xff1a;单页面应用程序的优缺点 优点&#xff1a;按需更新性能高&#xff0c;开发效率也高&#xff0c;用户的体验较好 缺点&#xff1a;学习成本高…

spring的redis注解@Cacheable @Cacheput @CacheEvict的condition、unless

概述 redis的注解使用的过程中总会遇到condition和unless这两个属性&#xff0c;而且不同的注解使用注意事项不一样。本人也是错误使用之后详细查询了一下&#xff0c;作了如下的总结。 Cacheale 这个注解的使用和意义这里不多说&#xff0c;可以查看我的其他文档。这里主要说…

java:Java中的抽象类

什么是抽象类&#xff1a; 我们知道&#xff0c;类用来模拟现实的事物&#xff0c;一个类模拟一类事物&#xff0c;某个类的一个实例化对象可以模拟某个属于该类的具体事物。类中描绘了该类所有对象的共同的特性&#xff0c;当一个类中给出的信息足够全面时候&#xff0c;我们就…

docker灵活部署mysql

博客简要 用docker部署mysql,并将数据库映射到主机上&#xff0c;并增加远端访问mysql数据库 当你使用Docker运行MySQL时&#xff0c;并且希望将MySQL数据库的数据存储在宿主机&#xff08;也就是运行Docker的主机&#xff09;上的特定路径&#xff0c;你需要在启动容器时通过…

Axure设计原型图工具 Windows11安装步骤详解

文章目录 目录 文章目录 安装流程 小结 概要安装流程技术细节小结 概要 Axure 是一个流行的原型设计工具&#xff0c;它被用来创建交互式原型、线框图和用户界面设计。Axure 可以帮助用户在项目早期阶段快速制作出可交互的原型&#xff0c;以便进行用户测试、验证设计概念和与…

机器学习和深度学习--李宏毅(笔记与个人理解)Day17

Day 17Convolutional Neyral Network (CNN) 卷积神经网络一般都用在image 上面比较多一些&#xff0c;所以课程的例子大多数也都是image Image Classification the same size how about for pc? 这里对于tensor 张量这个概念&#xff0c;我还是比较奇怪&#xff0c;在我认为一…

ssm 体检预约管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 ssm 体检预约管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c; 系统主要采用B/S…

JET毛选学习笔记:如何利用《实践论》学习实验

一、个人背景介绍 本人本科读的是预防医学专业&#xff08;因为没考上临床&#xff09;&#xff0c;硕博连读&#xff08;报名人少&#xff0c;我报了就得了&#xff09;的时候专业是流行病与卫生统计学&#xff0c;除了学习流行病学、统计学&#xff08;忘得差不多了&#xf…

2024-4-19 群讨论:JVM 堆外内存如何查看?

以下来自本人拉的一个关于 Java 技术的讨论群。关注公众号&#xff1a;hashcon&#xff0c;私信进群拉你 JVM 堆外内存如何查看&#xff1f; 参考&#xff1a;https://juejin.cn/post/7225871227743043644 分为&#xff1a; 通过 Native Memory Tracking 能看到的&#xff1…

【C++】:函数重载,引用,内联函数,auto关键字,基于范围的for循环,nullptr关键字

目录 一&#xff0c;函数重载1.1 函数重载的定义1.1.1.形参的类型不同1.1.2参数的个数不同1.1.3.参数的顺序不同1.1.4.有一个是缺省参数构成重载。但是调用时存在歧义1.1.5.返回值不同&#xff0c;不构成重载。因为返回值可接收&#xff0c;可不接受&#xff0c;调用函数产生歧…

二分答案复习

y总二分查找算法模板 int bsearch_1(int l, int r) {while (l < r){int mid l r >> 1;//性质在右边&#xff0c;区间划分成[l, mid]和[mid 1, r]if (check(mid)) r mid;else l mid 1;}return l; }int bsearch_2(int l, int r) {while (l < r){int mid l r …

LCR 023. 相交链表

给定两个单链表的头节点 headA 和 headB &#xff0c;请找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&#xf…

Git - 在PyCharm/Idea中集成使用Git

文章目录 Git - 在PyCharm/Idea中集成使用Git1.新建GitHub仓库2.将仓库与项目绑定3.在PyCharm中使用Git4.新建Gitee仓库5.将仓库与项目绑定6.在IDEA中使用Git Git - 在PyCharm/Idea中集成使用Git 本文详细讲解了如何在 PyCharm 或 Idea 中配置 Gitee 或 GitHub 仓库&#xff0…

《黑羊效应》一群好人欺负一个好人,其他好人却坐视不管的诡谲现象 - 三余书屋 3ysw.net

黑羊效应&#xff1a;一群好人欺负一个好人&#xff0c;其他好人却坐视不管的诡谲现象 大家好&#xff0c;今天我们要解读的书是《黑羊效应》。黑羊效应是一种心理陷阱&#xff0c;指的是一群好人欺负一个好人&#xff0c;而其他好人却坐视不理。我们每个人或多或少都目睹过或…

每日一题

腐烂的苹果_牛客题霸_牛客网 思路分析:广度优先遍历&#xff0c;找到所有腐烂的苹果同时向四方扩散&#xff0c;就是第一轮把所有腐烂的苹果加入队列中&#xff0c;这就跟MQ的消息队列的原理差不多&#xff0c;第一次记录队列的长度&#xff0c;广度遍历一次&#xff0c;长度--…

HCIP-OSPF综合实验

一实验拓扑图 二.实验要求 1、R4为ISP&#xff0c;其上只配置IP地址&#xff1b;R4与其他所直连设备间均使用公有IP&#xff1b; 2、R3-R5、R6、R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b;除了R12有两个环回&…

Xinstall:让URL打开App变得如此简单

在移动互联网时代&#xff0c;App已经成为我们日常生活中不可或缺的一部分。然而&#xff0c;在使用App的过程中&#xff0c;我们常常会遇到一些烦恼。比如&#xff0c;当我们通过一个网页链接想要打开对应的App时&#xff0c;往往需要先复制链接&#xff0c;然后在App中粘贴&a…

力扣287. 寻找重复数

Problem: 287. 寻找重复数 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 利用二分查找搜索1 ~ n中重复的元素&#xff0c;我们每次取出当前二分查找的区间的中间元素mid并在元始的数组nums中统计小于mid的元素的个数count&#xff1a; 若count > mid则说明重复的…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 4月20日,星期六

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年4月20日 星期六 农历三月十二 1、 证监会&#xff1a;调降基金股票交易佣金费率&#xff0c;年度降幅测算将达38%&#xff0c;7月1日起实施。 2、 民政部举办全国“乡村著名行动”培训班&#xff0c;助力乡村振兴。 3、…