导语
首先说下为什么要写集合类的博文,毕竟这类的文章网上也是数不胜数,可是并不能因为这个就成了自己不写的理由,写博文其实主要是为了锻炼自己思考问题的思路,很多时候一篇文章写的好坏与否,主要就看写作者的思路如何。当你的思路很清晰的时候,同时也说明了你对它的熟悉度如何。
虽然集合源码的解析又很多人写过,看上去也不是很复杂,可是如果你去细看的话,其实还是可以看出很多东西的。笔者之所以要写,更多的是要强化一下自己对这些基础代码的熟悉度,更多的还是抱着学习的态度的。
其他的也就不多说了,本篇文章所要讲的是我们日常工作或面试中常见的一位“兄弟”ArrayList,对于集合类平常的使用,无非就是增删改查,对于面试中常问的问题,那么无非也就是线程安全与否、底层数据结构、和LinkedList的区别如何等等。
一、ArrayList的基本实现
在正式开始之前,先来看下一张ArrayList的UML类图:
从上面这张类图中,可以直接看出ArrayList的基本实现,那么我们就先来罗列一下:
实现:
1)List接口:说明ArrayList具有List的相关功能
2)RandomAccess:这就是ArrayList的一个特性,具有速记访问数据的能力
3)Cloneable:说明ArrayList是具有克隆的功能
4)Serializable:说明其可被序列化
继承:
AbstractList:ArrayList => AbstractList => AbstractCollection => Collection => Iterable, 从这里可以看出ArrayList具有 Collection和Iterable两个几口中的相关功能,中间还有AbstractList实现了List接口的这么一条路线。
二、ArrayList基本信息
就着前面的一些信息,先来看看ArrayList中的一些基本信息:
基本属性:
// 默认初始化容量:10,这个初始化是在没有给定初始容量,且第一次添加数据时进行的初始化
private static final int DEFAULT_CAPACITY = 10;
// 有参初始构建时,所传入的参数为0时的数据共享大小
private static final Object[] EMPTY_ELEMENTDATA = {};
// 无参初始构建时的默认大小,用于与EMPTY_ELEMENTDATA的区分
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 数据存储的数组
transient Object[] elementData; // non-private to simplify nested class access
// 数组长度
private int size;
构造方法:
// 有参构造
public ArrayList(int initialCapacity) {
// 根据自设定的大小来定义数组大小
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 所设定的大小为0,则使用EMPTY_ELEMENTDATA参数来定义数组大小
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
// 无参构造,使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA参数来定义数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 使用collection作为参数构造
public ArrayList(Collection<? extends E> c) {
// 获取collection中的数据
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// 如果elementData的class不是Object[]的class类型,则使用Arrays.copyOf(。。。)拷贝数据
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
三、添加方法
// 1.添加方法
public boolean add(E e) {
// 确保每次添加时,内部容量是足够的
ensureCapacityInternal(size + 1); // Increments modCount!!
// 添加数据
elementData[size++] = e;
return true;
}
// 2.根据索引位置添加数据
public void add(int index, E element) {
// 检出下标是否越界
rangeCheckForAdd(index);
// 确保内部容量是否足够
ensureCapacityInternal(size + 1); // Increments modCount!!
// 进行数据移动
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 赋值
elementData[index] = element;
size++;
}
// 3、添加集合类型的数据
public boolean addAll(Collection<? extends E> c) {
// 获取集合数据
Object[] a = c.toArray();
// 获取集合长度
int numNew = a.length;
// 确保内部容量:s当前size + 集合长度numNew
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
// 4、根据索引添加集合数据
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
// 判断所需要插入的位置
int numMoved = size - index;
// 如果插入位置不是底部,则根据index的位置到ndex + numNew的区间进行数据插入
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 否则从数组尾部怜恤插入
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
// 确保内部空间是否足够
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 当elementData 不等于空时
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
// 先取出老数据的长度
int oldCapacity = elementData.length;
// 再根据老数据的长度扩容0.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新的size小于minCapacity,则使用minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新的长度减去MAX_ARRAY_SIZE (Integer.MAX_VALUE - 8),则调用hugeCapacity方法
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 若以上条件都不成立,则直接根据newCapacity进行数组扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
//
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如果minCapacity 大于Integer.MAX_VALUE - 8的长度,则使用Integer.MAX_VALUE,否则使用MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
四、删除方法
// 1、根据索引删除数据
public E remove(int index) {
// 检查索引
rangeCheck(index);
modCount++;
// 很久索引提取数据
E oldValue = elementData(index);
// 计算出所要移除的空间,方便后面做数据移动
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 意思是,这个工作是由GC去做的
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
// 2、根据所传入的集合对象进行数据移除
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 void fastRemove(int index) {
modCount++;
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
}
// 3、清除集合数据
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
// 4、移除所有
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
// 调用batchRemove
return batchRemove(c, false);
}
// 批量删除
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
//遍历数组,并检查这个集合是否对应值,移动要保留的值到数组前面,w最后值为要保留的值得数量
//如果保留:将相同元素移动到前段,如果不保留:将不同的元素移动到前段
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
// 最后 r=size 注意for循环中最后的r++
// w=保留元素的大小
// 如果r!=size表示可能出错了
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
//如果w==size:表示全部元素都保留了,所以也就没有删除操作发生,所以会返回false;反之,返回true,并更改数组
//而 w!=size;即使try抛出异常,也能正常处理异常抛出前的操作,因为w始终要为保留的前半部分,数组也不会因此乱序
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
五、修改数据
// 根据索引,修改数据
public E set(int index, E element) {
rangeCheck(index);
// 获取旧数据
E oldValue = elementData(index);
// 新值赋值
elementData[index] = element;
return oldValue;
}
六、查询数据
// 1、通过索引获取数据
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
// 2、顺序查找,返回元素的最低索引值(最首先出现的索引位置)
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;
}
// 3、 逆序查找,返回元素的最低索引值(最首先出现的索引位置)
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;
}
七、克隆
// 按照注释上所说,这里所返回的只是一个浅克隆的数据
public Object clone() {
try {
// Object 的克隆方法,会复制本对象及其内所有基本类型成员和 String 类型成员,但不会复制对象成员、引用对象
ArrayList<?> v = (ArrayList<?>) super.clone();
// 对需要进行复制的引用变量,进行独立拷贝,将元素移入新的 ArrayList 中
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
八、彩蛋
在开始我们有写 transient Object[] elementData,这里使用了transient进行修饰elementData,这里的transient修饰符是防止序列化的作用,但是transient作用的其中之一是:一个一旦变量被transient所修饰,这个变量将不再是对象持久化的一部分了,该变量内容在序列化后无法获得访问。那么这个问题该怎么解决呢?下面的两个方法正式解决这个问题的。
// 从反序列化中重构ArrayList实例
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
// 读出大小和隐藏的东西
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
// 从输入流中将“所有的元素值”读出
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
// 将ArrayList实例序列化
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 size as capacity for behavioural compatibility with clone()
// 写入clone行为的容量大小
s.writeInt(size);
// 以合适的顺序写入所有的元素
// 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();
}
}
关于批量删除部分的代码参考:https://www.cnblogs.com/gxl1995/p/7534171344218b3784f1beb90d621337.html
ArrayList源码算是比较简单的,但是我想总有一些刚入门的同学们,所以我想着趁着自己复习的同时也写一点可以让入门的同学能看的文章。在所有的集合源码中,其中大多数的功能无非是增删改查,但也有比较复杂的,譬如HashMap的插入的源码,后面笔者也会去写相应的文章
由于前段时间抱恙在身,所以一直没有更新关于mybatis的收尾代码,不过大家放心,笔者最近又重新阅读了mybatis源码,所以准备更详细的去解析一下mybatis源码,我想大家的支持会是我前进最大的动力。