(以下源码建立在JDK 10版本基础上)
ArrayList这个类用的实在是太频繁了,除基本类型之外应该算是最常用了吧,但是一直用过却一直不曾研究过里面的源码,这是程序员的大忌,用什么就要研究什么,否则只是代码工匠谈不上代码师。
在开始ArrayList学习之前,我一直有个疑问,就是数组可以动态扩容吗?我们知道java声明数组时,必须指明数组的个数或者列举出所有的元素(等于告诉编译器自己长度为多少)
String a[] = new String[]{"1","2","3"};
String b[] = new String[10];
String c[] = new String[];//这一行编译不过去
可是如果我们使用的时候发现数据太多了,数组太小了,需要扩容怎么办呢?
没办法,新建一个数组,将旧的数组数据拷贝到新数组里面
String a[] = new String[]{"1","2","3"};
String b[] = new String[a.length+10];
例子比较简单,这样做尚可但是如果比较复杂的数组怎么处理长度呢?而且即使你处理完了发现数组又小了,该如何是好?继续创建新的数组吗?这样做太麻烦了!
创建一个可改变长度的数组就好了——ArrayList
正因为有了这样的需求,ArrayList横空出世,ArrayList就是可改变长度的数组
Resizable-array implementation of the {@code List} interface.
与此同时,其元素还包括null
Implements all optional list operations, and permits all elements, including {@code null}
Vector线程安全,ArrayList线程不安全
(This class is roughly equivalent to {@code Vector}, except that it is unsynchronized.)
其成员变量有下面几个
/**
* Default initial capacity. 默认集合长度为10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
* 当你用new ArrayList(0)初始化的时候指定长度为0就会用到这个数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* 当你使用new ArrayList()生成集合的时候默认使用这个数组,解释具体看后面的代码
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
前面transient 表示是暂时的变量,序列化和反序列化都不会操作这个变量
这个变量什么呢?就是在不断变化的那个数组,就好像我们使用p=head,在不破坏原有指针的情况下,构造了一个局部变量来代替head指针,这个也是如此用于代替上面两个数组
transient Object[] elementData;
集合的长度
需要注意的是size是表示集合的长度,而后续源码中有elementData.length表示的是内部数组的长度,这两个不是一个概念!size是暴露给调用者的,elementData这个数组是底层数组,对于用户是不可知的,他的长度不用和size相同
private int size;
集合最大长度,最大值-8,为啥-8我也不知道????
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
1 先看看我们常用的初始化操作 new ArrayList<>()
List<String> a=new ArrayList<>();
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
也就是说如果我们使用无参构造方式创建的集合,使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个数组
2 再看看add()方法
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
如果发现数组已经满了,程序会走进grow()方法,用于扩容这个数组,操作成功之后集合长度+1
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private Object[] grow() {
return grow(size + 1);
}
最少也要在原来的数组上增加1个,所以size+1,接下来我们看下Arrays.copyOf里面的newCapacity方法,这个方法是获取要扩容的长度,我们增加一个元素只扩容1的话岂不是每次扩容全部方法都要走一遍?不如多扩容点,这样的话省的以后麻烦
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
int newCapacity = oldCapacity + (oldCapacity >> 1);
这句话的意思是新的数组长度要是旧的1.5倍,也就是说扩容要扩充1.5倍,>>1表示除以2的意思
当集合为空的时候,newCapacity - minCapacity <= 0,又是DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组所以返回的扩容长度为DEFAULT_CAPACITY,所以默认集合长度为10
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
new Object[newLength]是新建了一个数组
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
这段代码很重要,数组扩容就是用的这个方法
这句话的意思是:
将original数组从0位置至original.length位置,复制到copy数组0位置到Math.min位置。
为什么做了一个取最小值操作呢?
因为有可能是数组增加也有可能是数组删除,防止造成数组下标越界。
经过了一系列操作,这个数组变成了一个新数组copy,数组长度也变化了
3 再看看get()方法
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
Objects.checkIndex(index, size);这句话是检验是否下标越界,如果越界的话会抛出异常
本身就是数组,所以取相应索引的数据也很容易看懂
4 remove方法
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
这也很容易只是找到对应object的索引,具体删除的工作交给了fastRemove方法
found:{}这个是语句块命名的意思,现在基本上不这么写了,估计是作者写1.2java的时候还是忘不掉c语言的goto语句就这么写了,就是返回个索引而已,很简单
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
如果要删除的元素在最后一个,也就是说(newSize = size - 1) == i的话,没有必要操作数组,直接数组的最后一个数据为空就好了,但是如果是前面的某个数据要被删除呢?
System.arraycopy(es, i + 1, es, i, newSize - i);
又是这句话,意思是把i之后的元素复制到i个位置开始到newSize - i长度,这个有点绕,举个例子
数组 1 2 3 4
我想删除第二个元素,下标是1
变成新数组 1 3 4
把2删除掉,34前移
5 addAll()方法
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
modCount++;
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
System.arraycopy(a, 0, elementData, s, numNew);
size = s + numNew;
return true;
}
增加的操作之前已经说过啦,但是有一点需要说下,项目中是不允许addAll空的集合的,因为作者在第一句Object[] a = c.toArray();使用的时候就没有做判空操作,所以我项目中就遇到过源码的空指针异常,把我吓坏了~~~
6 clear方法
public void clear() {
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
数组还在,只是里面每个数据变成空了而已,但是size已经被赋值成了0,也就是说对于外部来说这个集合为空但是实际上里面还是有数组的。
好啦,以上就是ArrayList的大致源码总结,欢迎读者朋友下方留言。