質問をする
- 何に基づいて?アレイ?リンクリスト?キュー?
- なぜあなたは
add
要素を保つことができますか?
分析
実現する方法
定義された変数:
配列は維持されます:
transient Object[] elementData; // non-private to simplify nested class access
private int size;
ArrayList
すべてが内部的add、remove、set、get
にelementData
この配列で動作するため、ArrayList
実装は配列に基づいています。
2つの長さ:size
====>現在のリストのelementData.length
長さ====>配列の長さ
配列の長さ≥リストの長さ
デフォルトの長さと2つのデフォルトの配列:
/**
* 默认的数组长度,当我们直接创建一个ArrayList对象时,容量为10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 直接创建无参ArrayList时,内部指向该数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 创建带初始长度的ArrayList,或者传入另一个列表为参数创建对象时,如果长度为0或者传入列表长度为0,内部指向该数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
工法
ArrayList
3つの構築方法が定義されています。それらは非パラメータ構築です。1つのint
タイプのパラメータ構築方法が渡され、リストがパラメータ構築方法として渡されます。
- 初期の長さを渡すこの方法は、無駄なメモリスペースを使いすぎないように、リストの長さがわかっている場合に一般的に使用されます。
public ArrayList(int initialCapacity) {
//如果长度大于0,则创建该长度的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//长度为0,指向默认数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
- パラメータの構築は
elementData
なく、デフォルトの配列、長さ0 を指します - リストを渡します。
Arrays.copyOf()
新しい配列に渡されたリストをコピーすることにより、方法、およびそれからelementData
アドレスを指します
操作方法
各メソッドの内部実装は、アレイに対する操作です。
get
配列の対応する添え字の値を直接取得します
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
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;
}
add
添え字は現在のものsize+1
であり、追加する必要のある値は、配列内の添え字に対応する値に設定されます。アレイの長さは固定されているため、最も重要な拡張および成長戦略であるensureCapacityInternal(size + 1)
方法が含まれます。
public boolean add(E e) {
//扩容算法,传入当前长度+1
ensureCapacityInternal(size + 1); // Increments modCount!!
//扩容完成后赋值
elementData[size++] = e;
return true;
}
addAll
メソッド、最初にリストパラメータを配列に変換し、次にSystem.arraycopyメソッドを使用して配列をelementData
それにコピーします。これには拡張と拡張の戦略も含まれますが、入力パラメータは異なります。
public boolean addAll(Collection<? extends E> c) {
//先转为数组
Object[] a = c.toArray();
int numNew = a.length;
//扩容算法,传入当前长度+需要add的元素的数量
ensureCapacityInternal(size + numNew); // Increments modCount
//拷贝数组
System.arraycopy(a, 0, elementData, size, numNew);
//列表长度修改
size += numNew;
return numNew != 0;
}
remove
同じコピー操作がアレイで実行されます。これは、アレイ内で削除する必要のある要素の1ビット後に各要素を前方に移動して、元の値を上書きするのと似ています。
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) 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;
}
拡張
フロントadd
とaddAll
メソッド、ensureCapacityInternal1
拡張アルゴリズム
が関連するコード拡張を調べるためのメソッドがあります。
//①传入列表长度值
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//②如果计算后需要的长度大于当前数组的长度,执行扩容操作
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//③对数组进行扩容
private void grow(int minCapacity) {
// overflow-conscious code
//获取当前长度
int oldCapacity = elementData.length;
//计算新长度为旧长度 * 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新长度小于需要的长度,使用传入的长度,适用于第一次创建后添加元素的扩容和addAll方法元素很多超过原有1.5倍的情况下
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果计算后长度大于最大列表长度
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//通过copyOf方法将原有数据拷贝到一个新的数组对象中,再赋值给elementData,至此,扩容完成。
elementData = Arrays.copyOf(elementData, newCapacity);
}
分析:
- まず、addまたはaddAll後のリストの長さを表す値を渡します。
- 追加するたびに配列を拡張することはできません。そうしないと、コストが高すぎるため、配列を拡張すると、毎回長さが1つ増えるだけではありません。
- 現在空の配列である場合は、デフォルト値と渡された値の最大値を取ります。初めて直接長さ10の配列を作成するには、それ以外の場合は要素を追加して1回拡張しますが、これはコストがかかります。
- 拡張が必要かどうかを計算します
- 拡張操作、通常の状況では、アレイ容量は1.5倍に拡張されますが、2つの特殊なケースがあります。
- 初めて要素を追加し、デフォルトの長さ10に直接拡張します
- addAll要素がたくさんあり、全長が元の長さの1.5倍を超えている場合は、この長さに直接拡張します
- array copyメソッドを使用して、すべての元のデータを展開された新しい配列にコピーし、最後に配列をelementDataに割り当てます。
- 拡張が完了しました
総括する
- ArrayListは配列に基づいて実装され、配列のデフォルトの長さは10です。すべての操作は、維持されている配列に対する操作です。
- addメソッドとaddAllメソッドは、配列拡張をトリガーする場合があります
- 長さが十分でない場合、拡張長さは通常、既存の長さの1.5倍になります。
- 拡張、削除などはすべてアレイをコピーすることで実現されるため、リストが長すぎないようにしてください。長すぎると、各コピーのパフォーマンスが過度に消費されます。