ArrayList
通过分析Collection(一)我们说了ArrayList的数据结构以及优缺点,今天在写一篇自己手写的ArrayList实现增删改的功能(add,remove,get),来更好的了解一下它的一些相关的实现思路
一、父类以及实现类
从上图可以看到,ArrayList实现了List接口。我们自己动手的话,我们让新写的类让他实现List接口。这样创建的时候就会是 List xxx = new SelfArrayList(); 这种格式了,这篇文章的代码并不是按照着jdk里面的代码写的,我们只需要实现List接口实现它里面的get,remove还有add方法就行了,然后其他的操作代码是自己想法来写,和他做到相同(不考虑线程安全的问题,也不考虑占用内存之类的,功能出来就行了。)
二、开始上手
ArrayList的数据结构是数组,我们先简单的把这个类的代码结构做出来
1.属性
看下面代码,object用来保存我们数据的一个数组,我们再定义一个size,用来保存当前咱这个List的元素的个数,说白了就是这个object的长度,至于为什么给这个数组定义为Object类型的呢??? Object是Java中所有类型它祖宗,也就是谁都可以转成Object,用列表代替数组的原因本来不就是,数组固定了存储的类型以及固定长度了吗??这里就肯定有人问,为什么不直接用Object来做数组的类型,这不就又扯出来数组不能变长度了吗,别纠结怎么多。。。
在构造函数里面创建一个Object的数组给他长度固定到3(调试的时候好看出来),然后在赋值给我们的那个object属性,当然也可以在属性里面直接写 private Object[] object = new Object[3];
public class SelfArrayList<E> implements List<E>{
private int size; //用来保存这个列表的当前有多少元素
private Object[] object; //用来存储数据的数组
public SelfArrayList(){
this.object = new Object[3]; //创建这个实例的时候,给这个数组固定长度,让他等于10
}
}
2.方法
接下来开始写它的方法了,它既然有个size属性,那么肯定就会有个获取size的方法,还有它的增删改操作方法,当我们实现List类的时候,它肯定会让你实现它的全部方法,这里我们只写 size(),get(),add(),remove()这4个方法,然后我们在一步一步完善它
public class SelfArrayList<E> implements List<E>{
private int size; //用来保存这个列表的当前有多少元素
private Object[] object; //用来存储数据的数组
public SelfArrayList(){
this.object = new Object[15]; //创建这个实例的时候,给这个数组固定长度,让他等于10
}
@Override
public int size() {
// TODO Auto-generated method stub
return 0;
}
@Override
public boolean add(E e) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean remove(Object o) {
// TODO Auto-generated method stub
return false;
}
@Override
public E get(int index) {
// TODO Auto-generated method stub
return null;
}
}
Size方法
既然size是获取元素个数,那么就是获取数组的长度,我们直接让它返回 size就行了
@Override
public int size() {
// TODO Auto-generated method stub
return this.size;
}
Add方法
在写删除的时候,我们要考虑到数组的长度是否够用,如果够用我们直接添加就行,如果不够用那么就该给数组扩容了,JDK中的ArrayList是原来的1.5倍,我们直接给他原来的x2就行了,不按照它的来写。。
@Override
public boolean add(E e) {
// TODO Auto-generated method stub
//这里就是用来判断是否超出长度了,size是我们用来记录添加的个数的
if(this.size >= object.length) {
Object[] temp = new Object[size * 2]; //创建一个新的数组,长度是我们当前元素个数×2
//然后我们用arraycopy方法把object的数据复制到这个新的数组里面
System.arraycopy(object, 0, temp, 0, size);
//最后我们将这个临时数组赋值给原来的object,这样就完成了扩容
object = temp;
}
this.object[size++] = e; //这步就是给数组里面添加元素的,
return true;
}
this.object[size++] = e; 这个拿出来说一下
size++,先用完这个size之后在让这个size+1,可以理解为 object[size] = e; size = size + 1;
数组的下标是从0开始的,而长度是从1开始的,也就是我们取第一个数,它的下标是0,但是我们在添加一个数,就是下标(0)+1 = 1,第二个数的下标就是1了,但是它实际上是第二个,是不是发现,它的新添加的元素下标一定是它的长度,所以直接用size就是它最新元素的下标
调试
由于这个没有办法用截图完全展示出来,录制了个视频放上来
http://www.fanxing.live/video/debug-collection2.mp4
Get方法
我们前一篇文章说了,ArrayList的查询快,也就是取数据快,因为它是直接根据下标(索引)取的
@Override
public E get(int index) {
// TODO Auto-generated method stub
E value = (E) this.object[index];
return value;
}
是不是第一个想法就是这样,上面代码没有考虑一件事,如果元素只有10个,那么我们用索引查的时候查 20该怎么办??是不是肯定会超啊,所以我们应该在做个判断,看下是不是超出索引范围了,如果超出了让他抛出异常
@Override
public E get(int index) {
// TODO Auto-generated method stub
//定义超出索引异常
if(index >=size || index < 0) {
throw new IndexOutOfBoundsException();
}
E value = (E) this.object[index]; //根据索引index拿到数据
return value;
}
调试
Remove方法
删除方法效率也有点慢,如果删除的数是中间的,我们不仅要考虑到前面的元素,还要考虑后面的元素
这样删除的方法也完成了,来测试一下看看效果
调试
@Override
public E remove(int index) {
// TODO Auto-generated method stub
if(index < 0 && index >= size) {
throw new IndexOutOfBoundsException();
}
Object[] temp = new Object[size - 1]; //创建出一个比原先object数组长度少1的数组
System.arraycopy(object, 0, temp, 0, index); //将这个指定元素前面的元素全部复制给它
//判断这个元素是不是最后一个
if(index < size - 1) {
System.arraycopy(object, index + 1, temp, index, temp.length - index);
}
this.object = temp;
return (E)object;
}
通过自己写可以发现,查询(获取)元素很快,增加和删除则非常的慢了