具体方法
add方法
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { // 扩容操作,稍后讲解 ensureCapacityInternal(size + 1); // Increments modCount!! // 将元素添加到数组尾部 elementData[size++] = e; return true; } 复制代码
第一步 扩容操作 第二步 往尾部添加一个元素 跟着往下看
ensureCapacityInternal(xxx); 确定内部容量的方法
private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { //看,判断初始化的elementData是不是空的数组,也就是没有长度 //因为如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小,但是带这里,还没有真正的初始化这个elementData的大小。 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } //确认实际的容量,上面只是将minCapacity=10,这个方法就是真正的判断elementData是否够用 ensureExplicitCapacity(minCapacity); } 复制代码
ensureExplicitCapacity(xxx);
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code //minCapacity如果大于了实际elementData的长度,那么就说明elementData数组的长度不够用,不够用那么就要增加elementData的length。这里有的读者就会模糊minCapacity到底是什么呢,这里给你们分析一下 /*第一种情况:由于elementData初始化时是空的数组,那么第一次add的时候,minCapacity=size+1;也就minCapacity=1,在上一个方法(确定内部容量ensureCapacityInternal)就会判断出是空的数组,就会给 将minCapacity=10,到这一步为止,还没有改变elementData的大小。 第二种情况:elementData不是空的数组了,那么在add的时候,minCapacity=size+1;也就是minCapacity代表着elementData中增加之后的实际数据个数,拿着它判断elementData的length是否够用,如果length 不够用,那么肯定要扩大容量,不然增加的这个元素就会溢出。 */ if (minCapacity - elementData.length > 0) //arrayList能自动扩展大小的关键方法就在这里了 grow(minCapacity); } 复制代码
grow(xxx); arrayList核心的方法,能扩展数组大小的真正秘密。
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //将扩充前的elementData大小给oldCapacity int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity if (newCapacity - minCapacity < 0)//这句话就是适应于elementData就空数组的时候,length=0,那么oldCapacity=0,newCapacity=0,所以这个判断成立,在这里就是真正的初始化elementData的大小了,就是为10.前面的工作都是准备工作。 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity超过了最大的容量限制,就调用hugeCapacity,也就是将能给的最大值给newCapacity newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //新的容量大小已经确定好了,就copy数组,改变容量大小咯。 elementData = Arrays.copyOf(elementData, newCapacity); } 复制代码
hugeCapacity();
//这个就是上面用到的方法,很简单,就是用来赋最大值。 private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //如果minCapacity都大于MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,反之将MAX_ARRAY_SIZE返回。因为maxCapacity是三倍的minCapacity,可能扩充的太大了,就用minCapacity来判断了。 //Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 也就是说最大也就能给到第一个数值。还是超过了这个限制,就要溢出了。相当于arraylist给了两层防护。 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } 复制代码
总结一下扩容过程
- 1.判断是否需要扩容,如果需要,计算需要扩容的最小容量
- 2.如果确定扩容,就执行grow(int minCapacity),minCapacity为最少需要的容量
- 3.第一次扩容是的容量大小是原来的1.5倍
- 4如果第一次 扩容后容量还是小于minCapacity,那就扩容为minCapacity
- 5.最后,如果minCapacity大于最大容量,则就扩容为最大容量
add(int, E)方法
public void add(int index, E element) { // 插入数组位置检查 rangeCheckForAdd(index); // 确保容量,如果需要扩容的话则自动扩容 ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); // 将index后面的元素往后移一个位置 elementData[index] = element; // 在想要插入的位置插入元素 size++; // 元素大小加1 } // 针对插入数组的位置,进行越界检查,不通过抛出异常 // 必须在0-最后一个元素中间的位置插入,,否则就报错 // 因为数组是连续的空间,不存在断开的情况 private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } 复制代码
这个也不难 前面我们数组已经实现了
get方法
public E get(int index) { // 先进行越界检查 rangeCheck(index); // 通过检查则返回结果数据,否则就抛出异常。 return elementData(index); } // 越界检查的代码很简单,就是判断想要的索引有没有超过当前数组的最大容量 private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } 复制代码
先检查数组越界,然后你就是直接去数组那边拿数据然后返回
set(int index, E element)
// 作用:替换指定索引的元素 public E set(int index, E element) { // 索引越界检查 rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } 复制代码
这个就是替换指定位置的元素
remove(int):通过删除指定位置上的元素
public E remove(int index) { // 索引越界检查 rangeCheck(index); modCount++; // 得到删除位置元素值 E oldValue = elementData(index); // 计算删除元素后,元素右边需要向左移动的元素个数 int numMoved = size - index - 1; if (numMoved > 0) // 进行移动操作,图形过程大致类似于上面的add(int, E) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 元素大小减1,并且将最后一个置为null. // 置为null的原因,就是让gc起作用,所以需要显示置为null elementData[--size] = null; // clear to let GC do its work // 返回删除的元素值 return oldValue; } 复制代码
remove(Object o)
public boolean remove(Object o) { // 如果删除元素为null,则循环找到第一个null,并进行删除 if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { // 根据下标删除 fastRemove(index); return true; } } else { // 否则就找到数组中和o相等的元素,返回下标进行删除 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); // 元素大小减1,并且将最后一个置为null. 原因同上。 elementData[--size] = null; // clear to let GC do its work } 复制代码
总结
ArrayList的优缺点
- ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快
- ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已
- 删除元素时,涉及到元素复制,如果要复制的元素很多,那么就会比较耗费性能
- 插入元素时,涉及到元素复制,如果要复制的元素很多,那么就会比较耗费性能
为什么ArrayList的elementData是用transient修饰的
- 说明:ArrayList实现了Serializable接口,这意味着ArrayList是可以被序列化的,用transient修饰elementData意味着我不希望elementData数组被序列化
- 理解:序列化ArrayList的时候,ArrayList里面的elementData未必是满的,比方说elementData有10的大小,但是我只用了其中的3个,那么是否有必要序列化整个elementData呢?显然没有这个必要,因此ArrayList中重写了writeObject方法。
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() 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(); } } 复制代码
- 优点:这样做既提高了序列化的效率,减少了无意义的序列化;而且还减少了序列化后文件大小。
版本说明
- 这里的源码是JDK8版本,不同版本可能会有所差异,但是基本原理都是一样的。
结尾
ArrayList就这么多了,大家自己可以对着博客,对着源码看,我感激它这个源码不是很难,基于数组的把可能是 因为博主也是一个开发萌新 我也是一边学一边写 我有个目标就是一周 二到三篇 希望能坚持个一年吧 希望各位大佬多提意见,让我多学习,一起进步。