简介
ArrayList是实现了List接口的集合,也是很常用的集合,是一个有序可重复的集合,是使用数组实现的,分析ArrayList源码实现,使用场景,自己实现一个ArrayList
原文地址:itzmn.github.io/2018/12/05/…
源码分析
底层实现
变量
// 集合的默认容量private static final int DEFAULT_CAPACITY = 10;// 空实例的实例对象private static final Object[] EMPTY_ELEMENTDATA = {};// 用于存储默认构造的实例,在第一次添加的时候,区分数组的长度private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 存放集合元素的数组transient Object[] elementData; // non-private to simplify nested class access// 存储这个集合的数据数量private int size;
复制代码
构造方法
// 传入集合长度
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}
// 默认构造函数
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
// 传入一个集合的构造函数
public ArrayList(Collection<? extends E> c) {elementData = c.toArray();if ((size = elementData.length) != 0) {// c.toArray might (incorrectly) not return Object[] (see 6260652)if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);} else {// replace with empty array.this.elementData = EMPTY_ELEMENTDATA;}}
复制代码
基础方法
size
public int size() {return size;}
复制代码
返回集合内部的数据量
toArray
public Object[] toArray() {return Arrays.copyOf(elementData, size);}
复制代码
此方法会返回一个新的数组,创建一个新的,则对这个新的操作,不会 改变原来数组的值
toArray(T[] a)
public <T> T[] toArray(T[] a) {if (a.length < size)// 如果传入的数组长度小于集合长度,则拷贝集合中的数据,返回一个集合长度的数组return (T[]) Arrays.copyOf(elementData, size, a.getClass());// 如果传入数组大于集合长度,则拷贝全部数据,未填满的赋值为null,System.arraycopy(elementData, 0, a, 0, size);if (a.length > size)a[size] = null;return a;}
复制代码
此方法会返回新数组
iterator
public Iterator<E> iterator() {return new Itr();}
复制代码
此方法返回一个迭代器,这个是内部类
private class Itr implements Iterator<E> {// 要返回的下一个节点的索引int cursor; // index of next element to return// 返回最后一个元素的索引,如果没有就是-1int lastRet = -1; // index of last element returned; -1 if no such// 被更改元素的数量int expectedModCount = modCount;Itr() {}// 是否有下一个节点public boolean hasNext() {// 判断当前节点是否是最后一个节点return cursor != size;}@SuppressWarnings("unchecked")public E next() {// 判断当前集合是否被修改checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();// 当前指针,指向下一个节点cursor = i + 1;// 返回当前节点的值return (E) elementData[lastRet = i];}public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {// 删除当前节点的上一个元素ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;// 更改集合的修改次数expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}@Override@SuppressWarnings("unchecked")public void forEachRemaining(Consumer<? super E> consumer) {Objects.requireNonNull(consumer);final int size = ArrayList.this.size;int i = cursor;if (i >= size) {return;}final Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length) {throw new ConcurrentModificationException();}while (i != size && modCount == expectedModCount) {consumer.accept((E) elementData[i++]);}// update once at end of iteration to reduce heap write trafficcursor = i;lastRet = i - 1;checkForComodification();}final void checkForComodification() {// 当集合的被修改次数,与保存的被修改次数不同的时候,说明在迭代数据的时候该集合被修改了,说明数据有可能不正常,抛出异常,快速失败if (modCount != expectedModCount)throw new ConcurrentModificationException();}}
复制代码
增删改查
添加
add方法,将数据添加到数组中
public boolean add(E e) {// 校验数据ensureCapacityInternal(size + 1); // Increments modCount!!elementData[size++] = e;return true;}
复制代码
开始校验数据,
1.返回数组需要的长度
ensureCapacityInternal(int minCapacity)
// 返回数组需要的长度private static int calculateCapacity(Object[] elementData, int minCapacity) {// 判断,如果是默认构造方法,则返回一个默认的数组长度,if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}// 返回数组需要的长度return minCapacity;}
复制代码
2.判断需要的数组是否越界
private void ensureExplicitCapacity(int minCapacity) {// 修改集合的被修改次数modCount++;// 比较需要的长度与数组真正的长度if (minCapacity - elementData.length > 0)grow(minCapacity);}
复制代码
3.如果越界,需要进行扩容
private void grow(int minCapacity) {// 保存以前数组的长度int oldCapacity = elementData.length;// 对数组进行扩容,默认增加以前长度的一半 >> 1 相当于除以2int newCapacity = oldCapacity + (oldCapacity >> 1);// 如果扩容之后的长度还是不够,则将长度设为需要的,在第一次扩容的时候使用if (newCapacity - minCapacity < 0)newCapacity = minCapacity;// 如果扩容长度大于最大值if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 数组扩容之后,将原数组中的数据,拷贝到新数组,, // 方法解释 Arrays.copyof(提供数据数组,数组长度) 返回一个数组长度的数组elementData = Arrays.copyOf(elementData, newCapacity);}
复制代码
数据校验完毕,把数据添加到数组中,将集合size增加
add方法,在某个位置插入数据
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;size++;}
复制代码
1.校验索引
// 对传入的索引进行校验
private void rangeCheckForAdd(int index) {if (index > size || index < 0)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}
复制代码
2.和另一add方法一样,校验数组长度
ensureCapacityInternal(size + 1); // Increments modCount!!
复制代码
3.因为是在某个位置插入数据,也是进行数组拷贝,但是思路是,将这个位置的数据以及以后的数据都向后面挪一位
例如:
索引 1 2 3 4 5
值 a b c d e
在2插入x
变化 a b c d e
x// 对数据进行拷贝 System.arraycopy(提供数据数组,从那个位置拷贝,拷贝数据数组,从哪个位置放,拷多长)
System.arraycopy(elementData, index, elementData, index + 1,size - index);
复制代码
4.在对应位置放入值
5.增加size
addAll(Collection c)方法,将一个集合添加进来
public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew); // Increments modCountSystem.arraycopy(a, 0, elementData, size, numNew);size += numNew;return numNew != 0;}
复制代码
- 首先将传入的集合转成数组,并且计算长度
- 返回需要的数组(校验数组长度够不够,是否扩容)
- 对数组进行拷贝,将传入的数据拷贝到真正的数组
- 增加集合的size
addAll(int i,Collection c) 在某个索引处,添加一个集合
public boolean addAll(int index, Collection<? extends E> c) {// 校验索引rangeCheckForAdd(index);//2.将集合转成数组Object[] a = c.toArray();// 3.计算长度int numNew = a.length;// 4.得到需要的数组长度(内部判断,扩容)ensureCapacityInternal(size + numNew); // Increments modCountint numMoved = size - index;// 5.拷贝数组if (numMoved > 0)System.arraycopy(elementData, index, elementData, index + numNew,numMoved);System.arraycopy(a, 0, elementData, index, numNew);size += numNew;return numNew != 0;}
复制代码
查找
根据索引位置查找数据
public E get(int index) {rangeCheck(index);return elementData(index);}
复制代码
- 判断索引,校验是否越界 private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
- 返回对应位置的数据
修改
根据索引位置,修改数据
public E set(int index, E element) {// 1. 校验下标rangeCheck(index);// 2.得到数组对应下标的数据E oldValue = elementData(index);// 3.设置数组对应下标的数据为新值elementData[index] = element;// 4.返回以前的值return oldValue;}
复制代码
删除
1.根据索引删除数据
public E remove(int index) {// 1. 校验索引rangeCheck(index);// 2.修改集合类内部的修改值,用于统计集合被修改的此时modCount++;// 3. 得到该位置的值E oldValue = elementData(index);int numMoved = size - index - 1;// 4. 对数组进行拷贝if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);// 5,对该位置赋值为nullelementData[--size] = null; // clear to let GC do its workreturn oldValue;}
复制代码
2.删除某个元素
public boolean remove(Object o) {if (o == null) {for (int index = 0; index < size; index++)// 删除null节点if (elementData[index] == null) {// 查看下面详情1fastRemove(index);return true;}} else {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); elementData[--size] = null; // clear to let GC do its work }
总结
- 集合内部的数组默认长度为10, 扩容长度为原数组长度的一半
- 集合默认构造器,使用了默认的空数组,当在第一次添加数据的时候,会根据这个判断该如何扩容,如果判断是默认的构造器,则使用默认的数组长度,如果不是则对其处理
- 添加方法,首先校验索引,再者判断现有的数组长度是否是需要的数组 长度,如果不够,则进行扩容
- 在进行数组拷贝的时候,需要对某个位置之后的数据进行拷贝,则使用System.copy(),
自己实现
只实现了默认的增删改查方法
import java.util.Arrays;
import java.util.Collection;/*** @Author: 张梦楠* @Date: 2018/12/5 19:33* 简书:https://www.jianshu.com/u/d611be10d1a6* 码云:https://gitee.com/zhangqiye* @Description: 自定义的ArrayList*/
public class QiYeArrayList<T> {/*** 存放数据的数组*/private Object[] data;/*** 空数组*/private Object[] empty = {};/*** 默认构造器的空数组,在添加的时候,判断,进行不同的构造*/private Object[] default_empty = {};/*** 集合的数据量*/private int size;private static final int INITSIZE = 10;public QiYeArrayList(){this.data = this.default_empty;}public QiYeArrayList(int init) throws Exception {if (init < 0){throw new IllegalArgumentException("初始化参数错误: "+init);}else {this.data = new Object[init];}}public Object[] toArray(){return Arrays.copyOf(data,size);}public int size(){return size;}public boolean add(Object o){// 1. 得到需要的数组checkOldArray(data,size+1);// 2. 将数据放在数组中data[size++] = o;return true;}private void checkOldArray(Object[] data, int i) {// 2.返回需要的数组长度int arrayLength = getArrayLength(data, i);//3. 对校验两个数组是否匹配checkOldArrayIsOK(arrayLength);}private void checkOldArrayIsOK(int arrayLength) {if (arrayLength - data.length > 0){// 需要的数组大于现在的数组长度,需要扩容grow(arrayLength);}}private void grow(int arrayLength) {int oldLength = data.length;int newLength = oldLength + oldLength >> 1;// 如果扩容之后还是不够,则将数组指定为需要的数组长度if (arrayLength > newLength){newLength = arrayLength;}// 将原有数据拷贝过来,生成新的数组,data = Arrays.copyOf(data, newLength);}private int getArrayLength(Object[] data,int i) {return data == default_empty ? INITSIZE:i;}public boolean add(int index, Object o){checkIndex(index);// 1.得到需要的数组checkOldArray(data,size+1);// 2. 将原数据进行挪动 0 1 2 3 4 5System.arraycopy(data,index,data,index+1,size-index);// 3.添加新数据data[index] = o;size++;return true;}private void checkIndex(int index) {if (index < 0 || index > size){throw new IndexOutOfBoundsException("下标越界了index:"+index+",size:"+size);}}public boolean addAll(Collection c){Object[] objects = c.toArray();int length = objects.length;if (length == 0){return true;}// 1.得到需要的数组checkOldArray(data,size+length);// 2.对数组进行数据的挪动System.arraycopy(objects,0,data,size,length);size += length;return true;}public Object get(int index){checkIndex(index);Object datum = data[index];return datum;}public Object set(int index,Object o){checkIndex(index);Object datum = data[index];data[index] = o;return datum;}public Object remove(int index){checkIndex(index);Object datum = data[index];int moveSize = size - index - 1;if (moveSize > 0){// 1.对数据进行拷贝System.arraycopy(data,index+1,data,index,size-index-1);}data[--size] = null;return datum;}public boolean remove(Object o){if (null == o){for (int i=0;i<size;i++){if (data[i] == null){removeIndex(i);return true;}}}else {for (int i=0;i<size;i++){if (data[i] .equals(o)){removeIndex(i);return true;}}}return false;}private void removeIndex(int index) {checkIndex(index);// 1. 对数据进行拷贝int moveSize = size - index - 1;if (moveSize > 0){// 1.对数据进行拷贝System.arraycopy(data,index+1,data,index,size-index-1);}data[--size] = null;}}
复制代码