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

server/2024/10/20 3:41:16/

文章目录

      • 继承关系 :
      • 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/server/4964.html

相关文章

Kafka集群搭建可视化指南

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Kafka集群搭建可视化指南 前言准备工作硬件要求环境准备 kafka集群的部署与配置3.1 单节点部署与多节点集群搭建单节点部署&#xff1a;多节点集群搭建&#xff1a; 3.2 Broker配置与优化3.3 Topic的创…

ES6的模块化

ES6模块化是JavaScript的一种组织代码的方式&#xff0c;它允许开发者将代码分割成多个独立的部分&#xff08;模块&#xff09;&#xff0c;每个模块有自己的作用域和接口&#xff0c;模块之间可以通过导入&#xff08;import&#xff09;和导出&#xff08;export&#xff09…

阿里云服务器怎么更换暴露的IP

很多客户阿里云服务器被攻击IP暴露&#xff0c;又不想迁移数据换服务器&#xff0c;其实阿里云服务器可以更换IP&#xff0c;今天就来和大家说说流程&#xff0c;云服务器创建成功后6小时内可以免费更换公网IP地址三次&#xff0c;超过6小时候就只能通过换绑弹性公网IP的方式来…

【Stable Diffusion】ModuleNotFoundError: No module named ‘ifnude‘ and roop v0.0.2

提示&#xff1a;ModuleNotFoundError: No module named ‘ifnude’ 一、issues/299&#xff1a;ModuleNotFoundError: No module named ‘ifnude’ 路径 cmd 中也可以看到&#xff0c;路径可能有点不一样&#xff0c;但是后面的路径应该都是一样的&#xff0c;如&#xff1a;…

Flask vs FastApi 性能对比测试

Flask和Fastapi都是Python下流行的Web框架&#xff0c;前者有大量拥趸&#xff0c;是一个老牌框架&#xff0c;后者相对较新&#xff0c;但是利用了异步技术和uvloop&#xff0c;都说性能比Flask好很多&#xff0c;于是就我就对比实测一下。由于Windows下不支持uvloop&#xff…

[论文笔记] megatron训练参数:dataloader_type

在深度学习中&#xff0c;dataloader_type参数通常控制着数据的加载、处理和输入到模型的方式。不同的dataloader可能会按照不同的策略处理数据集&#xff0c;这可以显著影响模型训练和评估的效果。具体来说&#xff0c;single和cyclic类型通常如此区别&#xff1a; Single Dat…

小型燃气站3D可视化:打造安全高效的燃气新时代

随着科技的不断进步&#xff0c;越来越多的行业开始融入3D可视化技术&#xff0c;燃气行业也不例外。 小型燃气站作为城市燃气供应的重要节点&#xff0c;其安全性和运行效率至关重要。传统的燃气站管理方式往往依赖于人工巡检和纸质记录&#xff0c;这种方式不仅效率低下&…

OpenHarmony南向开发案例:【智能中控屏】

样例简介 本Demo是基于Hi3516开发板&#xff0c;使用开源OpenHarmony开发的应用。通过控制面板可以控制同一局域网内的空调&#xff0c;窗帘&#xff0c;灯等智能家居设备。 当前支持的配套L0设备只有[智能灯]&#xff0c;如需添加新的设备。 应用运行效果图&#xff1a; 样…