JAVA实现自定义实现的ArrayList
那,为了更加深刻理解ArrayList的底层原理实现,就写一个自定义的ArrayList集合,实现出底层的基本功能,关于一些底层的原理,可以参考文章:
https://blog.csdn.net/weixin_46726346/article/details/107562529
好,言归正传,现在开始写:
1、arraycopy的用法
最开始想介绍一个方法arraycopy这个方法的使用,因为这个方法会被用到;
我们用代码加图解来介绍:
int arr[]=new int[5];
for(int i=0;i<arr.length;i++) {
arr[i]=i;
}
写了一个大小为5的数组,值分别为01234对吧;
看图,我现在想把3,4这两个元素去替换前面的1,2这两个元素,看我的操作:
System.arraycopy(arr, 3, arr, 1, 2);
就这一行代码,那是什么意思呢:
public static void arraycopy(Object src, int srcPos, Object dest,int destPos,int length)
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。从 src 引用的源数组到 dest 引用的目标数组,数组组件的一个子序列被复制下来。被复制的组件的编号等于 length 参数。源数组中位置在 srcPos 到 srcPos+length-1 之间的组件被分别复制到目标数组中的 destPos 到 destPos+length-1 位置。
然后数组就变成了:
好,这个方法理解了,我们就开始写这个自定义的ArrayList!
首先肯定要创建好类,然后再给类加个泛型:
public class MyArrayList<E> {}
然后就开始写了
2、成员变量
我们在上一篇中说过,ArrayList底层是一个数组,
那么成员变量 最主要的就是要有一个数组来存储元素
private Object[] elementData;
然后要有记录元素个数的成员变量
private int size;
然后在初始化的时候,要有一个空数组(上一篇说过就不详细说了)
private Object[] emptyArray[]= {};
再有一个第一次扩容的初始容量10,应该定义为常量
private final int capcity=10;
3、构造方法
说完成员变量,第一个方法肯定是构造方法;
我们这里只写一个无参数的构造方法
我们知道在调用无参数构造方法的时候会初始化一个容量为0的数组:
public MyArrayList() {
elementData=emptyArray;
}
这就用到了之前定义的成员变量!
4、扩容方法
关于扩容方法,我们知道,第一次调用add函数的时候,会把容量设置为10;
然后每次添加元素,数组不够了之后,会吧容量扩容至原来的1.5倍:
这里的1.5倍,我们是有移位运算符来写的(底层就是)
private void grow() {
if(elementData==emptyArray) {
elementData=new Object[capcity];
}
if(size==elementData.length) {
int oldCpacity=elementData.length;
int newCapcity=oldCpacity+(oldCpacity>>1);
Object obj[]=new Object[newCapcity];
System.arraycopy(obj, 0, obj, 0, oldCpacity);
elementData=obj;
}
}
然后这个方法不被外界调用,所以我们设置成私有的,由于只有在调用add方法的时候,才会调用grow方法,所以我们写一下add方法;
5、add方法
我们知道add方法返回值是布尔类型,并且把元素添加到尾部,size加一;
每次调用add方法都要调用一次grow方法:
public boolean add(E e) {
grow();
elementData[size++]=e;
return true;
}
6、toString方法
我们上篇说过,toString方法底层是通过迭代器进行遍历,我们自己写就用随机访问来进行遍历数组,输出字符串,在这里最好不要用+来写,用StringBuilder来写来减少开销:
public String toString() {
if(size==0) {
return "[]";
}
StringBuilder str=new StringBuilder();
str.append("[");
for(int i=0;i<size;i++) {
if(i==size-1) {
str.append(elementData[i]).append("]");
}
else {
str.append(elementData[i]).append(",").append(" ");
}
}
return str.toString();
}
7、set方法
set方法,返回值是被修改的元素,我们要判断索引的位置是否有错,如果正常,就可以直接修改元素了!
public E set(int index,E element) {
if(index<0||index>size) {
throw new IndexOutOfBoundsException("索引越界");
}
E value =(E) elementData[index];
elementData[index]=element;
return value;
}
8、remove方法
如果我们要删除一个元素,首先要判断位置是否有错误,然后把索引位置的元素删除,后面所有的元素向前一位,哎,这里就用到了arraycopy这个方法:
public E remove(int index) {
if(index<0||index>size) {
throw new IndexOutOfBoundsException("索引越界");
}
E value =(E) elementData[index];
int numMoved=size-index-1;
if(numMoved>0) {
System.arraycopy(elementData, index+1, elementData, index, numMoved);
}
elementData[--size]=null;
return value;
}
就相当于把要删除的元素,后面所有的元素复制到前一个位置上,然后再把最后的一个元素置空;
返回值呢,就是被删除的元素;
9、get方法
获取元素就更简单了,判断索引,正常的话直接返回就好了,返回元素个数也是一样的!
public E get(int index) {
if(index<0||index>size) {
throw new IndexOutOfBoundsException("索引越界");
}
return (E) elementData[index];
}
public int getSize() {
return size;
}
到此基本的方法就全写完了,然后我们在主函数里面测试一下:
10、test函数
public static void main(String[] args) {
MyArrayList<String> list=new MyArrayList();
list.add("123");
list.add("456");
list.add("789");
list.set(1, "abc");
list.remove(2);
System.out.println(list.get(0));
System.out.println(list.getSize());
System.out.println(list);
}
然后我们看一下输出:
123
2
[123, abc]
也是木有问题的,好了,到此就结束了,关于ArrayList就这样了!
本文地址:https://blog.csdn.net/weixin_46726346/article/details/107580973