ArrayList是我们经常见到的一个集合数据类型,所以我们来看看ArrayList是怎么实现的?
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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;
}
}
/**
* Trims the capacity of this ArrayList instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an ArrayList instance.
*/
这是三个构造函数,第一个构造函数是一个参数传入,这个参数的名字已经很详细了,叫做初始容量(initialCapacity),当初始化容量大于0的时候我们就开辟InitialCapacity个内存空间出来,假如说赋值为0,我们就动用EMPTY_ELEMENTDATA(空元素数据),那么这个EMPTY_ELEMENTDATA又是什么呢?我们继续往上翻,因为这些常量的定义往往都放在开头。
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {
};
看到这其实已经很明白了,注释的意思也说得很清楚,那就是新建一个空的实例,啥也没有。
else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
其他的呢,也就说的很明白了,你有0个capacity也好,10000个capacity也好,但是你传给我-1个什么意思?你在为难这个数据结构是吧?直接抛出异常,非法的初始值。
众所周知,没有参数的我们称其为无参构造(NoArgsConstructor),所以我们仔细看一下ArrayList也是有无参构造的,我们可以发现,ArrayList在实现无参构造的时候会初始化赋个值DEFAULTCAPACITY_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.
* 默认尺寸为空的实例,我们与EMPTY_ELEMENTDATA区分去了解当第一个元素填入膨胀了多少
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
};
那么我们还注意到第三个构造函数就是collection类型的他也能传,首先将这个传入的c转换成了Collection类型的array,接着用反射判断是不是与Object[]这个类型一致,一致就对这里面的array进行复制操作,不一致就传入一个空的默认数据集合。
接下来就是contain和size了,判断这个arraylist为不为空和这个arraylist的大小的函数
那么有的人会问,这个size和capacity两个参数之间到底有什么区别,其实举个例子来讲,一个杯子500ML,我分5个刻度线,当水注进去的时候我就加个刻度线的标记,这样就可以倒满这500ML(capacity),那么,我倒进去的这个水位就是size,这个集合操作也是一样,我开辟这个空间就是capacity,当我每插入一个数进去size更新进行标记,当我删除的时候也进行size更新,这里的size就相当于个标记,事实上,Java中弱化了对指针的概念,但是size可以理解成指针指向某个空间,不断地在移动。
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
public int size() {
return size;
}
/**
* Returns true if this list contains no elements.
*
* @return true if this list contains no elements
*/
public boolean isEmpty() {
return size == 0;
}
那么,问题来了,当我们新建一个ArrayList的时候,我们习惯于传入一个capacity,也就是说,这个List初始化就是一个0的list,我们插进去不就异常了吗?为什么仍然能够正常执行?
首先看一下Add是如何实现的。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return true (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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++;
}
这里有俩Add,从传入参数大致可以猜出一个是尾部插入,size+1,一个是指定index插入,index后的元素往后挪一位,插入空缺的地方
1 2 3 4 5 6 7 8 9
无index参数 1 2 3 4 5 6 7 8 9 10
index为 2
element为10
1 2 坑 3 4 5 6 7 8 9
1 2 10 3 4 5 6 7 8 9
那么我回答了这么多,的确还没解决0capacity这个问题。
仔细看下ensureCapacityInternal这个实现。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
假如我们为空的数据集,啥也没有,我们就给他开辟出来,这个minCapacity就从我默认设置的这个capacity或者你设置的minCapacity中选取个较大值,也就是说,你只要操作Add这个方法,甭管你这capacity初始化的时候传没传给我,你这最少我给你开辟10个内存空间。
注意是在add的情况下且数组为空数组开辟的这10个内存空间,并非一些资料上讲的初始化就开辟。
我们继续追踪一下代码 ensureExplicitCapacity(minCapacity)这个方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
当我们传入mincapacity(10或者你传的参数)小于数据集合的长度,这时,我们的常识就是溢出,满了,塞不下了,当时Java给出了个方案,那就是grow,增长。、
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
我们看下增长的算法
旧容量就是这个数据集合长度,新容量=旧容量+旧容量>>1(右移一位)也就是扩容1.5倍,
假如计算的数字比我传入的mincapacity小,我就用我传的capacity,
否则就用这1.5倍扩容的新值,再将数据集合赋给扩容后的数组中
说完了增加操作,俗话说应用开发业务逻辑的主要四个方面,增删改查,作为一个CRUD工程师,我们要有良好的职业素养,下一个讨论下删除操作。
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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;
}
首先第一个我们要面对的就是rangeCheck这个函数,顾名思义,就是核查范围。假如你传入的参数大于这个size,当然是越界,所以我们要抛出异常,越界异常,终止程序。
/**
* Checks if the given index is in range. If not, throws an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an ArrayIndexOutOfBoundsException if index is negative.
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
然后我们就是查找到这个元素所在的位置,当该元素处在index位置时,index+1往index位置移动,整体向前赋值一位,最后那个值毫无意义,我们做size指针前移,指向的空间赋值为空释放掉。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
我们还看到一个remove传入对象的方法,就是简单的遍历一遍,假如里面有这个对象,就删除,没有就不删,重要的点在于这个fastRemove的实现,既然我们实现了remove,那么这个fastRemove又是怎么来的呢?
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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
}
fastRemove和remove的差别在于fastRemove没有验证index这个选项,而且它是个私有的方法,外界不可调用,所以我们会比较陌生,正因为没有验证,它的速度和执行效率可能会比remove快上一丢丢。
//有无下一个元素,在于焦点是否等于size
public boolean hasNext() {
return cursor != size;
}
//next函数是将cursor这个指针往后移
@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();
}
}
迭代器(iterator)有时又称游标(cursor)是程序设计的软件设计模式,可在容器(container,例如链表或阵列)上遍访的接口,设计人员无需关心容器的内容。
arrayList中的迭代器lastRet初始化为-1,这就说明为什么上来直接remove会报错,因为这个lastRet的值就是不合法的啊!
当我第一次执行next的时候,我下标就开始变动,lastReturn就会被cursor赋值,返回当前所指向的元素,反复执行,我们就可以做一个清空当前list的操作,也就是说,当hasNext为true的时候,表示一直有下一个元素,指向next,执行remove。
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
Iterator itr = arrayList.iterator();
while (itr.hasNext()) {
itr.next();
itr.remove();
}
}
//当然我们也可以这么操作
ArrayList<Integer> arrayList = new ArrayList<Integer>();
for (int i = 3; i < 20; i++) {
arrayList.add(i);
}
for (int i = 0; i < arrayList.size(); i++) {
arrayList.remove(i);
}
//所以这两个基本一致
那么迭代器是如何运作删除的呢?
我们debug调试一下就可以发现,这个lastRet一直在-1和0之间徘徊,也就是说是从第一个数开始,不断前移,类似与pop这个功能,运行就像枪打出头鸟那种感觉。