ArrayList原理の分析

質問をする

  1. 何に基づいて?アレイ?リンクリスト?キュー?
  2. なぜあなたはadd要素を保つことができますか?

分析

実現する方法

定義された変数:

配列は維持されます:

transient Object[] elementData; // non-private to simplify nested class access

private int size;

ArrayListすべてが内部的add、remove、set、getelementDataこの配列で動作するため、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 = {};

工法

ArrayList3つの構築方法が定義されています。それらは非パラメータ構築です。1つのintタイプのパラメータ構築方法が渡され、リストがパラメータ構築方法として渡されます。

  1. 初期の長さを渡すこの方法は、無駄なメモリスペースを使いすぎないように、リストの長さがわかっている場合に一般的に使用されます。
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);
    }
}
  1. パラメータの構築はelementDataなく、デフォルトの配列、長さ0 指します
  2. リストを渡します。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;
}

拡張

フロントaddaddAllメソッド、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);
}

分析:

  1. まず、addまたはaddAll後のリストの長さを表す値を渡します。
  2. 追加するたびに配列を拡張することはできません。そうしないと、コストが高すぎるため、配列を拡張すると、毎回長さが1つ増えるだけではありません。
  3. 現在空の配列である場合は、デフォルト値と渡された値の最大値を取ります。初めて直接長さ10の配列を作成するには、それ以外の場合は要素を追加して1回拡張しますが、これはコストがかかります。
  4. 拡張が必要かどうかを計算します
  5. 拡張操作、通常の状況では、アレイ容量は1.5倍拡張されますが、2つの特殊なケースがあります。
    1. 初めて要素を追加し、デフォルトの長さ10に直接拡張します
    2. addAll要素がたくさんあり、全長が元の長さの1.5倍を超えている場合は、この長さに直接拡張します
  6. array copyメソッドを使用して、すべての元のデータを展開された新しい配列にコピーし、最後に配列をelementDataに割り当てます。
  7. 拡張が完了しました

総括する

  1. ArrayListは配列に基づいて実装され、配列のデフォルトの長さは10です。すべての操作は、維持されている配列に対する操作です。
  2. addメソッドとaddAllメソッドは、配列拡張をトリガーする場合があります
  3. 長さが十分でない場合、拡張長さは通常、既存の長さの1.5倍になります。
  4. 拡張、削除などはすべてアレイをコピーすることで実現されるため、リストが長すぎないようにしてください。長すぎると、各コピーのパフォーマンスが過度に消費されます。

おすすめ

転載: blog.csdn.net/lizebin_bin/article/details/88876878