膨張機構についての話のArrayList

次のようにArrayListに、ソースのコンストラクタを初期化する3つの方法があります。

 /**
     * 默认初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;
    

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     *默认构造函数,使用初始容量10构造一个空列表(无参数构造)
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    /**
     * 带初始容量参数的构造函数。(用户自己指定容量)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//初始容量大于0
            //创建initialCapacity大小的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//初始容量等于0
            //创建空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {//初始容量小于0,抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }


   /**
    *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
    *如果指定的集合为null,throws NullPointerException。 
    */
     public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

観察力の鋭い学生は見つける:あなたは、引数なしのコンストラクタでのArrayListを作成すると、割り当てが実際に空の配列を初期化しています。本当に配列要素が動作に追加すると、実際に容量を割り当てました。すなわち、第1の素子アレイ、拡張能力のアレイ10を追加する場合。 私たちは分析すると、次のArrayListの拡大はこのコンテンツについてお話します!

ステップ解析のArrayList膨張機構によって二つのステップ

ArrayListのAのケーススタディを作成するには、ここで引数なしのコンストラクタ

1.まず見て  add 方法

    /**
     * 将指定的元素追加到此列表的末尾。 
     */
    public boolean add(E e) {
   //添加元素之前,先调用ensureCapacityInternal方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //这里看到ArrayList添加元素的实质就相当于为数组赋值
        elementData[size++] = e;
        return true;
    }

で2見てみましょう  ensureCapacityInternal() 方法

私たちは見ることができる  add 方法最初の呼び出しをensureCapacityInternal(size + 1)

   //得到最小扩容量
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // 获取默认的容量和传入参数的较大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

あなたが比較Math.max()メソッドでは、minCapacityに1であることを、最初の要素に追加したい場合は、minCapacityに10なります。

3.  ensureExplicitCapacity() メソッド

あなたが呼び出す場合  ensureCapacityInternal() の方法は、確かに(実行)このメソッドに行ったことになる、のは、このメソッドのソースコードを見てみましょう!

  //判断是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            //调用grow方法进行扩容,调用此方法代表已经开始扩容了
            grow(minCapacity);
    }

私たちは、詳しく見ていきます。

  • 我々は、第1吸気ArrayListに要素を追加すると、elementData.lengthは0(またはリストが空であるため)、ための実行であり  ensureCapacityInternal() 、このケース10の方法において、minCapacityになります。この場合、minCapacity - elementData.length > 0 それはセットアップし、それが入ります  grow(minCapacity) 方法を。
  • 第二の追加要素は、minCapacityにある2である場合、その後に第1膨張要素10を添加した後lementData.length(容量)からe。この時点で、minCapacity - elementData.length > 0 それが(実行)を入力しないように、それは、保持していないgrow(minCapacity) 方法を。
  • 3,4···10第二の要素に、方法がまだ実行されていないが成長を追加する場合は、アレイ10の容量です。

最初の11個の要素を追加するまで、minCapacityに(11)elementData.length(10)よりも大きくなっています。拡張の方法に成長します。

4.  grow() メソッド

  /**
     * 要分配的最大数组大小
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * ArrayList扩容的核心方法。
     */
    private void grow(int minCapacity) {
        // oldCapacity为旧容量,newCapacity为新容量
        int oldCapacity = elementData.length;
        //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
        //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
       // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,
       //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。
        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)、 その各拡張後ArrayListの容量は約1.5倍になると(oldCapacity偶数が1.5倍または約1.5倍です)! 10 + 10/2 = 15、33 + 33/2 = 49:などの異なるパリティ。それが奇数の場合、それは小数を失うことになります。

「>>」(シフト演算子):1 >>の電源に右の対応するシフトnは2で除算されたnビットだけ右に、2で割りました。ここoldCapacityは明らかにそうoldCapacity / 2に右に対応にシフトしました。バイナリ・ラージ・データ演算のためのプログラムだけそれについて移動されるので、変位操作者は、はるかに速く、通常の算術演算子のものよりも、しない計算、それによって効率を改善し、リソース保存  

一例として、私たちは何を探検しましょうgrow() 方法を:

  • 最初の決定を比較した後、newCapacity = minCapacityに(10)肯定的である場合、最初の追加要素は、oldCapacityは、0である場合。しかし、場合裁判官は大MAX_ARRAY_SIZEよりnewCapacityは、それが入らないこと、第二に設定していない  hugeCapacity 方法を。アレイ容量10は、addメソッドがtrueを返し、大きさが1に増加しました。
  • 最初の追加が成長法、newCapacity 15に素子11はminCapacityに比(11)であるときに第一決意を満たさない場合、大きいです。新しい容量が最大のアレイサイズよりも大きくない、hugeCapacity方法を入力しないであろう。アレイ15の容量を拡大し、追加方法がtrueを返し、サイズが11に増加しました。
  • だから、??????

重要なことは、ここに加えるが、簡単に知識のポイントを見落とし:

  • Javaの  length プロパティは、配列が言うのは、たとえば、あなたは、配列を宣言配列の長さは、このプロパティの長さを使用している知りたいと思っています。
  • Javaの  length() 文字列の長さを見てみたいがで使用されている場合の方法は、言うべき文字列である  length() 。この方法。
  • Javaの  size() 希望は、この一般的などのように多くの要素を参照する場合、このメソッドは、言ってジェネリックコレクションのものです、それが参照するには、このメソッドを呼び出します!

5.  hugeCapacity() 方法。

上記の  grow() 方法源我々が知っている:新しい容量はMAX_ARRAY_SIZEより大きい場合、(実行)を入力し  hugeCapacity() 、最大容量よりminCapacityには大きいが、新たな容量があった場合minCapacityに及びMAX_ARRAY_SIZEを比較する方法をInteger.MAX_VALUE、そうでなければ、新しい容量がMAX_ARRAY_SIZE比べて大きさがあります  Integer.MAX_VALUE - 8

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

三  System.arraycopy() 及び  Arrays.copyOf()方法

ソースコードを読んで、私たちはこれらの2つの方法の呼び出しのArrayListの数が多いでしょう。例えば:私たちは、上記の操作と拡張について話しているadd(int index, E element)toArray() そして他の方法は、方法で使用されています!

3.1  System.arraycopy() 方法

    /**
     * 在此列表中的指定位置插入指定的元素。 
     *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
     *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //arraycopy()方法实现数组自己复制自己
        //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量;
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;
    }

我々は、次をテストするための簡単な方法を書きました:

public class ArraycopyTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a = new int[10];
		a[0] = 0;
		a[1] = 1;
		a[2] = 2;
		a[3] = 3;
		System.arraycopy(a, 2, a, 3, 3);
		a[2]=99;
		for (int i = 0; i < a.length; i++) {
			System.out.println(a[i]);
		}
	}

}

結果:

0 1 99 2 3 0 0 0 0 0 

3.2  Arrays.copyOf()方法

   / **
     このリストに戻り正しい順序は、すべての配列要素は、(最初​​から最後の要素まで)を含む、返される配列の実行時の型が指定された配列型を実行しています。 
     * /
    パブリックオブジェクト[]のtoArray(){
    //からelementData:配列をコピーするには、サイズ:長さがコピーします
        Arrays.copyOf(からelementData、サイズ)を返します。
    }

個人の使用と感じ  Arrays.copyOf()、次のようにメソッドは、主に元の配列の拡張を与えるために、テストコードがあります:

public class ArrayscopyOfTest {

	public static void main(String[] args) {
		int[] a = new int[3];
		a[0] = 0;
		a[1] = 1;
		a[2] = 2;
		int[] b = Arrays.copyOf(a, 10);
		System.out.println("b.length"+b.length);
	}
}

結果:

10

3.3リンクと違いどちらも

連絡先:

両方のソースコードはcopyOf()は実際の内部コール見出すことができる参照  System.arraycopy() 方法

違い:

arraycopy() ターゲット配列のニーズ、配列または元の配列の独自の定義における配列の元のコピー、および始点とコピーの長さを選択することができ、かつ配列内の新しい位置に配置され  copyOf() 、システムが自動的に内部で新しい配列を作成し、配列を返しています。

四つ  ensureCapacityの方法

ArrayListのソースは、そこにある  ensureCapacity 方法は、あなたが気づいた場合、私は明らかに呼び出しが、その後、ユーザーにこの方法どのような影響を与える可能ですので、このメソッドは、内部のArrayListと呼ばれていないか分からないのですか?

    /**
    如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。
     *
     * @param   minCapacity   所需的最小容量
     */
    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);
        }
    }

好ましくは、前に使用される要素の多数追加  ensureCapacity 再割当て増分の数を減らすために、方法を

私たちは、この方法は、実際には次のテスト次のコードに影響を与えます:

public class EnsureCapacityTest {
	public static void main(String[] args) {
		ArrayList<Object> list = new ArrayList<Object>();
		final int N = 10000000;
		long startTime = System.currentTimeMillis();
		for (int i = 0; i < N; i++) {
			list.add(i);
		}
		long endTime = System.currentTimeMillis();
		System.out.println("使用ensureCapacity方法前:"+(endTime - startTime));

	}
}

結果:

使用ensureCapacity方法前:2158
public class EnsureCapacityTest {
    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<Object>();
        final int N = 10000000;
        list = new ArrayList<Object>();
        long startTime1 = System.currentTimeMillis();
        list.ensureCapacity(N);
        for (int i = 0; i < N; i++) {
            list.add(i);
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1));
    }
}

結果:


使用ensureCapacity方法前:1773

業績によって、我々はArrayListに追加が前に多数の素子を使用するのが最適であることがわかりますensureCapacity 増分再割り当ての数を減らすための方法。

公開された91元の記事 ウォン称賛32 ビュー30000 +

おすすめ

転載: blog.csdn.net/qq_41345773/article/details/104990203