3.ArrayListソースコード分析

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;
}

ソースコード分析

  1. DEFAULT_CAPACITYは、最初に展開されたときの配列のサイズを表します。この値は、インタビュー中によく言及され、初期化中にも言及されます。
  2. sizeは現在の配列のサイズを表し、タイプはintであり、volatileは変更されないため、スレッドセーフではありません。
  3. 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;
        }
    }
}

ソースコード分析

  1. ArrayListパラメーターなしコンストラクターが初期化されると、デフォルトのサイズは空の配列になります。これは通常の10ではなく、要素が最初に追加されたときに展開される配列値です。
  2. データの初期化を指定すると、コメントに「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を使用して要素を配列に直接追加します。ロック制御がないのはこの単純な割り当てによるため、ここでの操作はスレッドセーフではありません。

総括する

  1. 拡張のルールは2倍ではなく、元の容量に半分の容量を加えたものです。端的に言えば、拡張された容量は元の容量の1.5倍です。
  2. ArrayList内の配列の最大値はInteger.MAX_VALUEです。この値を超えると、JVMは配列にメモリスペースを割り当てません。
  3. 追加されたとき、値は厳密にチェックされていなかったため、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();
        }
    }
}

ソースコード分析

  1. ソースコードからわかるように、次の方法は2つのことを実行します。1つは反復を続行できるかどうかを確認すること、もう1つは反復の値を見つけて次の反復(カーソル+1)の準備をすることです。
  2. lastRet = -1の目的は、重複した削除を防ぐことです。
  3. 要素が正常に削除されると、配列の現在のmodCountが変更され、expectedModCountがここで再割り当てされ、2つの値は次の反復で同じになります。

おすすめ

転載: blog.csdn.net/Jgx1214/article/details/109064315