目录
1.前言
在上一篇文章中我们已经大致了解了ArrayList的一些基本用法和ArrayList的部分模拟实现,而在本文我们将继续完成上文未完成的模拟ArrayList的代码,并和大家讲解一些ArrayList使用的一些注意事项。上一篇文章链接:基础数据结构——ArrayList(上)_invictusQAQ的博客-CSDN博客
2.模拟实现ArrayList
上一篇文章未实现的功能已放在下文:
public int get(int pos){//获取pos位置元素
if(!checkPosInGet(pos)){
throw new MyArrayListIndexException("获取下标不合法!");
}
return this.elem[pos];
}
public void remove(int key){
if(isEmpty()){
throw new MyArrayListEmptyException("顺序表为空!");
}
int index=indexOf(key);
if(index==-1){
throw new MyArrayListIndexException("该元素不存在!");
}
else{
for(int i=index;i<this.usedSize-1;i++){
this.elem[i]=this.elem[i+1];
}
}
this.usedSize--;
// this.elem[usedSize] = null; 如果是引用类型 这里需要置为空
}
// 获取顺序表长度
public int size() {
return this.usedSize;
}
public void clear(){
/*
如果是引用数据类型 得一个一个置为空
for (int i = 0; i < this.usedSize; i++) {
this.elem[i] = null;
}
this.usedSize = 0;
*/
this.usedSize=0;
}
public void set(int pos,int value){
if(!checkPosInGet(pos)){
throw new MyArrayListIndexException("下标位置不合法!");
}
this.elem[pos]=value;
}
完整代码我将上传至gitee: ArrayList: 有关ArrayList实现的数据结构
3.ArrayList的使用及部分注意事项
1.
首先我们知道ArrayList即使我们不初始化给他一个大小他也是有一个默认的大小的,但是他并非在我们创建对象时就已经分配好了默认大小,事实上在创建完ArrayList时他默认为空,只有当我们在使用了add方法后他才会被分配内存空间。
我们先看到下面一段源码代表的意思:
//默认容纳容量
private static final int DEFAULT_CAPACITY = 10;
//默认空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认容纳容量下的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储数据的数组,定义为Object是为了容纳泛型
transient Object[] elementData;
//数组存储的实际内容大小
private int size;
//最大数组大小,数组为int类型的最大值-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
在我们new一个ArrayList对象时,我们转到源码可以看到如下代码
可以看到如果我们不执行add方法那么他默认就是一个空的数组,而当我们看到add源码时,他首先会执行下面的一个方法
add方法先调用ensureCapacityInternal方法来保证容纳容量可用,随后在指定的数组位置赋值为传入参数,随后size自增1来添加1个元素。
然后看下ensureCapacityInternal方法。
他的作用是计算容量是否需要扩容
这里可以看到这个方法里面调用了很多方法,由内向外看一下具体方法的作用。
先看calculateCapacity方法。
这个方法的实际作用翻译一下就是
1.判断数组是否为默认容纳容量下的空数组(实际上数组如果为默认空数组结果也是一样的,因为二者实际内容相同)
2.是则返回默认容量和传入的最小容量两者之间的较大值
3.否则返回最小容量
calculateCapacity方法实际上是判断数组是否为空然后计算其对应的容量,若数组不为空则直接返回传入的最小容量。
然后来看ensureExplicitCapacity方法。
这个方法可以简单理解为判断是否需要扩容
接下来终于进入了真正的扩容方法grow,以下是他的源代码
private void grow(int minCapacity) {
//将数组容纳容量赋值给oldCapacity
int oldCapacity = elementData.length;
//将数组容纳容量扩至原来的1.5倍的值赋给newCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
//判断newCapacity和minCapacity的大小关系
if (newCapacity - minCapacity < 0)
//newCapacity小于minCapacity则将minCapacity的值赋给minCapacity
newCapacity = minCapacity;
//判断newCapacity和最大数组大小的大小关系
if (newCapacity - MAX_ARRAY_SIZE > 0)
//调用hugeCapacity方法,传入参数为minCapacity,将得到的值赋给newCapacity
newCapacity = hugeCapacity(minCapacity);
//调用Arrays.copyOf方法扩容数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
可以看到grow的扩容是1.5倍扩容,相对于我们自己模拟实现的2倍扩容在一定程度上减少了空间的浪费。
2.
import java.util.*;
public class TestDemo {
public static void main(String[] args) {
ArrayList<Integer> arrayList=new ArrayList<>();
arrayList.add(5);
arrayList.add(4);
arrayList.add(3);
arrayList.add(2);
arrayList.add(1);
System.out.println(arrayList);
arrayList.remove(1);
arrayList.remove(new Integer(1));
System.out.println(arrayList);
}
}
现在我们看到这一串代码,众所周知remove方法是删除ArrayList中下标为(x)的元素,但是有的时候我们想删除某个特定的元素又该怎么使用remove方法呢?其实我们只需要把你要删除的那个对象的引用传进去就好,比如
arrayList.remove(new Integer(1)); 这个操作就是删除1这个元素,运行结果如下
3.
我们知道ArrayList中也是有截取方法sublist的,而当我们在上述代码中执行以下代码时
System.out.println(arrayList.subList(1,3));
可以发现sublist方法的截取区间是一个左闭右开的区间
但是sublist是直接截取一个新的部分还是只是返回了被截取部分的引用呢?看到下面的代码
import java.util.*;
public class TestDemo {
public static void main(String[] args) {
ArrayList<Integer> arrayList=new ArrayList<>();
arrayList.add(5);
arrayList.add(4);
arrayList.add(3);
arrayList.add(2);
arrayList.add(1);
System.out.println(arrayList);
List<Integer> tmp = arrayList.subList(1, 3);
tmp.add(99);
System.out.println(arrayList);
}
}
他的运行结果如下:
可以看到我们对sublist截取的部分进行修改原数组也发生了变化,证明sublist是在其本身进行截取的。