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

devtools/2024/9/25 5:04:58/

文章目录

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

继承关系 :

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

1. 构造方法的模拟

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

java"> /*** 在我们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倍率增长是无法完成的
*/

java">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的查找
代码实现如下
从前向后查找和从后向前查找

java">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. 获取,修改元素的方法模拟

代码实现如下

java">/*** 获取元素,修改元素的方法*/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调用的时候,会修改源数组空间的指向,也就是如果在这之前进行过数组指向的约定,要重写修改指向

java">/*** 添加元素的方法* 请注意这里有一个小点是比较坑的,就是数组完成扩容之后,原来的数组指向是要进行修改的*/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)…
代码实现如下

java">/*** 删除元素的操作(源码里面是借助一个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中的元素,其他都进行删除
*/
代码实现如下

java"> 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;}

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

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

java">
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.ppmy.cn/devtools/5676.html

相关文章

SQLite 的命令行 Shell(三十一)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite FTS5 扩展&#xff08;三十&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 1. 入门 SQLite 项目提供了一个名为 sqlite3&#xff08;或 Windows 上的sqlite3.exe&#xff09;的简单命令行程序 …

深度学习基础——卷积神经网络的感受野、参数量、计算量

深度学习基础——卷积神经网络的感受野、参数量、计算量 深度学习在图像处理领域取得了巨大的成功&#xff0c;其中卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;CNN&#xff09;是一种非常重要的网络结构。本文将介绍卷积神经网络的三个重要指标&#…

MongoDB快速启动

两种方法: 方式 1 &#xff1a;命令行参数方式启动服务 在 bin 目录中打开命令行提示符&#xff0c;输入如下命令&#xff1a; (mongod --dbpath..\data\db) mongod --dbpath..\data\db 方式 2 &#xff1a;配置文件方式启动服务 在解压目录中新建 config 文件夹&#xff0…

踏上R语言之旅:解锁数据世界的神秘密码(二)

R语言学习 文章目录 R语言学习1.数据的R语言表示2.多元数据的R语言调用3.多元数据的简单R语言分析 总结 1.数据的R语言表示 数据框&#xff08;data frame) R语言中用函数data.frame()生成数据框&#xff0c;其句法是&#xff1a; data.frame(data1,data2,…)&#xff0c;例如…

spring高级篇(二)

1、Aware和InitializingBean Aware和InitializingBean都与Bean的生命周期管理相关。 Aware接口: 概念: Aware接口是Spring框架中的一个标记接口&#xff0c;它表示一个类能够感知到&#xff08;aware of&#xff09;Spring容器的存在及其特定的环境。Spring框架提供了多个Awar…

选择生产制造项目管理系统?全面解析功能与实际应用!

生产效率和项目规划是制造企业亟需解决的难题&#xff0c;想要从容的应对这些挑战&#xff0c;离不开好用的生产制造项目管理系统。下面我们全面解析什么才能称得上是好用的生产制造项目管理系统。 一、好用的生产制造项目管理系统 什么样的项目管理系统才能算是好用呢&#x…

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

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

论文略读:SWE-bench: Can Language Models Resolve Real-world Github Issues?

iclr 2024 oral reviewer评分 5668 现有的语言模型&#xff08;LMs&#xff09;的基准测试已经饱和&#xff0c;无法捕捉到最先进的语言模型能做什么和不能做什么的前沿。 ——>要具有挑战性的基准测试论文引入了SWE-bench 在现实软件工程环境中评估语言模型的基准测试 ​​…