ArrayListのソースコード解析(膨張機構jdk8)

ArrayListの概要

(1)ArrayList固定長配列に基づいて、可変長コレクションクラスです。

(2)ArrayListによってうヌルと繰り返しエレメント、基礎となる配列要素の能力多数のArrayListに加え、許可膨張大きなアレイ機構を再生成します。

(3)のでArrayList、基礎となるアレイベースの実装は、その保証することができるO(1)ランダム複雑で完全なルックアップ動作を。

(4)ArrayList非スレッドセーフ、並行環境は、ArrayListのを操作する複数のスレッドは、例外または予測不可能なエラーをスローします。

ArrayListのメンバプロパティ

ArrayListの上の様々な方法を導入する前にメンバーの基本的な性質を見てください。これはDEFAULTCAPACITY_EMPTY_ELEMENTDATA与EMPTY_ELEMENTDATA的区别是:当我们向数组中添加第一个元素时,DEFAULTCAPACITY_EMPTY_ELEMENTDATA将会知道数组该扩充多少

//默认初始化容量
private static final int DEFAULT_CAPACITY = 10;

//默认的空的数组,这个主要是在构造方法初始化一个空数组的时候使用
private static final Object[] EMPTY_ELEMENTDATA = {};

//使用默认size大小的空数组实例,和EMPTY_ELEMENTDATA区分开来,
//这样可以知道当第一个元素添加的时候进行扩容至多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//ArrayList底层存储数据就是通过数组的形式,ArrayList长度就是数组的长度。
//一个空的实例elementData为上面的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,当添加第一个元素的时候
//会进行扩容,扩容大小就是上面的默认容量DEFAULT_CAPACITY
transient Object[] elementData; // non-private to simplify nested class access

//arrayList的大小
private int size;
复制代码

静的修正EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA

ArrayListのコンストラクタ

(1)コンストラクタの初期容量を持ちます

  • 配列パラメータが0より大きい、からelementDataはサイズのInitialCapacityの値を初期化
  • パラメータが0未満である、からelementData最初は空の配列
  • パラメータは、例外がスローされ、0未満であります
//参数为初始化容量
public ArrayList(int initialCapacity) {
    //判断容量的合法性
    if (initialCapacity > 0) {
        //elementData才是实际存放元素的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //如果传递的长度为0,就是直接使用自己已经定义的成员变量(一个空数组)
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
复制代码

(2)引数なしのコンストラクタを

  • コンストラクタに空の配列DEFAULTCAPACITY_EMPTY_ELEMENTDATAからelementDataを初期化します
  • 最初の要素を追加するには、Addメソッドを呼び出すときに、それが拡大されます
  • = 10 DEFAULT_CAPACITYの大きさに膨張
//无参构造,使用默认的size为10的空数组,在构造方法中没有对数组长度进行设置,会在后续调用add方法的时候进行扩容
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
复制代码

(3)収集パラメータの型コンストラクタ

//将一个参数为Collection的集合转变为ArrayList(实际上就是将集合中的元素换为了数组的形式)。如果
//传入的集合为null会抛出空指针异常(调用c.toArray()方法的时候)
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        //c.toArray()可能不会正确地返回一个 Object[]数组,那么使用Arrays.copyOf()方法
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //如果集合转换为数组之后数组长度为0,就直接使用自己的空成员变量初始化elementData
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
复制代码

これらの工法の上に2つのコンストラクタがそう注意する前に、目標は、理解することは比較的簡単である根本的な配列からelementDataを(this.elementData = XXX)を初期化します違いがあります无参构造方法会将 elementData 初始化一个空数组,插入元素时,扩容将会按默认值重新初始化数组そして、有参的构造方法则会将 elementData 初始化为参数值大小(>= 0)的数组通常の状況下で、我々は、デフォルトのコンストラクタを使用することができます。コンストラクタは、ケースのArrayListに挿入されているどのように多くの要素を知っているだろうにパラメータがある場合は、使用することができます。

それは、引数なしのコンストラクタ上記の使用に来て、あなたが呼び出すときにaddメソッドは、拡張可能なので、のは、メソッドの詳細を見てみましょうと拡張が追加されます場合には

ArrayListのaddメソッド

一般的なプロセスの方法を追加します

//将指定元素添加到list的末尾
public boolean add(E e) {
    //因为要添加元素,所以添加之后可能导致容量不够,所以需要在添加之前进行判断(扩容)
    ensureCapacityInternal(size + 1);  // Increments modCount!!(待会会介绍到fast-fail)
    elementData[size++] = e;
    return true;
}
复制代码

私たちは、要素を追加する追加する前にアプローチを参照してください、それは最初のサイズの大きさを決定しますので、我々は方法の詳細を確認ensureCapacityInternal

ensureCapacityInternalの分析方法

private void ensureCapacityInternal(int minCapacity) {
    //这里就是判断elementData数组是不是为空数组
    //(使用的无参构造的时候,elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
    //如果是,那么比较size+1(第一次调用add的时候size+1=1)和DEFAULT_CAPACITY,
    //那么显然容量为10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
复制代码

あなたが最初の要素に追加したい場合は*、minCapacityには比較Math.max()メソッド、minCapacityに10に、(サイズ+ 1 = 0 + 1 = 1)です。**その後すぐにensureExplicitCapacity更新された値のmodCountを呼び出し、追加の容量が必要とされているかどうかを判断します

ensureExplicitCapacity分析法

private void ensureExplicitCapacity(int minCapacity) {
    modCount++; //这里就是add方法中注释的Increments modCount
    //溢出
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);//这里就是执行扩容的方法
}
复制代码

拡大の主な方法で次の外観は育ちます。

分析するために成長

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
    // oldCapacity为旧数组的容量
    int oldCapacity = elementData.length;
    // newCapacity为新数组的容量(oldCap+oldCap/2:即更新为旧容量的1.5倍)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 检查新容量的大小是否小于最小需要容量,如果小于那旧将最小容量最为数组的新容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果新容量大于MAX_ARRAY_SIZE,使用hugeCapacity比较二者
    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);
}
复制代码

hugeCapacity方法

ここでは方法を簡単に見hugeCapacity

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    //对minCapacity和MAX_ARRAY_SIZE进行比较
    //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
    //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
    //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
复制代码

要約実行フローメソッドを追加

私たちは、引数なしでコンストラクタを使用した場合、最初の呼び出しの後に実行プロセスは、メソッドを追加する際に何を整理するために、単純なマップを使用します

これが初めてである容量拡張の値は10の後である場合に、Addメソッドを呼び出す方法、

  • 第二の要素を追加していき(最初の呼び出しパラメータ受け渡し方法があることに注意をensureCapacityInternalサイズ+ 1 = 1 + 1 = 2)

  • ensureCapacityInternal方法では、からelementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATAは確立し、その直接の実行方法のensureExplicitCapacityありません

  • 第二の決意(2-10 =は-8)、大MAX_ARRAY_SIZEより即ちnewCapacityを確立していない場合、それが入らないように、わずかensureExplicitCapacity minCapacityに2の方法は、渡されたgrow方法。アレイ容量10は、追加の方法は、大きさが1に増加し、trueを返します。

  • 要素10は、(プロセスは同様であるが、拡張メソッドを成長行わない場合)...... 3,4をさらに添加するとし

  • newCapacity 15を計算するときに最初の決意を満たさない場合は11個の要素の追加時間は、グロー方法に入る場合、(10 + 1 = 11)のことminCapacityに比が大きくなります。新しい容量が最大のアレイサイズよりも大きくない、hugeCapacity方法を入力しないであろう。アレイ15の容量を拡大し、方法は真を返す追加、サイズが11に増加しました。

追加(int型のインデックス、Eの要素)メソッド

//在元素序列 index 位置处插入
public void add(int index, E element) {
    rangeCheckForAdd(index); //校验传递的index参数是不是合法
    // 1. 检测是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 2. 将 index 及其之后的所有元素都向后移一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 3. 将新元素插入至 index 处
    elementData[index] = element;
    size++;
}
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0) //这里判断的index>size(保证数组的连续性),index小于0
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
复制代码

追加(INTインデックス、E要素)方法(要素の配列中の(指定された位置は、假设该位置合理次の手順では約ある)が挿入されます)

  1. (ここで上記実装)十分な空間配列が存在するか否かを検出します
  2. そのインデックスのすべての要素が後退した後、
  3. 新しい要素がインデックスに挿入されます。

新しい要素がシーケンスに指定した場所に挿入されて、あなたは要素を配置し、移動後に戻って、新しい要素のためのスペースを作る必要があります。この操作の時間計算量は、O(N)しばしば要素は効率の問題、設定時間内の要素の特に多数につながる可能性が動きます。必要でない場合は、毎日の開発では、我々は第二の挿入方法は、大規模なコレクションを呼び出し、避けるべきです。

ArrayListの削除方法

ArrayListの要素を削除する2つの方法をサポートしています

1、添字に従って削除(INTインデックス)を削除

public E remove(int index) {
    rangeCheck(index); //校验下标是否合法(如果index>size,旧抛出IndexOutOfBoundsException异常)
    modCount++;//修改list结构,就需要更新这个值
    E oldValue = elementData(index); //直接在数组中查找这个值

    int numMoved = size - index - 1;//这里计算所需要移动的数目
    //如果这个值大于0 说明后续有元素需要左移(size=index+1)
    //如果是0说明被移除的对象就是最后一位元素(不需要移动别的元素)
    if (numMoved > 0)
        //索引index只有的所有元素左移一位  覆盖掉index位置上的元素
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //移动之后,原数组中size位置null
    elementData[--size] = null; // clear to let GC do its work
    //返回旧值
    return oldValue;
}
//src:源数组   
//srcPos:从源数组的srcPos位置处开始移动
//dest:目标数组
//desPos:源数组的srcPos位置处开始移动的元素,这些元素从目标数组的desPos处开始填充
//length:移动源数组的长度
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);
复制代码

以下に示すように除去プロセス

図2は、マッチングパラメータおよび削​​除の最初の要素を要素を除去するために応じて(オブジェクトO)を除去します

public boolean remove(Object o) {
    //如果元素是null 遍历数组移除第一个null
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                //遍历找到第一个null元素的下标 调用下标移除元素的方法
                fastRemove(index);
                return true;
            }
    } else {
        //找到元素对应的下标 调用下标移除元素的方法
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
//按照下标移除元素(通过数组元素的位置移动来达到删除的效果)
private void fastRemove(int index) {
  modCount++;
  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
}
复制代码

ArrayListの他の方法

ensureCapacityメソッド

使用前に最善の方法は、新規割り当ての増分の数を減らすための要素ensureCapacity多数を追加します

public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}
复制代码

ArrayListの概要

(1)ArrayList10(1.7初期化つまりは、第1添加元素追加メソッドが呼び出され、遅延された後、既定のコンストラクタを使用して、固定長の配列に基づいて可変長コレクションクラスは、容量のうちからelementData時になります初期化され容量)が10に初期化されます。

(2)ArrayListによってうヌルと繰り返しエレメント、基礎となる配列要素の能力多数のArrayListに加え、許可膨張大きなアレイ機構を再生成します。ArrayList拡張長さは1.5倍の長さであります

(3)のでArrayList、基礎となるアレイベースの実装は、その保証することができるO(1)ランダム複雑で完全なルックアップ動作を。

(4)ArrayList非スレッドセーフ、並行環境は、ArrayListのを操作する複数のスレッドは、例外または予測不可能なエラーをスローします。

(5)添加の順序は便利です

(6)欠失および挿入コピーアレイ、パフォーマンスの低下(LinkindListを使用)

(7)はInteger.MAX_VALUE - 8:それは場合よりも主に異なるJVMの都合上、JVMといくつかの最初のいくつかのデータを追加し、拡張後の容量がMAX_ARRAY_SIZEよりも大きい場合、我々は最小容量とMAX_ARRAY_SIZE比較を比較する必要があるが、大規模な、唯一それ以外の場合にInteger.MAX_VALUEで-8は、Integer.MAX_VALUEを取ることができます。これはjdk1.7から始まりに過ぎません

メカニズムを、高速フェイル

フェイルファスト説明:

システム設計では、フェイルファストシステムは、直ちにその界面での失敗を示す可能性がある任意の状態を報告1です。フェイルファストシステムは、通常、正常な動作を停止ではなく、おそらく欠陥のあるプロセスを継続しようとするように設計されています。このようなデザインは、多くの場合、運転中にいくつかの点で、システムの状態をチェックし、そのいずれかの障害を早期に発見することができます。フェイルファストモジュールの責任は、システムの次の最高レベルはそれらを処理せ、エラーを検出しています。

おそらく意味:システム設計、システムはすぐに示すことがあり、システム障害のいずれかの場合を報告し、すぐに失敗する可能性があります。システムの失敗は、一般的ではなく、潜在的欠陥のプロセスを継続しようとするよりも、正常に動作して迅速な停止のために設計されています。この設計は、通常、検査システムの動作状態の複数の点であるので、誤動作を早期に検出することができます。迅速な障害検出モジュール業務、システムに処理エラーの次に高いレベルをさせ、間違っています。

実際には、システムの設計時間を行うと、異常な状況を考慮することは、例外が発生すると、そのような次の簡単な例として、停止して、直接報告

//这里的代码是一个对两个整数做除法的方法,在fast_fail_method方法中,我们对被除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。
public int fast_fail_method(int arg1,int arg2){
    if(arg2 == 0){
        throw new RuntimeException("can't be zero");
    }
    return arg1/arg2;
}
复制代码

不適切に使用されている場合設計するために使用されるメカニズムの多くの部分でのJavaコレクションクラスでは、予想外に設計されたコードフェイルファストトリガ機構が起こります。我々は通常、Javaで言うフェイルファストメカニズムは、デフォルトでは、Javaのエラー検出機構の集合を指し複数のスレッドが構造変化のコレクションの一部を操作するときは、メカニズムをトリガする可能性があり、その後、**例外同時変更がスローされますConcurrentModificationException**。もちろん、そうでない場合は、マルチスレッド環境下では、foreachのトラバーサル場合追加/ removeメソッドを使用している場合にもスローされることがあります。リファレンスは、メカニズムを、高速フェイル簡単な要約であることをここに、

之所以会抛出ConcurrentModificationException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示可能发生了并发修改!所以,在使用Java的集合类的时候,如果发生ConcurrentModificationException,优先考虑fail-fast有关的情况,实际上这可能并没有真的发生并发,只是Iterator使用了fail-fast的保护机制,只要他发现有某一次修改是未经过自己进行的,那么就会抛出异常。

おすすめ

転載: juejin.im/post/5d42ab5e5188255d691bc8d6