ArrayList中几个重要的属性:
// 序列化ID private static final long serialVersionUID = 8683452581122892189L; // 默认初始化容量 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 // 当前arraylist集合的大小,也就是elementData数组中数据元素的个数 private int size;
ArrayList的构造方法,ArrayList的构造方法支持三种形式:
/** * 形式一:无参构造方法 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
我们可以看到,在构造方法中直接将 elementData 指向 DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,这个时候该ArrayList的size为初始值0。
/** * 形式二:携带一个int类型的参数,指定arraylist的初始容量 */ 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); } }
参数initialCapacity为我们所指定的arraylist初始容量,可以看出,方法中对initialCapacity的值进行了一系列判断,当我们所指定的初始容量小于0时无意义,直接抛出非法参数异常。当initialCapacity大于0时,会直接创建一个Object类型的数组,数组的初始大小就是initialCapacity的值。当initialCapacity等于0时,会直接将elementData 指向EMPTY_ELEMENTDATA空数组。
/** * 形式三:携带一个Collection类型的参数 */ 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; } }
代码中首先将Collection参数通过toArray方法转换成数组,并赋值给elementData,然后对arraylist中的size进行赋值并判断size是否等于0。当size为0时,直接将elementData 指向EMPTY_ELEMENTDATA空数组。当size不为0时执行copyOf方法。
ArrayList的构造方法到这里就结束了,接着我们分析下add方法。add方法根据参数个数的不同有两种,如下所示:
/** * 方式一:直接添加数据元素到arraylist的尾部 */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } /** * 方式二:插入数据元素到特定的角标位置 */ public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
我们首先对方式一进行分析下。方法中首先调用到ensureCapacityInternal方法,将size+1作为参数传入。我们知道,size用来表示当前arraylist的大小,也就是elementData数组中元素的个数,size+1就是确保数据元素添加成功的最小容量。ensureCapacityInternal方法是做什么的?让我们跟进去看下:
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
可以看到方法中首先将elementData 和DEFAULTCAPACITY_EMPTY_ELEMENTDATA进行对比,如果elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则执行 if 语句体的操作,否则直接调用ensureExplicitCapacity方法,将最小容量minCapacity作为参数传入。什么情况下elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA呢?不知道大家还有没有印象,当我们通过ArrayList的无参构造方法创建ArrayList对象时,在构造方法中会直接将elementData 指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA。也就是说当我们通过ArrayList的无参构造方法创建ArrayList对象后,再调用add方法的时候会执行if 语句体操作,将minCapacity 重新赋值为DEFAULT_CAPACITY和minCapacity中的最大值。DEFAULT_CAPACITY我们之前也有讲过,为默认初始化容量。当我们通过ArrayList的无参构造函数创建ArrayList对象后,首次调用add方法时,这个时候ensureCapacityInternal方法中传入的minCapacity为1,if 语句体中minCapacity重新赋值为10。我们接着看下ensureExplicitCapacity方法:
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
可以看到,方法中首先将变量modCount自增1,modCount是做什么用的呢?其实modCount是用来标记当前arraylist集合操作变化的次数,在fail-fast机制中会有用到这个变量,关于fail-fast机制我们稍后会讲下。接着判断minCapacity - elementData.length 是否大于0,当minCapacity - elementData.length大于0的时候说明当前elementData数组大小不够用,需要扩容,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) { // 1.首先获取到elementData数组的长度,作为原容量 int oldCapacity = elementData.length; // 2.新容量 = 原容量 + 原容量/2; 1.5倍扩容 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) // 3.若1.5倍扩容后还不够,则将最小容量作为新容量 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) // 4.限制最大容量 newCapacity = hugeCapacity(minCapacity); // 5.进行原有数据元素copy处理 elementData = Arrays.copyOf(elementData, newCapacity); }
关于grow方法扩容操作代码中已经注释很清楚了。接着我们回到add方法接着往下看。下面的代码就比较简单了,就是一个简单的数据元素赋值操作,注意size自增1,最后return true,表示添加元素成功。
上面我们分析了add方法的第一种方式,直接添加数据元素到arraylist的尾部。简单讲下,首先会处理elementData数组是否需要扩容操作,接着对新添加的数据元素进行赋值操作,size自增1,最后return true表示数据添加成功。add方法的第二种方式和第一种方式类似,插入数据元素到特定的角标位置,方法中首先会对下角标index 进行越界判断,然后会对elementData数组是否需要扩容操作进行处理,接着调用 System.arraycopy方法将指定角标后的元素后移一位,最后对指定角标位置进行赋值操作并将size自增1。
add方法到这里就结束了,下面我们接着看remove方法,remove方法根据参数的不同也同样分为两种,方法如下:
/* * 方式一:根据角标进行remove操作 */ public E remove(int index) { // 1. 对角标越界进行判断 if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); // 2.modCount自增1 modCount++; // 3.获取到指定下角标位置的数据 E oldValue = (E) elementData[index]; // 4.计算需要移动的元素个数 int numMoved = size - index - 1; if (numMoved > 0) // 5. 指定角标位置后的元素前移一位 System.arraycopy(elementData, index+1, elementData, index, numMoved); // 6.将size自减1,并将数组末尾置为null,便于垃圾回收 elementData[--size] = null; // clear to let GC do its work // 7.最后将所要删除的数据元素return掉 return oldValue; } /* * 方式二:根据数据元素进行remove操作 */ 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操作,代码中的注释已经很清楚了,下面我们看下方式二,根据数据元素进行remove操作。方法中首先对当前remove的数据元素进行null判断。无论当前remove的数据元素是否为null,都需要一个for循环进行遍历操作,注意if 条件块的代码在数据元素为null和不为null两种情况下是不同的。如果elementData数组中某一角标处的元素等于当前remove的数据元素,会调用fastRemove方法进行具体remove操作,最后return true表示remove操作成功。否则return false表示remove操作失败。fastRemove方法的代码如下:
/* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) { // 1.modCount的值自增1 modCount++; // 2.计算需要移动的元素个数 int numMoved = size - index - 1; if (numMoved > 0) // 3. 指定角标位置后的元素前移一位 System.arraycopy(elementData, index+1, elementData, index, numMoved); // 4.将size自减1,并将数组末尾置为null,便于垃圾回收 elementData[--size] = null; // clear to let GC do its work }
接着我们看下set方法:
public E set(int index, E element) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); E oldValue = (E) elementData[index]; elementData[index] = element; return oldValue; }
相信set方法大家一看就明白了。我再说下,set方法需要接受两个参数,第一个参数index为下角标,第二个参数element为数据元素,表示将下角标index处的数据元素赋值为element。在方法中,首先对index下角标进行越界判断,然后获取到角标index处的原数据元素oldValue,接着将角标index处的数据元素赋值为element,最后将原有数据元素oldValue return掉。
接着就是get方法了:
public E get(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); return (E) elementData[index]; }
get方法中的操作更是简单,首先同样是进行越界判断,接着直接将下角标index处的数据元素return掉。
接着看下clear方法:
/** * Removes all of the elements from this list. The list will * be empty after this call returns. */ public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
同样clear方法中的操作也很简单,首先将modCount的值自增1,然后进行一个遍历操作,将elementData数组角标元素置为null,最后将size的值置为0。
come on,我们接着看下contains方法:
public boolean contains(Object o) { return indexOf(o) >= 0; }
what? ? ?,在方法内部直接将indexOf(o) >= 0作为结果return出去了,我们大家都知道,indexOf方法是将一个数据元素传入,返回该数据元素对应的下角标,如果数据元素在当前arraylist中不存在,则返回-1。也就是说当数据元素在当前arraylist中存在时,indexOf(o) >= 0成立,结果为true,否则为false。我们接着跟进去indexOf方法看下:
/** * Returns the index of the first occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the lowest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. */ public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }
可以看到indexOf方法中对数据元素进行了null判断,两种情况下都进行了遍历操作,只不过两种情况下的if 条件语句不同。当满足条件时,直接将对应的下角标return掉,否则return -1,表示当前arraylist中不存在该元素。
接下来我们一起来看下fail-fast机制。什么是fail-fast机制呢?其实fail-fast机制是集合中的一种错误检测机制,我们在操作集合中经常会遇到 java.util.ConcurrentModificationException异常,产生该异常的原因就是fail-fast机制。我们先来看个例子先:
List mDatas = new ArrayList<String>(); mDatas.add("hello"); mDatas.add("android"); mDatas.add("java"); Iterator<String> mIterator = mDatas.iterator(); while (mIterator.hasNext()){ if ("android".equals(mIterator.next())){ mDatas.add("php"); } }
上述代码运行后会出现什么结果呢?是不是在mDatas集合中存在四个元素,分别为:“hello” “android” “java” “php” 了呢?我们运行看下:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:903) at java.util.ArrayList$Itr.next(ArrayList.java:853) at com.example.halobear.eventbustest.ArrayListTest.main(ArrayListTest.java:21) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)
可以看到程序直接crash掉了,抛出的异常正是我们刚才提到的ConcurrentModificationException 并发修改异常,正是由于fail-fast机制导致的。在什么情况下会出现该异常呢?我先简单说下,一般情况下有两种情况,一种情况发生在多线程操作,当a线程正在通过迭代器操作集合mDatas时,同时b线程对mDatas进行添加或者删除元素,会触发fail-fast机制,抛出该异常。另一种情况是在迭代集合mDatas的过程中对mDatas集合进行元素添加或者删除操作时,会触发fail-fast机制,抛出该异常。归根结底就是当我们进行集合数据迭代时,有其他操作修改了当前arraylist的modCount的值,导致modCount != expectedModCount,会抛出该异常。下面我们从源码角度看下:
我们通过arraylist的iterator()方法获取到迭代器,点进去:
/** * Returns an iterator over the elements in this list in proper sequence. * * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>. * * @return an iterator over the elements in this list in proper sequence */ public Iterator<E> iterator() { return new Itr(); }
可以看到方法中直接创建了一个Itr实例对象,并return掉。我们跟进去:
private class Itr implements Iterator<E> { protected int limit = ArrayList.this.size; int cursor; // next 元素角标 int lastRet = -1; // last 元素角标 int expectedModCount = modCount; //重点:将modCount的值赋值给expectedModCount(期待修改数量) // 判断是否有下一个元素 public boolean hasNext() { return cursor < limit; } @SuppressWarnings("unchecked") public E next() { // 获取到next元素 // 1. 当modCount != expectedModCount时,会抛出ConcurrentModificationException异常 if (modCount != expectedModCount) throw new ConcurrentModificationException(); int i = cursor; // 2.角标越界检测 if (i >= limit) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); // 3.cursor的值自增1 cursor = i + 1; // 4.对lastRet进行赋值并将当前角标对应的数据元素return掉 return (E) elementData[lastRet = i]; } public void remove() { // 1.角标越界检测 if (lastRet < 0) throw new IllegalStateException(); // 2. 当modCount != expectedModCount时,会抛出ConcurrentModificationException异常 if (modCount != expectedModCount) throw new ConcurrentModificationException(); try { // 3.调用arraylist的remove方法,进行remove操作 ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; // 4.重点:在3 处调用arraylist的remove方法,进行remove操作时,会将modCount的值自增1,在这里重新对expectedModCount进行赋值操作,否则会导致modCount != expectedModCount,抛出ConcurrentModificationException异常。 expectedModCount = modCount; limit--; } 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 traffic cursor = i; lastRet = i - 1; if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
上述就是Itr类的所有源代码,有没有很少。我们可以看到Itr实现了Iterator,主要包含三个常用的方法,分别是:hasNext()、next()、remove()。
ArrayList源码解析
最新推荐文章于 2021-11-18 10:45:25 发布