1.データ構造
ArrayListのデータ構造は配列です。上の図に示すように、図は1から数えて10の長さの配列を示し、インデックスは0から数えて配列の添え字を示し、elementDataは配列要素を示します。 。さらに、ソースコードには3つの基本的な概念があります。
ソースコード
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//该值用于定义数组第一次扩容时的大小
private static final int DEFAULT_CAPACITY = 10;
transient Object[] elementData;
//该值用于定义当前数组的大小
private int size;
}
ソースコード分析
- DEFAULT_CAPACITYは、最初に展開されたときの配列のサイズを表します。この値は、インタビュー中によく言及され、初期化中にも言及されます。
- sizeは現在の配列のサイズを表し、タイプはintであり、volatileは変更されないため、スレッドセーフではありません。
- modCountは、現在の配列のリビジョン数を表します。配列構造が変更されると、+ 1になります。
2.
ArrayListの初期化には、パラメーターなしの直接初期化、指定されたサイズでの初期化、および指定された初期データでの初期化の3つの初期化メソッドがあります。ソースコードは次のとおりです。
ソースコード
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
};
//无参数直接初始化,数组大小为空
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//指定大小初始化
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(Collection<? extends E> c) {
//该值用于保存数组的容器,默认为null
elementData = c.toArray();
//如果给定的集合c有值
if ((size = elementData.length) != 0) {
//如果集合元素类型不是Object类型,则转成Object类型
if (elementData.getClass() != Object[].class) {
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
} else {
//给定的集合c无值
this.elementData = EMPTY_ELEMENTDATA;
}
}
}
ソースコード分析
- ArrayListパラメーターなしコンストラクターが初期化されると、デフォルトのサイズは空の配列になります。これは通常の10ではなく、要素が最初に追加されたときに展開される配列値です。
- データの初期化を指定すると、コメントに「c.toArrayが(誤って)Object []を返さない可能性があります(6260652を参照)」というコメントが含まれていることがわかります。これはソースコードのバグです。つまり、特定のコレクション内の要素がObject型でない場合、Object型に変換されます。通常の状況では、このバグはトリガーされません。次のシナリオでのみトリガーされます。ArrayListが初期化された後(ArrayList要素がObjectタイプではない)、toArrayメソッドが再度呼び出されてObject配列が取得され、Object配列が割り当てられたときにトリガーされます。バグ、特定のケースコード、および実行結果のスクリーンショットは次のとおりです。
public class App {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello world");
Object[] objArray = list.toArray();
//打印的值为String[]
System.out.println(objArray.getClass().getSimpleName());
//抛出ArrayStoreException异常
objArray[0] = new Object();
}
}
3.追加と拡張
追加とは、主に2つのステップに分かれて要素を配列に追加することです。1つは拡張が必要かどうかを判断し、必要に応じて拡張操作を実行すること、もう1つは値を直接割り当てることです。具体的なソースコードは次のとおりです。
ソースコード
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public boolean add(E e) {
//确保数组大小是否足够,不够执行扩容,size为当前数组的大小
ensureCapacityInternal(size + 1);
//直接赋值,这里是线程不安全的
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果初始化数组大小时,有给定初始值,以给定的大小为准,不走if逻辑
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确保容积足够
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//记录数组被修改
modCount++;
//如果期望的最小容量大于目前数组的长度,即扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容,并把现有数据拷贝到新的数组里
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//oldCapacity >> 1是把oldCapacity除以2
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的值小于期望值,扩容后的值就等于期望值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果扩容后的值大于jvm所能分配的数组的最大值,那么就用Integer的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//通过复制进行扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
ソースコード分析
展開が完了したら、elementData [size ++] = eを使用して要素を配列に直接追加します。ロック制御がないのはこの単純な割り当てによるため、ここでの操作はスレッドセーフではありません。
総括する
- 拡張のルールは2倍ではなく、元の容量に半分の容量を加えたものです。端的に言えば、拡張された容量は元の容量の1.5倍です。
- ArrayList内の配列の最大値はInteger.MAX_VALUEです。この値を超えると、JVMは配列にメモリスペースを割り当てません。
- 追加されたとき、値は厳密にチェックされていなかったため、ArrayListはnull値を許可します。
4.イテレーターイテレーター
を実装するには、java.util.Iteratorクラスを実装するだけで済みます。イテレーターには3つの重要なパラメーターとメソッドがあります。ソースコードは次のとおりです。
ソースコード
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//迭代过程中,下一个元素的位置,默认从0开始
int cursor;
/**
* 新增场景表示上一次迭代过程中,索引的位置
* 删除场景为-1
*/
int lastRet = -1;
/**
* expectedModCount表示迭代过程中,期望的版本号
* modCount表示数组实际的版本号
*/
int expectedModCount = modCount;
public boolean hasNext() {
//cursor表示下一个元素的位置,size表示实际大小,如果两者相等,说明没有可以迭代的元素,如果不等,说明还有元素需要迭代
return cursor != size;
}
public E next() {
//迭代过程中,判断版本号是否被修改,若被修改,抛出ConcurrentModificationException异常
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];
}
//版本号比较
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public void remove() {
//如果上一次操作时,数组的位置已经小于0,说明数组已经被删除完
if (lastRet < 0)
throw new IllegalStateException();
//迭代过程中,判断版本号是否被修改,若被修改,抛出ConcurrentModificationException异常
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
//-1表示元素已经被删除,这里也有防止重复删除的作用
lastRet = -1;
//删除元素时modCount的值已经发生变化,在此赋值给expectedModCount,下次迭代时,两者的值便是一致的
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
ソースコード分析
- ソースコードからわかるように、次の方法は2つのことを実行します。1つは反復を続行できるかどうかを確認すること、もう1つは反復の値を見つけて次の反復(カーソル+1)の準備をすることです。
- lastRet = -1の目的は、重複した削除を防ぐことです。
- 要素が正常に削除されると、配列の現在のmodCountが変更され、expectedModCountがここで再割り当てされ、2つの値は次の反復で同じになります。