ArrayList的源码分析

ArrayList是最常用的数组,这里我们分析一下源码:

ArrayList继承了AbstractList<E> ,并且实现了List<E>, RandomAccess, Cloneable, java.io.Serializable
里面有几个比较重要的成员变量:

序列化Id

private static final long serialVersionUID = 8683452581122892189L;

默认的初始化容量10:

private static final int DEFAULT_CAPACITY = 10;

用于空实例的共享空数组实例:

private static final Object[] EMPTY_ELEMENTDATA = {};

用于空实例的共享空数组实例,这个跟EMPTY_ELEMENTDATA的区别是在于当第一个元素被添加以后怎么知道需要多少扩容

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

数组缓冲区中的数组的元素存储。ArrayList的容量是这个array buffer的的长度

transient Object[] elementData;

ArrayList的长度

private int size;

这是一个常量,用来限制数组的最大长度,Integer.MAX_VALUE = 2的31次方-1

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

下面看一下ArrayList的几个构造函数:

这个是指定初始大小的构造函数

public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];//如果初始值>0则初始化数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;//如果初始值为0,则将孔数组赋给这个数组
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

这个是默认的构造函数

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;会把默认的空数组赋值给这个数组
}

还有一个构造方法,传入的参数是一个collection对象

public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//把collection转为一个Array
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)//是否成功转化为Object类型数组
elementData = Arrays.copyOf(elementData, size, Object[].class);//转化失败就进行复制
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

下面是分析里面具体的每个方法:

把 ArrayList实例的容量装饰成这个list当前的大小。一个应用可以使用这个方法来最小化ArrayList实例的产生

public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}

增加ArrayList实例的容量,如果有必要,要确保这个能保证当前所有元素最小的容量

public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)//先判断一下数组是不是为空
if (minCapacity > minExpand) { 如果最小容量大于默认容量,就要开始扩容了
ensureExplicitCapacity(minCapacity);
}
}

进行扩容初始化,这是一个私有方法

private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//找到最小的初始容量大小
}

    ensureExplicitCapacity(minCapacity);
}

这也是一个私有方法,开始进行扩容

private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)//判断最小的容量是不是> 数组的长度
grow(minCapacity);//扩容的具体实现
}

这是个私有方法,这个是扩容的具体实现

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //获取当前数组的大小
int newCapacity = oldCapacity + (oldCapacity >> 1); //新数组的容量算法为:当前数组大小 + (当前数组大小 / 2),相当于原来的1.5倍大小
if (newCapacity - minCapacity < 0)//如果新的容量大小比最小的容量还小,就用最小扩容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//如果新的容量大小比数组规定的最大容量还大,就调用hugeCapacity来算容量
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//进行扩容
}

这个也是一个私有方法,用来算出当新的数组容量比Java规定的最大数组容量还大的时候,要设置多少作为最大容量

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? //判断最小容量是不是已经超过了Java规定的最大数组容量
Integer.MAX_VALUE : 超过了就用Integer最大值 2的31次方-1
MAX_ARRAY_SIZE; 没有就用数组最大容量 Integer.MAX_VALUE - 8
}

返回list里面元素的个数

public int size() {
return size;
}

判断list是不是为空,通过判断szie是不是为0

public boolean isEmpty() {
return size == 0;
}

判断是不是传入的元素包含在了当前list里面,具体实现是在indexOf方法

public boolean contains(Object o) {
return indexOf(o) >= 0;//这个意思就是至少包含一次,通过查看indexof()的代码,返回值是数组下标,而数组下标是从0开始的,所以这里写>= 0
}

这个方法是把传入的Object对象跟list里面的值进行比较,返回的是符合要求的值的数组下标而不是个数

public int indexOf(Object o) {
if (o == null) {//这段代码刚开始没有看懂,后来又看了看,原来判断当前传入的对象如果为空,
for (int i = 0; i < size; i++)//并且list里面也有空对象,那么也是要找到,并说明存在为null的
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)//这个就是通过用Object的equals方法来做比较,当出现有符合要求的值就返回这个值的数组下标
if (o.equals(elementData[i]))
return i;
}
return -1;//没有符合条件的就返回-1
}

这个是ArrayList的一个克隆方法,返回的一个浅克隆的ArrayList实例,但是ArrayList里面的元素是没有被克隆的

public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();这里面是调用了Object的clone()方法
v.elementData = Arrays.copyOf(elementData, size);用Arrays的copyOf方法来把元素都放进新的实例中
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}

返回一个数组包含了所有当前list的元素,按照当前的数组序列,实际是用Arrays的copyOf方法来实现的

public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}

返回一个数组包含了所有当前list的所有元素,并且元素类型是指定的元素类型

public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size) //如果传入的数组长度大于当前的的数组长度,则返回空
a[size] = null;
return a;
}

根据数组下标返回元素

@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}

根据数组下标找到元素

public E get(int index) {
rangeCheck(index);//数组越界检查

    return elementData(index);//通过调用elementData来找到相应元素
}

将传入的参数元素添加到指定的下标渣饼返回这个元素

public E set(int index, E element) {
rangeCheck(index);//数组越界检查

    E oldValue = elementData(index);//先找到当前元素
    elementData[index] = element;//把传入的元素设置到指定的位置
    return oldValue;
}

添加一个元素到到当前list里面,如果添加成功则返回true

public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!//新进行扩容判断
elementData[size++] = e;//直接在当前list的后面进行添加
return true;
}

将传入的元素添加在指定的数组下标地址

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;//在list里面将传入的元素赋值给指定的数组下标位置
size++;
}

//将指定的数组下标从list中删除

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);//根据算出的要移动的元素,直接进行数组元素移动,这里也是为什么数组的删除的时间复杂度也是很大的原因
elementData[--size] = null; // clear to let GC do its work
return oldValue;//并返回这个元素
}

//将传入的Object对象从数组中移除,移除成功则返回true

public boolean remove(Object o) {
if (o == null) {//这里为什么为null还要处理,因为list里面还有为空的对象
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);//调用fastRemove进行删除
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {//通过遍历找到传入的Object然后进行删除
fastRemove(index);
return true;
}
}
return false;
}

这是一个私有的方法,用来快速删除元素,这个方法跟remove是一样的,只是这个方法没有检测数组是不是越界了,并且没有返回值

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
}

这个方法就是将数组清空

public void clear() {
modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

将传入的collection全部添加到当前数组

public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();//通过这个方法将collection变成一个数组
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount//通过当前数组的大小跟传入的数组大小进行扩容判断
System.arraycopy(a, 0, elementData, size, numNew);//通过这个方法把传入的collection都加入到当前数组
size += numNew;
return numNew != 0;
}

这个方法是将传入的数组放在当前list的指定位置

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;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);//先将当前数组在index以及以后的元素往后移动
System.arraycopy(a, 0, elementData, index, numNew);//将传入的数组从指定的位置开始插入
size += numNew;
return numNew != 0;
}

这个方法是将指定数组范围内的元素删除

protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);//先将需要删除的元素范围的位置给移除掉
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;//通过遍历将这些元素进行删除
}
size = newSize;
}

数组越界检查

private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

数组越界检查,这个检查是在add的时候用

private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

这个是构造数组越界的时候的异常语言

private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}

这个方法是将所有包含在collection的元素从当前数组中移除

public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);//调用Object的判断是不是空指针的方法
return batchRemove(c, false);//调用这个方法来删除

}

这个方法正好相反,是将所有没有包含在collection的元素从当前数组中移除

public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}

这个方法就是上面两个方法的具体实现,其中complement为true代表删除所有不包含在传入的数组里面的元素,为false时代表删除所有在传入的数组中的元素

private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)//这个逻辑就是根据传入的boolean值来判断是怎么删除的
elementData[w++] = elementData[r];
} finally {//将移除后的元素进行删除
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
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;
}

将一个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();//调用ObjectOutputStream的defaultWriteObject,这个方法是用来将一个类的非静态和非transient区域转化为流
// Write out size as capacity for behavioural compatibility with 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();
}
}

从stream反序列化成一个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();
}
}
}

从传入的位置开始返回一个iterator list包含了这个位置以后的所有元素

public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}

返回一个包含了所有元素的iterator list

public ListIterator<E> listIterator() {
return new ListItr(0);
}

这个方法也是返回一个包含了所有元素的iterator list,只是实现方式不一样

public Iterator<E> iterator() {
return new Itr();
}

往下分析发现了一些内部类以及一些不常用的方法,这里就不一一列举了,有兴趣可以自己看一下源码
这个主要说一下这个sort方法,这个经常被用到:ArrayList的sort是重写了父类List的sort排序,而在List里面调用了Arrays的sort排序算法,如果大家想要实现自己的逻辑排序算法的话,要调用sort方法并且新写一个Comparator重写compare方法,compare方法的返回值,代表了两个数的比较结果

public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}

下面是一个简单的运用sort实现Student类按照年龄进行排序的(Student类就不写了,只是里面有age这个成员变量)

List<Student> datas = new ArrayList<Student>();
datas.sort(new Comparator<Student>(
) {
@Override
public int compare(Student o1, Student o2) {
// return o1.getAge() - o2.getAge(); 升序
return o2.getAge() - o1.getAge(); //降序
}
});

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容