シリーズ記事ディレクトリ
線形テーブルの動的配列 (ArrayList) の自己実装 - Programmer Sought
記事ディレクトリ
- 序文
- 1. ArrayListの 3 つの構築方法の分析
- 2. ArrayList はどのように拡張されますか? (ソースコード解析)
- 3. ArrayList の共通メソッド
- ArrayList の 4、3 種類のトラバーサル
- 5. ArrayList の欠陥
序文
1. ArrayListの 3 つの構築方法の分析
方法 | 説明 |
配列リスト
()
|
引数なしのコンストラクタ
|
ArrayList
(Collection<? extends E> c)
|
他の
コレクションから
ArrayListを
構築する
|
ArrayList
(int initialCapacity)
|
シーケンステーブルの初期容量を指定
|
public static void main(String[] args) {
ArrayList<Integer> arrayList1 = new ArrayList<>();
ArrayList<Integer> arrayList2 = new ArrayList<>(12);
arrayList1.add(1);
ArrayList<Integer> arrayList3 = new ArrayList<>(arrayList1);
System.out.println(arrayList3);
}
注: ArrayList (Collection<? extends E> c) コンストラクターを使用する場合、これはワイルドカードの上限であるため、渡される型は E または E のサブクラスである必要があることに注意してください。
2. ArrayList はどのように拡張されますか? (ソースコード解析)
まず、パラメーターなしのコンストラクターを見てください。
DEFAULT_CAPACITY = 10デフォルトの容量は 10 です; 一時的な Object[] elementData, シーケンス テーブルの背後にある配列と同様; private int size , size は有効な要素の数で、この時点では 0 であり、 DEFAULTCAPACITY_EMPTY_ELEMENTDATAの長さが0、この構築メソッドは配列メモリを割り当てません。
ここで質問があります。なぜ DEFAULTCAPACITY_EMPTY_ELEMENTDATA の長さが 0 であり、デフォルトの容量が 10 であるのが矛盾しているのでしょうか? 今回はサイズがないので、最初にデータを追加するときはどのように入れましたか?
次に、引き続きクリックして add メソッドに入ります。
ストレージ要素を記述する場合、デフォルトでは最後の要素の後に配置されます。しかし、ensureCapacityInternal(size + 1) メソッドがここで使用され、クリックして入力します。
このとき size は 0. size+1 を渡すと minCapacity が 1 となり、あと 2 つのメソッドがある. calculateCapacity(elementData, minCapacity) メソッドの戻り値を ensureExplicitCapacity() のパラメータとして使用する必要があります.方法です。
次に、クリックして calculateCapacity(elementData, minCapacity) メソッドに入ります. ここでは、名前が示すように、容量を計算します. このとき、minCapacity は 1 です. これは、この時点でパラメーターなしの構築メソッドが呼び出されるためです. = この時点で DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
このときIf文が成立すれば最大値が計算されますが、このときminCapacityは1、DEFAULT_CAPACITY=10なので、最初にaddを呼び出すと10が返ってきます。このとき、戻り値の 10 が ensureExplicitCapacity() のパラメータとして使用されるので、クリックしてこのメソッドの内部に入ります。
このとき、minCapacity は 10 であり、elementData 配列の長さは 0 です。10-0 > 0 の場合、grow 関数が呼び出されて 10 が渡されます。もう一度 [grow] をクリックして、この関数に入ります。
このとき、oldCapacity と newCapacity は 0 と計算されます。これは、0 - 10 < 0 であるため、newCapacity = minCapacity = 10 です。次に、次の if ステートメントを見てください。
このとき、MAX_ARRAY_SIZE は int から 8 を引いた最大値です。この時点で、if ステートメントは入ってくることができず、実行を続けます。この時点で、配列には実際にメモリが割り当てられます。
elementData = Arrays.copyOf(elementData, newCapacity);
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal(size + 1) と同等 このメソッドは終了し、配列の容量は 10 になり、要素を入れることができます。ということで、パラメータなしでコンストラクタを呼ぶことが前提と言えますが、初回追加時のデフォルト容量は10です。
では、どのようにスケーリングするのでしょうか? 11番目の要素を置くときのように?
次に、grow 関数を見ていきます。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//按照1.5倍方式扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小
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);
}
int newCapacity = oldCapacity + (oldCapacity >> 1); 実はこのコードは伸びており、1.5倍の伸びです。
実際、パラメーターを使用した構築方法、初期値がどのくらい与えられているか、初期容量は何ですか:
ArrayList (Collection<? extends E> c) の構築メソッドは、実際には配列のコピーです。
まとめ:
3. ArrayList の共通メソッド
方法 | 説明 |
ブール
加算
(E e)
|
テールプラグ
e
|
void
add
(int インデックス、E 要素)
|
インデックス位置に
e
を
挿入
|
boolean
addAll
(Collection<? extends E> c)
|
cの
末尾挿入
要素
|
E
remove
(int インデックス)
|
インデックス位置の
要素を削除する
|
boolean
remove
(オブジェクト o)
|
最初に見つかったoを削除する
|
E
get
(int インデックス)
|
添え字の
インデックス
位置要素を取得する
|
E
セット
(int インデックス、E 要素)
|
添字
インデックス
位置要素を
要素に設定
|
ボイド
クリア
()
|
空の
|
boolean
contains
(オブジェクト o)
|
o
が線形テーブルにあるかどうかを判別する
|
int
indexOf
(オブジェクト o)
|
最初の
oが配置されている
添字を返します
|
int
lastIndexOf
(オブジェクト o)
|
最後の
o
の添字を返します
|
List<E>
subList
(int fromIndex, int toIndex)
|
リストのインターセプト部分
|
ここで、メソッドList<E> subList (int fromIndex, int toIndex)に注意する必要があります。
public static void main(String[] args) {
ArrayList<Integer> arrayList1 = new ArrayList<>();
arrayList1.add(1);
arrayList1.add(2);
arrayList1.add(3);
arrayList1.add(4);
arrayList1.add(5);
List<Integer> list = arrayList1.subList(1,3);
System.out.println(list);//2,3
list.set(0,99);
System.out.println(arrayList1);// 1,99,3,4,5
System.out.println(list);//99,3
}
これにより、リスト内のデータが変更された場合、arraylist 内のデータも変更されることがわかります。メモリ マップを見て、何が起こっているかを確認してください。
ArrayList の 4、3 種類のトラバーサル
ArrayListは次の 3 つの方法でトラバースできます: for loop + subscript、foreach、iterator を使用
public static void main(String[] args) {
ArrayList<Integer> arrayList1 = new ArrayList<>();
arrayList1.add(1);
arrayList1.add(2);
arrayList1.add(3);
arrayList1.add(4);
arrayList1.add(5);
int size = arrayList1.size();
for (int i = 0; i < size; i++) {
System.out.print(arrayList1.get(i)+" ");
}
System.out.println();
for (int x : arrayList1) {
System.out.print(x+" ");
}
System.out.println();
Iterator<Integer> it = arrayList1.iterator();
while (it.hasNext()) {
System.out.print(it.next()+" ");
}
}