事先说明,这是原创文章,不是从网上扒的,从晚上6点半写到了8点才搞定,饭都还没吃
如果你是新手,请一步一步的看,你一定能看懂的
说明:为了去掉原来的英文注释,我把ArrayList改为了ArrayListsrc
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.RandomAccess;
/**
*
* @author yj
* @date 2015-12-5 下午6:27:51
* @description: ArrayList的源码分析
* @param <E>
*
* @Declaration 关于容量(Capacity)和大小(Size)的理解
* 容量:ArrayList可以容纳的元素有多少个,比如初始化大小为10
* 大小:是说当前ArrayList中有多少个元素.想想平时怎么用size()的就很容易明白了
*/
//实现的接口介绍:RandomAccess(用于随机访问),Cloneable(用于克隆),Serializable(用于对象的序列化)
public class ArrayListSrc<E> extends AbstractList<E> implements List<E>,
RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8683452581122892189L;
// 底层使用一个Object数组实现的
private transient Object[] elementData;
// elementData中有多少个元素
private int size;
// 以指定的容量大小来初始化数组
public ArrayListSrc(int initialCapacity) {
super();
// 如果初始化容量<0,抛出非法参数异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "
+ initialCapacity);
//初始化数组
this.elementData = new Object[initialCapacity];
}
/*
* 这是默认的构造函数,调用了ArrayListSrc(int initialCapacity)
* 也就是说默认的数组容量是10
*/
public ArrayListSrc() {
this(10);
}
/*
* 将一个集合转换为数组并赋给elementData
* <? extends E>是泛型的范围限定,E是ArrayList类的泛型,表示E及E的子类
*/
public ArrayListSrc(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
//c.toArray()方法可能不能正确的转换,这里加了判断
if (elementData.getClass() != Object[].class)
//copyOf:将elementData[0-size]复制到Object[]中
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
/*
* 先看看老外起的方法名:trimToSize,削减到大小(size)
* 也就是说将当前的容量(Capacity)减小到size
* 比如现在ArrayList的容量是10,但是里面只有5个元素,就把容量减小到5
*/
public void trimToSize() {
//modCount是父类AbstractList中的一个变量,用于记录被修改了多少次
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
//这里就是实现削减的方式:已size为数组长度赋值一份原来的elementData
elementData = Arrays.copyOf(elementData, size);
}
}
/*
* 简单的说就是在必要的时候给数组扩容,记住在我们new一个数组是,长度就已经确定了,
* 扩容只能再创建一个长度更大数组,并把原来的复制过去
*/
public void ensureCapacity(int minCapacity) {
//修改次数+1
modCount++;
//获取底层数组的长度
int oldCapacity = elementData.length;
//如果参数>数组长度,说明需要扩容了
if (minCapacity > oldCapacity) {
//保存原来的数组
Object oldData[] = elementData;
//新的数组长度是原来的1.5倍
int newCapacity = (oldCapacity * 3) / 2 + 1;
//如果扩容以后还是不够,那么直接把minCapacity作为新数组长度
if (newCapacity < minCapacity){
newCapacity = minCapacity;
}
//copyOf内部创建一个新数组,并把elementData复制进去,最后的返回值赋给elementData
//要想看的更清楚可以选中copyOf按F3进去看源码
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
//获取ArrayList当前的元素个数
public int size() {
return size;
}
//判断是否为空
public boolean isEmpty() {
return size == 0;
}
//判断是否包含一个元素
public boolean contains(Object o) {
//indexOf:如果能索引到该元素,返回值是>=0的,如果索引不到返回-1
return indexOf(o) >= 0;
}
//获取元素的索引
public int indexOf(Object o) {
//判断o是否是一个空引用,如果调用该方法是为:indexOf(null)
if (o == null) {
//源码的for和if都没有{},看得我好难受,果断加上
for (int i = 0; i < size; i++){
//如果某个元素为null,返回索引
if (elementData[i] == null){
return i;
}
}
} else {
//暴力搜索,一个一个比较,相等就返回索引
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//找不到就返回-1
return -1;
}
//搜索元素o最后出现的位置
public int lastIndexOf(Object o) {
//o==null同indexOf
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;
}
//找不到就返回-1
return -1;
}
/*
* 覆写Object类的clone方法,可以看出是一个深克隆
* 友情复习:如果一个类中的成员遍历都是基本数据类型,那么就是浅克隆
* 而ArrayList中有Object[] elementData,这是引用类型遍历,所以是深克隆
*/
public Object clone() {
try {
ArrayListSrc<E> v = (ArrayListSrc<E>) super.clone();//这一步只是浅克隆
v.elementData = Arrays.copyOf(elementData, size);//elementData也要克隆才是深克隆
v.modCount = 0;//克隆后初始话记录修改次数的变量modCount为0
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
//把ArrayList转化为数组
public Object[] toArray() {
//直接拷贝了一个elementData[size]数组
return Arrays.copyOf(elementData, size);
}
//参数传入一个数组,输出和参数同类型的数组(没用过)
public <T> T[] toArray(T[] a) {
if (a.length < size){
//把elementData以size大小赋值到T[]数组中并返回
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
}
//所以说一般传入一个长度和size大小相同的数组
System.arraycopy(elementData, 0, a, 0, size);
//如果传入的数组长度>size就设置a[size]为空
if (a.length > size)
a[size] = null;
return a;
}
//获取index角标处的元素
public E get(int index) {
//检查角标是否越界
RangeCheck(index);
return (E) elementData[index];
}
//用指定的元素替代指定位置上的元素
public E set(int index, E element) {
//检查角标是否越界
RangeCheck(index);
//获取原来的元素
E oldValue = (E) elementData[index];
//设置新元素
elementData[index] = element;
//以前位于该指定位置上的元素(虽然我也很好奇有返回值)
return oldValue;
}
//添加元素
public boolean add(E e) {
//用来保证当前的容量大小可以保存该元素,也就是添加这个元素超过了容量就要扩容了
ensureCapacity(size + 1);
//添加到最后的位置
elementData[size++] = e;
//添加成功就返回true
return true;
}
//在指定的位置添加元素
public void add(int index, E element) {
//如果index大于size或者<0就抛出异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
ensureCapacity(size + 1); // Increments modCount!!增加了修改次数
/*
* 说下这个方法吧,arraycopy:把elementData<1>数组的index位置 复制到 elementData<2>的index+1位置
* 复制size-index个元素,相当于index后面的元素整体后移一位,想想让自己如何完成这个功能,就很好想通了
*/
System.arraycopy(elementData, index, elementData, index + 1, size
- index);
//index后面的元素都后移一位了,那么把element放在这就行了
elementData[index] = element;
//最后size++
size++;
}
//删除指定位置的元素
public E remove(int index) {
//这俩不说了
RangeCheck(index);
modCount++;
//获取index处的元素
E oldValue = (E) elementData[index];
//计算要移动的数目,也就是说把index处的元素删除了,后面都得往前移一位
int numMoved = size - index - 1;
if (numMoved > 0){
//后面的元素都往前移一位
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
}
/*
* 这里很重要,设置最后一个元素为null,可以让gc来回收,避免了内存泄露
* 友情复习(我是好人):内存泄露就是说一个引用不指向任何一个对象,但是它还占据着内存空间
* 如果把它设置为null,gc就可以回收了
*/
elementData[--size] = null; // Let gc do its work
//返回被删除的元素
return oldValue;
}
//删除指定的元素(如果一步一步看的这里不用说了,就说说fastRemove吧)
public boolean remove(Object o) {
if (o == null) {
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方法,属于类内部,用于删除index角标处的元素
private void fastRemove(int index) {
modCount++;
//和remove(int index)一样
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
//清空集合
public void clear() {
modCount++;
//把所有元素都设置为空引用
for (int i = 0; i < size; i++)
// Let gc do its work 设置为null,交给gc回收
elementData[i] = null;
//size设置为0
size = 0;
}
//把一个已有的集合添加到ArrayList中
public boolean addAll(Collection<? extends E> c) {
//先转成数组
Object[] a = c.toArray();
//获取数组长度
int numNew = a.length;
//保证ArrayList的容量
ensureCapacity(size + numNew); // Increments modCount
//把转换后的数组赋值到elementData数组中,长度为numNew
System.arraycopy(a, 0, elementData, size, numNew);
//size也会增加
size += numNew;
//新手看不懂返回值的话就不看return,只看numNew != 0,这个逻辑表达式要么为true,要么为false
return numNew != 0;
}
//这个方法是对上面方法的一个小增强:在指定的位置添加一个集合
public boolean addAll(int index, Collection<? extends E> c) {
//判断索引
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0){
//不用之处只是复制到elementData数组的位置为index + numNew
System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
}
//这是添加一个空集合的情况
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
//删除角标在[fromIndex,toIndex)范围内的元素,我用区间表示范围,有高中知识的应该能看懂吧?
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);
//这是计算要从哪里开始删
int newSize = size - (toIndex - fromIndex);
while (size != newSize){
// Let gc do its work
elementData[--size] = null;
}
}
//很简单的一个角标判断
private void RangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
}
/*
* writeObject和readObject都是用于序列化的方法,我主要将ArrayList的线性表部分,只对序列化做一个简单介绍给新手
* 序列化:
* 我们代码中的对象都会在程序运行结束后销毁,那么能不能存到硬盘上,
* 或者直接通过网络传输呢?答案是可以,只要实现了序列化接口(Serializable)即可
* 这是一个标志接口(Cloneable也是),意思是这个接口中什么都没有,相当于给类打个标签,实现该接口的就可以序列化
* writeObject:将所有元素写入到一个输出流中
* readObject:从一个输入流中读取元素
*
* @question 留个疑问给新手,为什么ArrayList已经实现了序列化接口还有写这两个方法
* 提示:transient Object[] elementData,transient是干嘛的?
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i = 0; i < size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in array length and allocate array
int arrayLength = s.readInt();
Object[] a = elementData = new Object[arrayLength];
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
a[i] = s.readObject();
}
}