ArrayList源码分析(Java&Android)
我的CSDN博客
我的GitHub
我的GitHub博客
自从开始看源码之后发现一个好处,就是各种方法串在一起,想弄懂就要深入进去了解其他类的方法,慢慢的就会越来越了解内部的原理。
集合类是java的重点。今天看了看ArrayList的源代码,改天要仔细看看集合类的框架,分享一下我的收获吧。
声明:开始看源代码了才知道android和java的源代码有好多出入,都了解一下吧,我把java和android源码比较了一下,相似的还是多的。我是以android源码为主,因为感觉android比较繁琐,不同的地方会贴java的代码对比。
java真的设计的很好,很多类内部实现差别很大,但是在外部看起来是一样的,设计良好的API使我们不需要去关注内部的实现,举个例子说,我们需要一个绳子,他可以是真丝制成的,那么它比较坚固,也可以是牛皮制成的,那么它可以不怕水,但是对使用绳子的人来说,他声明自己需要的绳子需要具有什么样的特性,但是他却不知道真丝的需要什么工艺,牛皮的又需要什么工艺。我们程序员就是使用绳子的人,各种各样的List就是绳子,虽然内部实现大相径庭,但是都对外开放了一条绳子的外表,使得我们可以极为方便的使用它,这体现了面向对象的编程思想,也可以使java程序员更加专注的实现功能而不尽量少的关心逻辑和算法。
1.ArrayList内部基于数组实现,继承了AbstractList类,实现了Cloneable接口表示其可被克隆复制,实现了Serializable接口表示其可被序列化,实现了RandomAccess接口,表示其可快速随机访问(但是这个接口是空的,只是表示它具有这样的特性)。
public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess{}
2.一些常量和成员
android中提供了最小容量,java中并没有这个成员,而是使用了10这个数字作为初始容量。
//最小容量
private static final int MIN_CAPACITY_INCREMENT = 12;
//元素数量
int size;
//对象数组,transient表示在序列化时它会被忽略。。啥意思?
transient Object[] array;
3.初始化
3.1指定初始容量,首先容量大于0,如果容量==0会使用EmptyArray.OBJECT来初始化,在android的源码里面链接不到EmptyArray这个类,去了这个网站看到了源码,EmptyArray.OBJECT是一个容量是0的数组。所以也就是说,如果指定容量==0则创建一个容量是0的对象数组,反之创建一个指定容量大小的数组。(java中没有做这个判断,本来这个判断就什么用)
public ArrayList(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("capacity < 0: " + capacity);
}
array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);
}
3.2无参构造函数,同上,初始容量是0。但是java源码中初始容量是10。
public ArrayList() {
array = EmptyArray.OBJECT;
}
//java源码
public ArrayList() {
this(10);
}
3.3初始化时拷贝集合,主要的操作是判断是不是一个对象数组,如果是直接内部数组直接指向,如果不是进行一次拷贝,java中所有类都继承自Object。这是为了防止基本数据类型吗?(<-这是我不懂的地方)用的方法不一样,java代码更少一点。
3.3.1System.arraycopy()方法是native修饰的,使用C实现的底层方法。
3.3.2Arrays.copyOf()函数内部也使用了System.arraycopy()方法。
3.3.3就是我不懂的那个地方了,感觉使用这个方法可以解决不是对象数组的问题,但是不懂这个判断是为了什么,System.arraycopy又是如何内部实现的。
if (a.getClass() != Object[].class) {System.arraycopy(a, 0, newArray, 0, a.length);}
public ArrayList(Collection<? extends E> collection) {
if (collection == null) {
throw new NullPointerException("collection == null");
}
Object[] a = collection.toArray();
if (a.getClass() != Object[].class) {
Object[] newArray = new Object[a.length];
System.arraycopy(a, 0, newArray, 0, a.length);
a = newArray;
}
array = a;
size = a.length;
}
//java源码
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
4.Add方法
4.1Android中的实现
@Override
public boolean add(E object) {
Object[] a = array;
int s = size;
if (s == a.length) {
Object[] newArray = new Object[s +
(s < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : s >> 1)];
System.arraycopy(a, 0, newArray, 0, s);
array = a = newArray;
}
a[s] = object;
size = s + 1;
modCount++;
return true;
}
@Override
public void add(int index, E object) {
Object[] a = array;
int s = size;
if (index > s || index < 0) {
throwIndexOutOfBoundsException(index, s);
}
if (s < a.length) {
System.arraycopy(a, index, a, index + 1, s - index);
} else {
// assert s == a.length;
Object[] newArray = new Object[newCapacity(s)];
System.arraycopy(a, 0, newArray, 0, index);
System.arraycopy(a, index, newArray, index + 1, s - index);
array = a = newArray;
}
a[index] = object;
size = s + 1;
modCount++;
}
4.2Java中的实现,跟android基本一样的,之所以代码简单了,是因为java将判断扩容的操作放在了方法实现,但是androd只是将计算新容量的方法提取出来了。
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
4.3解释一下下面这段代码,在java中也是如此实现的只是提取到了方法中,会在下面的扩容机制中说明。
4.3.1.首先最后都进行了一个赋值操作a[index] = object;
前面的代码的工作就是将这个位置空出来。代码5
4.3.2.如果当前的size没有超过length,则将index位置之后的位置向后移动一位,将index位置空出来(代码1),如果超过长度了,则进行一次扩容(代码2),将数据拷贝到新数组(代码3),再将数组后移一位(代码4)
4.3.3.它是怎么移动的呢?System.arraycopy(a, index, newArray, index + 1, s - index)
了解参数的含义(src,源数组中开始复制的位置,dest,目标数组开始粘贴的位置,复制的长度),这样就明白了,包括代码1是一样的意思,a数组和newArray数组是相同的,将a中index开始的数据复制到newArray中index+1开始的位置,然后复制的长度是s-index,刚好index位置被空出来了,举个例子,数据a={21,22,23,25,26},newArray={21,22,23,25,26},需要在3的位置插入一个24,则将3开始的size-index(5-3=2)长度的数组也就是25,26,复制到newArray中index+1(3+1=4)开始的位置,得到新数组21,22,23,空,25,26,然后array[3]=24;
Object[] a = array;
int s = size;
if (s < a.length) {
System.arraycopy(a, index, a, index + 1, s - index);//---1
} else {
Object[] newArray = new Object[newCapacity(s)];//---2
System.arraycopy(a, 0, newArray, 0, index);//---3
System.arraycopy(a, index, newArray, index + 1, s - index);//---4
array = a = newArray;
}
a[index] = object;//5
size = s + 1;
5.扩容机制
5.1单独拿出来是因为android和java扩容机制稍有不同
5.2Android中的实现
5.2.1.扩容时,如果当前容量小于6则让其等于12,否则扩大为原来的两倍。
private static int newCapacity(int currentCapacity) {
int increment = (currentCapacity < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : currentCapacity >> 1);
return currentCapacity + increment;
}
//这只是计算了新的容量,真正的扩容如此实现
Object[] newArray = new Object[newCapacity(s)];
System.arraycopy(a, 0, newArray, 0, index);
5.3.java中的实现,java中数组拷贝都是用Arrays.copyOf()方法的,基本原理相同。
5.3.1.可以发现java扩容将容量扩大到了原来容量的1.5倍。
5.3.2.这里有一行代码,看注释的意思是这个方法更加优化了,调皮的程序员a.大意是minCapacity 通常更加逼近size.
if (newCapacity < minCapacity) newCapacity = minCapacity;
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}