目录
看到源码时,先看属性,再看构造方法,再看一些基础方法
ArrayList有三种遍历方式:迭代器遍历、索引遍历、for循环遍历ArrayList是基于数组实现的,一个动态数组,其容量能自动扩大,动态扩容
一、属性
private static final int DEFAULT_CAPACITY = 10;
//需要存放数据时定义的初始化数组的大小
//why要弄两个空数组呢?
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//定义默认空数组
/*存储数组列表元素的数组缓冲区。ArrayList的容量就是这个数组缓冲区的长度。
当添加第一个元素时,任何具有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空 ArrayList将 被扩展为DEFAULT_CAPACITY。*/
transient Object[] elementData;
//存储ArrayList内的元素,存放数据的数组(不能被序列化)
private int size;
/*数组列表的大小(它包含的元素的数量)。*/
二、构造方法
/*构造一个具有指定初始容量的空列表。
形参:initialCapacity -列表的初始容量
抛出:IllegalArgumentException -如果指定的初始容量为负值*/
//有参构造,initialCapacity为传入的数组长度
public MyArraylist(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//为什么等于0要单独做个条件语句
this.elementData = EMPTY_ELEMENTDATA;
} else {
//这是抛出异常机制,用来判断输入的数据是否合理
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/*构造一个初始容量为10的空列表。*/
public MyArraylist() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/*这是无参构造,如果我们创建ArrayList对象时没有传入参数,
* 就会调用该方法创建ArrayList对象,这个对象的elementData被初始化为空的Object[]
* 空的Object[]会给默认容量10,
* 在第一次调用add时,elementData将会变成默认的长度:10*/
三、基础测试用例
这里我用的是无参构造。有参构造就是直接开辟了一块连续的空间,但增添元素容量不够时,系统会自动扩容
MyArraylist myArraylist = new MyArraylist(13);
传个参数就ok
package com.two.me;
import com.one.www.MyArraylist;
public class Test03 {
public static void main(String[] args) {
MyArraylist myArraylist = new MyArraylist();
myArraylist.add(10);
myArraylist.add(9);
myArraylist.add(8);
myArraylist.add(7);
myArraylist.add(6);
myArraylist.add(5);
myArraylist.add(4);
myArraylist.add(3);
myArraylist.add(2);
myArraylist.add(1);
myArraylist.add(0);
}
}
四、add()方法
1.add无参数的方法
直接将元素增至列表末尾,当增加第一个元素时,element容量从0增至默认初始容量10,在扩容时实现扩容实现步骤:
add()——>ensureCapacityInternal()【判断数组容量】——>calculateCapacity()【计算当前数组的容量,并进行恰当的赋值】——>ensureExplicitCapacity()【判断是否需要进行扩容】——>.....
/*将指定的元素追加到列表的末尾。
形参:e -元素附加到这个列表
返回值:true (Collection.add来指定*/
public boolean add(Object e) {//
//确认list容量,如果不够,容量加1。注意:只加1,保证资源不被浪费
ensureCapacityInternal(size + 1); // Increments modCount!!
//将元素放在size位置上
elementData[size++] = e;//先运算后自加
return true;
}
//确保内部容量足够,检查数组容量,不够就进行扩容
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//主要是第一次无参构造时,将数组默认容量赋为10,返回最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//第一次调用add(1)时,elementData为空数组
return Math.max(DEFAULT_CAPACITY, minCapacity);//这里调用了Math的max方法
}
return minCapacity;//返回最小容量
}
//这里就是来判断是否需要扩容的方法
private void ensureExplicitCapacity(int minCapacity) {
//
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//如果所需的最小容量大于当前数组的长度
//意思就是,你当前数组长度为0,而最小容量为10,当前数组就需要扩容
grow(minCapacity);
}
2.add有参数的方法
//add(索引,添加的元素)
public void add(int index, Object element) {
rangeCheckForAdd(index);//检查索引是否越界
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
/*调用arraycopy方法,将[index,size]的元素均后移一位*/
elementData[index] = element;//将index上的元素变为element
size++;//记得将数组的索引加1
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
//如果索引大于数组的索引或者索引小于0,就抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {//解释异常原因,方便知道哪错了
return "Index: "+index+", Size: "+size;
}
例:抛出异常
基础测试用例+
myArraylist.print();
myArraylist.add(12,45);
myArraylist.print();
五、grow()方法【扩容】
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//oldCapacity为老数组长度
int newCapacity = oldCapacity + (oldCapacity >> 1);
//这个应该是位运算符吧,当当前数组容量为0时,newCapacity也为0?
//开始扩容了,新的容量=当前容量+当前容量/2,即将当前容量增加一半(当前容量增加1.5倍)
if (newCapacity - minCapacity < 0)
//如果扩容后的容量还是小于想要的最小容量
//将扩容后的容量再次扩容为想要的最小容量
//为什么不直接将数组容量扩到自己想要的容量呢?
newCapacity = minCapacity;
//int newCapacity = minCapacity;//这个扩容是刚刚合适的,而上面的方式扩了还有多
if (newCapacity - MAX_ARRAY_SIZE > 0)
//如果扩容后的容量大于临界值,就进行大容量分配
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
//copyOf(原数组,新的数组长度)
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
//如果minCapacity小于0,抛出异常
throw new OutOfMemoryError();
//如果想要的容量大于MAX_ARRAY_SIZE,则分配Integer.MAX_VALUE,否则分配MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
-
上述例子输出:
-
当将扩容方式改为
int newCapacity = minCapacity;
输出为:
因此,第一种扩容方式是扩大数组的多少倍,扩大之后还有多,第二种方式是刚好满足需求的。这里暂时不细细理解hugeCapacity()方法。
六、remove()方法
1.删除指定位置上的元素
//删除指定位置上的元素
public Object remove(int index) {
rangeCheck(index);//检查索引是否越界
Object oldValue = elementData(index);//记下索引上的元素
//既然是记下索引上的元素,为什么不这样呢?
//Object oldValue = elementData[index];
int numMoved = size - index - 1;
//记下应该左移的元素个数,size是数组元素个数,而非索引
if (numMoved > 0)
//如果需要左移的话
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//数组左移
elementData[--size] = null; // clear to let GC do its work
//size先自减再使用,然后将索引为size-1处的元素置为null。
// 为了让GC起作用,必须显式的为最后一个位置赋null值
return oldValue;
//返回被删除的元素
}
private void rangeCheck(int index) {
if (index >= size)
//怎么=也抛错
//因为此时size为数组元素的个数
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
Object elementData(int index) {
return elementData[index];
}
-
上述例子输出为:与2相同
-
如果是 Object oldValue = elementData[index];
基础用例+ System.out.println("原列表:"); myArraylist.print(); System.out.println("在某个索引插入某个数:"); myArraylist.add(8,45); myArraylist.print(); System.out.println("移除某个索引上的元素:"); myArraylist.remove(3); myArraylist.print();
输出为:
2.删除指定的元素且索引最小
//删除指定的元素且索引最小
public boolean remove(Object o, int a) {
//因为这个方法与上一个remove方法类似,不好区分,于是来个形参a做个区别的标志
if (o == null) {//如果对象为空
//why还要判断是不是为空?直接循环不就好了,if的意义何在?
//删了这个if条件语句,
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
//找到了第一个与之匹配的,就进行删除操作
return true;
}
}
return false;
}
//进行删除的方法
private void fastRemove(int index) {
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
-
上述代码输出:
基础测试用例+ System.out.println("原列表:"); myArraylist.print(); System.out.println("移除某个元素且其索引最小:"); myArraylist.remove(4,1);//若将4换成43,列表不变 myArraylist.print();
输出为:
-
将代码改为下述代码,其结果与1相同
public boolean remove(Object o, int a) { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); //找到了第一个与之匹配的,就进行删除操作 return true; //只执行一个return语句,且执行完return语句后不再执行其他语句 } return false; }
七、indexOf()和LastIndexOf()方法
1.从前往后遍历indexOf()
//从前往后遍历
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;//返回索引大小
}
return -1;//如果遍历完数组后都没有发现指定元素,就返回-1
}
//将上述代码改为下述代码其运行结果一样
/*public int indexOf(Object o) {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;//返回索引大小
return -1;//如果遍历完数组后都没有发现指定元素,就返回-1
}*/
/*测试用例:【将add(8)改为add(4)】
基础测试用例+
System.out.println("原列表:");
myArraylist.print();
System.out.println("遍历某个元素且其索引最小:");
System.out.println(myArraylist.indexOf(4));*/
输出为:
2.从后往前遍历lastIndexOf()方法
//这是从后往前遍历,其返回的是最大索引
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//代码可去if条件语句,去了if后的结果一样
/*测试用例:【将add(8)改为add(4)】
基础测试用例+
System.out.println("原列表:");
myArraylist.print();
System.out.println("遍历某个元素且其索引最大:");
System.out.println(myArraylist.lastIndexOf(4));*/
//结果为6