Java 8では、なぜArrayListのデフォルトの容量がゼロになるのですか?


93

思い出しますが、Java 8以前は、デフォルトの容量ArrayListは10でした。

驚いたことに、デフォルト(void)コンストラクターに関するコメントには、まだ次のように書かれています。 Constructs an empty list with an initial capacity of ten.

からArrayList.java

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

...

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

回答:


105

技術的に10は、バッキング配列の遅延初期化を認めた場合、ゼロではありません。見る:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

どこ

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

あなたが参照しているのは、最初は空のすべてのArrayListオブジェクト間で共有される、サイズがゼロの初期配列オブジェクトだけです。つまり、容量10遅延保証されます。これは、Java 7にも存在する最適化です。

確かに、コンストラクタコントラクトは完全に正確ではありません。おそらく、これが混乱の元です。

バックグラウンド

マイクドゥイゴによるメールです

空のArrayListとHashMapパッチの更新バージョンを投稿しました。

http://cr.openjdk.java.net/~mduigou/JDK-7143928/1/webrev/

この改訂された実装ではどちらのクラスにも新しいフィールドは導入されていません。ArrayListの場合、バッキング配列の遅延割り当ては、リストがデフォルトサイズで作成された場合にのみ発生します。パフォーマンス分析チームによると、ArrayListインスタンスの約85%はデフォルトサイズで作成されているため、この最適化は圧倒的多数のケースで有効です。

HashMapの場合、しきい値フィールドをクリエイティブに使用して、バケット配列が必要になるまで、要求された初期サイズを追跡します。読み取り側では、空のマップケースがisEmpty()でテストされます。書き込みサイズでは、(テーブル== EMPTY_TABLE)の比較を使用して、バケット配列を拡張する必要性を検出します。readObjectでは、効率的な初期容量を選択するためにもう少し作業が必要です。

送信元:http : //mail.openjdk.java.net/pipermail/core-libs-dev/2013-April/015585.html


4
bugs.java.com/bugdatabase/view_bug.do?bug_id=7143928によれば、ヒープ使用量の削減と応答時間の改善につながります(2つのアプリケーションの数値が示されています)
ThomasKlägerDec

3
@khelwood:このJavadoc以外では、ArrayListは実際にその容量を「報告」しませんgetCapacity()。メソッドなどはありません。(とはいえ、のようなものensureCapacity(7)はデフォルトで初期化されたArrayListに対して何もしないので、私たちは本当にその初期容量が本当に10であるかのように動作するはずだと思います
...

10
いい掘り。デフォルトの初期容量は実際にはゼロではなく10です。デフォルトのケースは特別なケースとしてレイジーに割り当てられます。これは、ArrayList引数なしのコンストラクターで作成されたものにコンストラクターに要素を繰り返し追加するか、intコンストラクターにゼロを渡す場合、および内部配列のサイズを反映して、またはデバッガーで確認すると確認できます。デフォルトの場合、配列は長さ0から10にジャンプし、1.5倍の成長率に従って15、22にジャンプします。0から1、2、3、4、6、9、13、19 ....に成長初期容量の結果としてゼロを渡す
スチュアートマーク

13
私は変更と引用された電子メールの作成者であるMike Duigouです。このメッセージを承認します。Stスチュアートが言うように、動機は主にパフォーマンスではなくスペース節約にありましたが、バッキングアレイの作成を頻繁に回避することによるパフォーマンス上のわずかな利点もあります。
Mike Duigou

4
@assylias:; ^)いいえ、シングルトンはemptyList()まだいくつかの空のArrayListインスタンスよりも少ないメモリを消費するので、それはまだその場所があります。現在はそれほど重要ではないため、すべての場所で必要になるわけではありません。特に、後で要素を追加する可能性が高い場所では必要ありません。また、不変の空のリストが必要な場合があることも覚えておいてくださいemptyList()
Holger

23

Java 8では、ArrayListオブジェクトに少なくとも1つのオブジェクトを追加するまで、ArrayListのデフォルト容量は0です(遅延初期化と呼ぶことができます)。

では、なぜこの変更がJava 8で行われたのでしょうか。

答えは、メモリ消費を節約することです。何百万もの配列リストオブジェクトがリアルタイムのJavaアプリケーションで作成されます。10個のオブジェクトのデフォルトサイズは、作成時に基になる配列に10個のポインター(40または80バイト)を割り当て、それらをnullで埋めることを意味します。空の配列(nullで埋められた)は多くのメモリを占有します。

遅延初期化は、実際に配列リストを使用する瞬間まで、このメモリ消費を延期します。

以下のコードを参考にしてください。

ArrayList al = new ArrayList();          //Size:  0, Capacity:  0
ArrayList al = new ArrayList(5);         //Size:  0, Capacity:  5
ArrayList al = new ArrayList(new ArrayList(5)); //Size:  0, Capacity:  0
al.add( "shailesh" );                    //Size:  1, Capacity: 10

public static void main( String[] args )
        throws Exception
    {
        ArrayList al = new ArrayList();
        getCapacity( al );
        al.add( "shailesh" );
        getCapacity( al );
    }

    static void getCapacity( ArrayList<?> l )
        throws Exception
    {
        Field dataField = ArrayList.class.getDeclaredField( "elementData" );
        dataField.setAccessible( true );
        System.out.format( "Size: %2d, Capacity: %2d%n", l.size(), ( (Object[]) dataField.get( l ) ).length );
}

Response: - 
Size:  0, Capacity:  0
Size:  1, Capacity: 10

記事のJava 8でのArrayListの初期容量は詳細にそれを説明しています。


7

ArrayListで行われる最初の操作がaddAll、10を超える要素を持つコレクションを渡すことである場合、ArrayListの内容を保持するために最初の10要素の配列を作成しようとすると、ウィンドウがスローされます。ArrayListに何かが追加されるときはいつでも、結果のリストのサイズがバッキングストアのサイズを超えるかどうかをテストする必要があります。最初のバッキングストアのサイズを10ではなく0にすると、このテストは、最初の操作が「追加」であるリストの有効期間中に1回余分に失敗し、最初の10項目の配列の作成が必要になりますが、そのコストは決して使われることのない10項目の配列を作成するコストよりも少ない。

そうは言っても、「addAll」のオーバーロードがあり、現在のアイテムの後にリストに追加される可能性のあるアイテム(存在する場合)の数を指定し、それを使用して、割り当て動作に影響を与えます。場合によっては、リストに最後のいくつかの項目を追加するコードは、リストにそれ以上のスペースが必要になることはないというかなり良い考えを持っています。リストが一度作成され、その後変更されない状況が数多くあります。ポイントコードで、リストの最終的なサイズが170要素になることがわかっている場合、150個の要素とサイズ160のバッキングストアがあります。


について非常に良い点addAll()。これは、最初のmallocに関する効率を改善するもう1つの機会です。
kevinarpe

@kevinarpe:Javaのライブラリが、プログラムがどのように使用される可能性が高いかを示すために、さらにいくつかの方法で設計されていれば幸いです。たとえば、古いスタイルのサブストリングは、一部のユースケースではお粗末でしたが、他のユースケースでは優れていました。「オリジナルより長持ちする可能性が高い部分文字列」と「オリジナルより長持ちする可能性が低い部分文字列」に別々の関数があり、コードが90%の確率で正しいものを使用した場合、これらは古いまたは新しい文字列の実装。
スーパーキャット2015

3

問題は「なぜ?」です。

メモリプロファイリングインスペクション(たとえば(https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays)は、空の(nullで満たされた)配列が大量のメモリを占有していることを示しています。

10個のオブジェクトのデフォルトサイズは、作成時に基になる配列に10個のポインター(40または80バイト)を割り当て、それらをnullで埋めることを意味します。実際のJavaアプリケーションは、何百万もの配列リストを作成します。

導入された変更により、^ Wが削除され、実際に配列リストを使用するまでこのメモリ消費が延期されます。


「消費」を「廃棄物」に修正してください。あなたが提供するリンクは、彼らがどこでもメモリをどんどん飲み始めていることを意味するのではなく、null要素を持つ配列がそれらに割り当てられたメモリを不釣り合いに浪費するだけです。「消費」は、割り当てを超えて魔法のようにメモリを使用することを意味しますが、そうではありません。
mechalynx

0

JAVA 8のArrayListのデフォルトサイズは10です。JAVA8で行われた唯一の変更は、コーダーが10未満の要素を追加した場合、残りのarraylistの空白の場所がnullに指定されないことです。私は自分がこの状況を経験していて、日食がJAVA 8のこの変化を調べさせたのでそう言いました。

以下のスクリーンショットを見て、この変更を正当化できます。ObjectList [10]でArrayListのサイズが10に指定されていることがわかりますが、表示される要素の数は7のみです。残りのnull値の要素はここには表示されません。以下のJAVA 7のスクリーンショットは、1つの変更と同じです。つまり、完全な配列リストを反復している場合、コーダーがnull値を処理するためのコードを記述する必要があるnull値の要素も表示されますが、JAVA 8ではこの負担は取り除かれています。コーダー/開発者の責任者。

スクリーンショットのリンク。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.