ArrayList 詳細説明と展開ソースコード解析

シリーズ記事ディレクトリ

線形テーブルの動的配列 (ArrayList) の自己実装 - Programmer Sought


記事ディレクトリ


序文

In the collection framework, ArrayList is a common class that implements the List interface. 具体的なフレーム ダイアグラムは次のとおりです。
1. ArrayListは RandomAccessインターフェイスを 実装しており、 ArrayListがランダム アクセスをサポートしていることを示しています。
2. ArrayListは Cloneableインターフェイスを 実装し、 ArrayListを複製できることを示します。
3. ArrayListは Serializableインターフェイスを 実装し、 ArrayListがシリアル化をサポートしていることを示します。
4. Vector とは異なり ArrayListはスレッドセーフではなく、シングル スレッドで使用できます。マルチスレッドでは、 Vectorまたは CopyOnWriteArrayListを選択できます。
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) の構築メソッドは、実際には配列のコピーです。

 まとめ:

1.拡張を準備するためにgrowを呼び出す場合、拡張が本当に必要かどうかを検出します。
2.必要なストレージ容量のサイズを見積もります: 最初の見積もりは、サイズに応じて容量を拡張することであり、ユーザーが必要とするサイズが見積もりサイズを 1.5 倍超える場合、容量はサイズに応じて拡張されます。実際の拡張の前に、容量が大きくなりすぎないように、容量を正常に拡張できるかどうかを確認してください。拡張は失敗します。

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 + subscriptforeachiterator を使用

 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()+" ");
        }
}

5. ArrayList の欠陥

ArrayList の最下層は要素を格納するために配列を使用します. その最下層は連続した空間であるため、 ArrayListの任意の位置 で要素を挿入または削除する場合 、一連の要素全体を前後に移動する必要があり、時間計算量はO (n)の場合、効率は比較的低いため、ArrayListは任意の位置で多くの挿入と削除があるシナリオには適していませんまた、拡張後は無駄なスペースが発生する可能性がありますArrayList は、添え字の位置が与えられた要素を見つけるのに適しており、時間の計算量は O(1) に達する可能性があります。したがって、リンクされたリスト構造であるLinkedListがJavaコレクションに導入されました。

おすすめ

転載: blog.csdn.net/crazy_xieyi/article/details/126883221