ArrayList源码解读
1.背景
源码解读是提升编程能力的有效方式
在面试中也经常问到.....
2.自己开发一个类似ArrayList的对象
解读源码的最佳方式就是自己开发一个类似的....
package com.ldp.collection.my; import java.util.AbstractList; import java.util.Arrays; import java.util.List; import java.util.RandomAccess; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 04/04 6:39 * @description <p> * 需求:自己设计一个基于数组的集合,功能类似ArrayList * 设计思路 * 1.使用数组来存放添加的元素 * 2.默认数组长度为10,满了后按照原来的1.5倍扩容 * 3.设置的最大数组长度为MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,实际最大长度不超过 Integer.MAX_VALUE * 4.编写一个构造方法,一个添加元素的方法,一个获取元素的方法 * 难点: * 扩容算法,即计算的实际的数组长度 * 总结: * 当你看懂了我自己写的MyArrayList这个集合类,你再去看ArrayList, * 你会发现是不是ArrayList是不是"抄袭"我的MyArrayList,居然和我写的一模一样...哈哈哈... * </p> */ public class MyArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { /** * 默认的空集合 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 用于存放数据的集合 * transient:临时的 * 本文要介绍的是Java中的transient关键字,transient是短暂的意思。 * 对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略。 * 因此,transient变量不会贯穿对象的序列化和反序列化,生命周期仅存于调用者的内存中而不会写到磁盘里进行持久化。 */ transient Object[] elementData; // 数组的默认长度 private static final int DEFAULT_CAPACITY = 10; /*最大数组长度 *有些虚拟机会预留出一定长度作为数组的一些头信息,所以数组最大长度达不到Integer的最大值2~32-1 = 2147483647(21亿..)左右, *这个-8是为了避免内存溢出。 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 数组长度 private int size; /** * 构造方法 */ public MyArrayList() { // 设一个默认的空数组,Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 添加一个元素 * * @param e * @return */ @Override public boolean add(E e) { // 确保数组长度足够,需要的最小长度为当前数组的长度+1 ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } /** * 确保数组的容量不出界 * * @param minCapacity */ private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } /** * 计算数组的长度(容量) * * @param elementData 集合的数组 * @param minCapacity 需要的长度 * @return 返回一个数组长度 */ private static int calculateCapacity(Object[] elementData, int minCapacity) { // 如果数组为默认的空数组 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 在默认的数组长度10 与 需要的长度中取最大值 return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } /** * modCount 解读 * modCount只记录arraylist被修改次数,在每次添加修改删除的时候进行+1, * 这个参数的作用是在多线程并发的时候,由于arraylist不是线程安全的,在当前线程进行遍历或其他操作的时候, * 如果其它线程修改了改值,那么当前线程能检测到该参数发生变化,会抛异常。 * * @param minCapacity */ private void ensureExplicitCapacity(int minCapacity) { // 累加集合的修改次数 modCount++; // 如果需要的数组长度大于了实际的数组长度,则进行扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * 扩容算法 * * @param minCapacity */ private void grow(int minCapacity) { // 原来的数组长度 int oldCapacity = elementData.length; /* 新的数组长度, * 1.oldCapacity >> 1 右移动2位表示除2,即新长度为原来的+原来的一半(若原来为10,变为15) * 2.oldCapacity 第一次进入的时候 oldCapacity =0,因此计算出来的newCapacity=0 */ int newCapacity = oldCapacity + (oldCapacity >> 1); // 加一半后长度还是不够,则用传递过来的数值(addAll来说,就是原长度+新集合长度) if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 新的数组长度大于了设置的最大长度时,计算一个新的长度出来 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 执行扩容,扩容的实现时把原来的数组复制到新的数组中来,新的数组长度就是newCapacity elementData = Arrays.copyOf(elementData, newCapacity); } /** * 数组最大值计算 * * @param minCapacity 数组的当前需要的最小长度 */ private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) throw new OutOfMemoryError(); // 三目运算 , minCapacity大于当前设置的数组最大值时,就取Integer.MAX_VALUE,否则就是设置的最大值 // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } @Override public E get(int index) { // 检查下标是否越界 rangeCheck(index); return elementData(index); } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private String outOfBoundsMsg(int index) { return "Index: " + index + ", Size: " + size(); } @SuppressWarnings("unchecked") E elementData(int index) { // 从数组中取出一个元素 return (E) elementData[index]; } @Override public int size() { return size; } }
3.源码解读
3.1.构造方法解读
public ArrayList() {
// 设一个默认的空数组,Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 开始new ArrayList()的时候,数组长度其实是0,并不是10,是个空数组。
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3.2.add方法解读
@Override
public boolean add(E e) {
// 确保数组长度足够,需要的最小长度为当前数组的长度+1
ensureCapacityInternal(size + 1);
// 放在当前元素的下一个位置
elementData[size++] = e;
return true;
}
确定数组长度的方法ensureCapacityInternal
private void ensureCapacityInternal(int minCapacity) {
// calculateCapacity 计算需要数组长度minCapacity
// ensureExplicitCapacity 确定明确的值,即是否扩容
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity 计算需要数组长度minCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果数组为默认的空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 在默认的数组长度10 与 需要的长度中取最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
ensureExplicitCapacity 确定明确的值,即是否扩容
/**
* modCount 解读
* modCount只记录arraylist被修改次数,在每次添加修改删除的时候进行+1,
* 这个参数的作用是在多线程并发的时候,由于arraylist不是线程安全的,在当前线程进行遍历或其他操作的时候,
* 如果其它线程修改了改值,那么当前线程能检测到该参数发生变化,会抛异常。
*
* @param minCapacity
*/
private void ensureExplicitCapacity(int minCapacity) {
// 累加集合的修改次数
modCount++;
// 如果需要的数组长度大于了实际的数组长度,则进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
grow扩容算法
/**
* 扩容算法
*
* @param minCapacity
*/
private void grow(int minCapacity) {
// 原来的数组长度
int oldCapacity = elementData.length;
/* 新的数组长度,
* 1.oldCapacity >> 1 右移动2位表示除2,即新长度为原来的+原来的一半(若原来为10,变为15)
* 2.oldCapacity 第一次进入的时候 oldCapacity =0,因此计算出来的newCapacity=0
*/
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 加一半后长度还是不够,则用传递过来的数值(addAll来说,就是原长度+新集合长度)
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 新的数组长度大于了设置的最大长度时,计算一个新的长度出来
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 执行扩容,扩容的实现时把原来的数组复制到新的数组中来,新的数组长度就是newCapacity
elementData = Arrays.copyOf(elementData, newCapacity);
}
最大长度计算
/**
* 数组最大值计算
*
* @param minCapacity 数组的当前需要的最小长度
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
// 三目运算 , minCapacity大于当前设置的数组最大值时,就取Integer.MAX_VALUE,否则就是设置的最大值
// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
3.3get方法解读
这个比较简单
@Override
public E get(int index) {
// 检查下标是否越界
rangeCheck(index);
// 获取一个元素
return elementData(index);
}
/**
* 检查下标是否越界
* @param index
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size();
}
@SuppressWarnings("unchecked")
E elementData(int index) {
// 从数组中取出一个元素
return (E) elementData[index];
}
3.4.size方法解读
@Override
public int size() {
return size;
}
4.总结
新增一个元素,调用add方法,
如果长度没超出,那么就在数组下个位置放置该元素,
如果超出了,按照当前长度的一半增长,采用的是右位移的算法(如果对位移等基础知识不清楚的可以看之前讲的<<编程基础中的基础>>)。
最后:其实你采用断点的方式debug一下,或许对源码的理解会更加深入.