の通常のコンストラクタArrayList
は次のとおりです。
ArrayList<?> list = new ArrayList<>();
ただし、初期容量のパラメータを持つオーバーロードされたコンストラクタもあります。
ArrayList<?> list = new ArrayList<>(20);
必要にArrayList
応じて追加できるのに、初期容量を持つを作成すると便利なのはなぜですか?
の通常のコンストラクタArrayList
は次のとおりです。
ArrayList<?> list = new ArrayList<>();
ただし、初期容量のパラメータを持つオーバーロードされたコンストラクタもあります。
ArrayList<?> list = new ArrayList<>(20);
必要にArrayList
応じて追加できるのに、初期容量を持つを作成すると便利なのはなぜですか?
回答:
事前にサイズをご存知の場合 ArrayList
は、初期容量を指定する方が効率的です。これを行わないと、リストが大きくなるにつれて内部配列を繰り返し再割り当てする必要があります。
最終的なリストが大きいほど、再割り当てを回避することにより、時間を節約できます。
つまり、事前割り当てがなくても、n
要素の後ろに要素を挿入ArrayList
すると、合計O(n)
時間がかかることが保証されます。つまり、要素の追加は、償却済みの一定時間操作です。これは、各再割り当てで配列のサイズを指数的に、通常は倍に増やすことで実現され1.5
ます。このアプローチを使用すると、操作の合計数をと表示できますO(n)
。
O(n log n)
はlog n
作業n
時間を実行します。これはかなり大きく見積もられています(ただし、上限がOであるため、技術的には大きなOで正確です)。s + s * 1.5 + s * 1.5 ^ 2 + ... + s * 1.5 ^ m(s * 1.5 ^ m <n <s * 1.5 ^(m + 1)など)の要素を合計でコピーします。私は合計が得意ではないので、頭の上から正確な計算をすることはできません(サイズ変更係数2の場合、2nなので、1.5nであるか、小さな定数をとる可能性があります)。この合計がせいぜいnよりも大きい一定の因子であることを確認するには、目を細めすぎます。したがって、O(n)であるO(k * n)コピーを取ります。
ためArrayList
である動的にリサイズアレイが初期(デフォルト)固定サイズのアレイとして実現されるデータ構造は、。これがいっぱいになると、配列は倍のサイズに拡張されます。この操作はコストがかかるため、できる限り少なくする必要があります。
したがって、上限が20アイテムであることがわかっている場合は、初期の長さを20にして配列を作成する方が、デフォルトの15を使用するよりもサイズを変更して15*2 = 30
、拡張のサイクルを無駄にしながら20だけを使用するよりも優れています。
PS-AmitGが言うように、拡張係数は実装固有です(この場合(oldCapacity * 3)/2 + 1
)。
int newCapacity = (oldCapacity * 3)/2 + 1;
Arraylistのデフォルトのサイズは10です。
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this(10);
}
したがって、100個以上のレコードを追加する場合は、メモリ再割り当てのオーバーヘッドを確認できます。
ArrayList<?> list = new ArrayList<>();
// same as new ArrayList<>(10);
したがって、Arraylistに格納される要素の数について何か考えがある場合は、10から始めてそれを増やすのではなく、そのサイズのArraylistを作成することをお勧めします。
private static final int DEFAULT_CAPACITY = 10
私は実際に2か月前にこのトピックに関するブログ投稿を書きました。この記事はC#を対象としてList<T>
いますが、JavaのArrayList
実装も非常に似ています。以来ArrayList
ダイナミックアレイを使用して実装され、それは要求に応じてサイズが増大します。したがって、容量コンストラクターの理由は最適化のためです。
これらのサイズ変更操作のいずれかが発生すると、ArrayListは配列の内容を古い配列の容量の2倍の新しい配列にコピーします。この操作はO(n)時間で実行されます。
ArrayList
サイズの増加の例を次に示します。
10
16
25
38
58
... 17 resizes ...
198578
297868
446803
670205
1005308
したがって、リストの容量は10
で始まり、11番目のアイテムが追加されると、50% + 1
まで増加し16
ます。17番目のアイテムでArrayList
は、再び増加25
します。次に、必要な容量がすでにとして知られているリストを作成する例を考えてみましょう1000000
。ArrayList
サイズコンストラクタなしでを作成すると、ArrayList.add
1000000
通常はO(1)またはサイズ変更時にO(n)がかかる時間。
1000000 + 16 + 25 + ... + 670205 + 1005308 = 4015851操作
コンストラクタを使用してこれを比較してから、 ArrayList.add
O(1)で実行することが保証されているをします。
1000000 + 1000000 = 2000000オペレーション
Javaは上記のとおりで、から始まり、10
それぞれのサイズ変更を増やします50% + 1
。C#は、4
サイズを変更するたびに2倍になり、より積極的に増加します。の1000000
上記のC#の使用例追加の例3097084
操作をしています。
ArrayListの初期サイズをに設定するとArrayList<>(100)
、内部メモリの再割り当てが発生する回数が減ります。
例:
ArrayList example = new ArrayList<Integer>(3);
example.add(1); // size() == 1
example.add(2); // size() == 2,
example.add(2); // size() == 3, example has been 'filled'
example.add(3); // size() == 4, example has been 'expanded' so that the fourth element can be added.
上記の例でわかるように、ArrayList
必要に応じてを拡張できます。これが示さないことは、Arraylistのサイズが通常2倍になることです(ただし、新しいサイズは実装に依存することに注意してください)。以下はOracleからの引用です:
「各ArrayListインスタンスには容量があります。容量とは、リストに要素を格納するために使用される配列のサイズです。これは常に少なくともリストサイズと同じです。要素がArrayListに追加されると、その容量は自動的に増加します。成長ポリシーの詳細は、要素を追加すると償却費が一定になるという事実を超えて規定されていません。」
明らかに、保持する範囲の種類がわからない場合は、サイズを設定することはおそらくお勧めできません。ただし、特定の範囲を念頭に置いている場合は、初期容量を設定するとメモリ効率が向上します。
これは、すべてのオブジェクトの再割り当てにかかる可能性のある作業を回避するためです。
int newCapacity = (oldCapacity * 3)/2 + 1;
内部的new Object[]
に作成されます。arraylistに要素を追加
するnew Object[]
とき、JVMは作成する労力を必要とします 。あなたが再割り当てのために(あなたが考えるすべてのALGO)のコードの上に持っていない場合は、あなたが呼び出すたびはarraylist.add()
その後、new Object[]
無意味である作成する必要があり、我々はそれぞれ追加されるすべてのオブジェクトに対して1によってサイズを大きくするための時間を失うされています。したがってObject[]
、次の式でサイズを大きくすることをお勧めします。
(JSLは毎回1ずつ増加するのではなく、動的に成長する配列リストに以下のフォーキャスト式を使用しました。成長するにはJVMによる労力がかかるため)
int newCapacity = (oldCapacity * 3)/2 + 1;
add
-既に内部的にいくつかの増加式を使用しています。したがって、質問には答えられません。
int newCapacity = (oldCapacity * 3)/2 + 1;
ArrayListクラスに存在します。それでも答えられないと思いますか?
ArrayList
償却後の再割り当てでは、初期容量の値がどのような場合でも発生します。そして問題は、初期容量に非標準の値を使用するのはなぜですか?これに加えて、「行の間を読む」ことは、技術的な回答では望ましいものではありません。;-)
それは最適化だと思います。初期容量のないArrayListには最大10行の空行があり、追加を実行すると展開されます。
正確な数のアイテムのリストを取得するには、trimToSize()を呼び出す必要があります
での私の経験によればArrayList
、初期容量を指定することは、再割り当てコストを回避する良い方法です。ただし、注意が必要です。上記のすべての提案は、要素数の大まかな見積もりがわかっている場合にのみ初期容量を提供する必要があると述べています。しかし、何も考えずに初期容量を指定しようとすると、予約済みのメモリと未使用のメモリの量は無駄になります。リストが必要な数の要素でいっぱいになると、必要になることはないからです。私が言っていることは、容量を割り当てるときに最初から実用的であり、実行時に必要な最小容量を知るスマートな方法を見つけることができるということです。ArrayListはと呼ばれるメソッドを提供しますensureCapacity(int minCapacity)
。しかし、その後、スマートな方法を見つけました...
initialCapacityを使用した場合と使用しない場合のArrayListをテストしましたが、驚くべき結果が得
られました。LOOP_NUMBERを100,000以下に設定すると、initialCapacityの設定が効率的です。
list1Sttop-list1Start = 14
list2Sttop-list2Start = 10
しかし、LOOP_NUMBERを1,000,000に設定すると、結果は次のように変わります。
list1Stop-list1Start = 40
list2Stop-list2Start = 66
最後に、それがどのように機能するのか理解できませんでした!?
サンプルコード:
public static final int LOOP_NUMBER = 100000;
public static void main(String[] args) {
long list1Start = System.currentTimeMillis();
List<Integer> list1 = new ArrayList();
for (int i = 0; i < LOOP_NUMBER; i++) {
list1.add(i);
}
long list1Stop = System.currentTimeMillis();
System.out.println("list1Stop-list1Start = " + String.valueOf(list1Stop - list1Start));
long list2Start = System.currentTimeMillis();
List<Integer> list2 = new ArrayList(LOOP_NUMBER);
for (int i = 0; i < LOOP_NUMBER; i++) {
list2.add(i);
}
long list2Stop = System.currentTimeMillis();
System.out.println("list2Stop-list2Start = " + String.valueOf(list2Stop - list2Start));
}
私はwindows8.1とjdk1.7.0_80でテストしました