Javaでシリアルにアクセスするには、何千もの文字列をメモリに保持する必要があります。それらを配列に格納するべきですか、それともある種のリストを使うべきですか?
配列では(リストとは異なり)すべてのデータが連続したメモリに保持されるため、配列を使用して何千もの文字列を格納すると問題が発生しますか。
どちらが速いかをテストするためにプロファイラーを使用することをお勧めします。
私の個人的な意見は、あなたはリストを使うべきだということです。
私は大規模なコードベースで作業しており、以前の開発者グループは配列を使用していましたどこでも。それはコードを非常に柔軟性のないものにしました。その大きな塊をリストに変更した後、私たちは速度に違いがないことに気づきました。
Javaの方法は、どのデータ抽象化があなたのニーズに最も適しているかを考えるべきです。 Javaでは、Listは抽象的であり、具体的なデータ型ではありません。文字列をListとして宣言してから、ArrayList実装を使用して初期化する必要があります。
List<String> strings = new ArrayList<String>();
抽象データ型と特定の実装のこの分離は、オブジェクト指向プログラミングの重要な側面の1つです。
ArrayListは、その基礎となる実装として配列を使用してList Abstract Data Typeを実装します。アクセス速度は実質的に配列と同じですが、Listに要素を追加したり削除したりできるという利点があります(ただし、これはArrayListを使用したO(n)操作です)。基本的な実装を後で変更することができます。たとえば、同期アクセスが必要なことに気付いた場合は、コードをすべて書き直すことなく実装をVectorに変更できます。
実際、ArrayListは、ほとんどの状況で低レベルの配列構成体を置き換えるように特に設計されています。もしJavaが今日設計されているのであれば、ArrayList構造のために配列が完全に除外されている可能性は十分にあります。
配列では(リストとは異なり)すべてのデータが連続したメモリに保持されるため、配列を使用して何千もの文字列を格納すると問題が発生しますか。
Javaでは、すべてのコレクションはオブジェクトへの参照のみを格納し、オブジェクト自体は格納しません。配列とArrayListはどちらも、連続する配列に数千の参照を格納するため、基本的に同じです。最新のハードウェアでは、数千の32ビット参照の連続したブロックがいつでもすぐに利用できると考えることができます。これは、あなたが完全にメモリ不足にならないことを保証するものではありません。もちろん、連続したメモリブロックの必要量をfufilするのは難しいことではありません。
あなたは配列よりジェネリック型を好むべきです。他の人が述べたように、配列は柔軟性がなく、ジェネリック型の表現力を持っていません。 (しかしながら、それらはランタイム型チェックをサポートします、しかしそれはジェネリック型とうまく混同しません。)
しかし、いつものように、最適化するときは、必ず次の手順に従ってください。
ArrayListを使用することを提案する答えはほとんどのシナリオで意味がありますが、相対的なパフォーマンスの実際の問題は実際には答えられていません。
配列を使ってできることがいくつかあります。
ArrayListではgetとsetの操作がやや遅くなりますが(私のマシンでは1呼び出しあたり1と3ナノ秒)、非集中的な使用のためにArrayListを使用することに対するオーバーヘッドはほとんどありませんただし、留意することがいくつかあります。
list.add(...)
を呼び出すとき)はコストがかかるため、可能であれば初期容量を適切なレベルに設定するようにしてください(配列を使用する場合も同じ問題が発生します)これは、標準のx86デスクトップマシンでJDK 7を使用して jmhベンチマークライブラリ (ナノ秒単位)を使用してこれら3つの操作について測定した結果です。結果が比較可能であることを確認するためにテストでArrayListのサイズが変更されることはありません。 ここで利用可能なベンチマークコード 。
私は4つのテストを実行し、以下のステートメントを実行しました。
Integer[] array = new Integer[1];
List<Integer> list = new ArrayList<> (1);
Integer[] array = new Integer[10000];
List<Integer> list = new ArrayList<> (10000);
結果(1コールあたりのナノ秒単位、95%の信頼度):
a.p.g.a.ArrayVsList.CreateArray1 [10.933, 11.097]
a.p.g.a.ArrayVsList.CreateList1 [10.799, 11.046]
a.p.g.a.ArrayVsList.CreateArray10000 [394.899, 404.034]
a.p.g.a.ArrayVsList.CreateList10000 [396.706, 401.266]
結論:目立った違いはありません。
私は2つのテストを実行し、以下のステートメントを実行しました。
return list.get(0);
return array[0];
結果(1コールあたりのナノ秒単位、95%の信頼度):
a.p.g.a.ArrayVsList.getArray [2.958, 2.984]
a.p.g.a.ArrayVsList.getList [3.841, 3.874]
結論:配列から取得する方がArrayListから取得するよりも約25%高速です。ただし、違いは1ナノ秒程度です。
私は2つのテストを実行し、以下のステートメントを実行しました。
list.set(0, value);
array[0] = value;
結果(1コールあたりのナノ秒単位):
a.p.g.a.ArrayVsList.setArray [4.201, 4.236]
a.p.g.a.ArrayVsList.setList [6.783, 6.877]
結論:配列に対する集合演算はリストよりも約40%高速ですが、getに関しては、各集合演算に数ナノ秒かかります。 1秒に達するための違いは、リスト/配列の項目を何億回も設定する必要があるでしょう。
ArrayListのコピーコンストラクタはArrays.copyOf
に委譲するので、パフォーマンスは配列のコピーと同じです(clone
、Arrays.copyOf
またはSystem.arrayCopy
を使用して配列をコピーする 重要なパフォーマンスの違いはありません )。
私は、オリジナルのポスターがC++/STLの背景から来ていることを推測しています。 C++ではstd::list
は二重リンクリストです。
Javaでは[Java.util.]List
はインプリメンテーションフリーのインタフェースです(C++用語では純粋な抽象クラスです)。 List
は二重リンクリストにすることができます - Java.util.LinkedList
が提供されています。しかし、新しいList
を作成したい場合は100回のうち99回、代わりにJava.util.ArrayList
を使用する必要があります。これはC++のstd::vector
とほぼ同じです。 Java.util.Collections.emptyList()
やJava.util.Arrays.asList()
が返すような、他の標準的な実装もあります。
パフォーマンスの観点からは、インターフェイスと余分なオブジェクトを通過しなければならないことによるヒットはごくわずかですが、実行時インライン展開では意味がありません。 String
は通常、オブジェクトと配列です。したがって、各エントリには、おそらく他に2つのオブジェクトがあります。 C++のstd::vector<std::string>
では、ポインタを使わずに値でコピーすることはできますが、文字配列は文字列のオブジェクトを形成します(そしてこれらは通常共有されません)。
このコードが本当にパフォーマンスに敏感な場合は、すべての文字列のすべての文字に対して1つのchar[]
配列(さらにはbyte[]
)を作成してから、オフセットの配列を作成することができます。 IIRC、これがjavacの実装方法です。
私は、ほとんどの場合、配列よりもArrayListsの柔軟性と優雅さを選ぶべきであることに同意します - そして、ほとんどの場合、プログラム性能への影響は無視できるでしょう。
しかし、たとえばソフトウェアグラフィックスレンダリングやカスタム仮想マシンのために、構造的な変更をほとんど加えずに一定の重い反復処理(追加や削除を行わない)を行っている場合、私のシーケンシャルアクセスベンチマークテストではArrayListsは1.5倍です。私のシステムでは配列よりも遅い(私の1年前のiMacではJava 1.6)。
いくつかのコード:
import Java.util.*;
public class ArrayVsArrayList {
static public void main( String[] args ) {
String[] array = new String[300];
ArrayList<String> list = new ArrayList<String>(300);
for (int i=0; i<300; ++i) {
if (Math.random() > 0.5) {
array[i] = "abc";
} else {
array[i] = "xyz";
}
list.add( array[i] );
}
int iterations = 100000000;
long start_ms;
int sum;
start_ms = System.currentTimeMillis();
sum = 0;
for (int i=0; i<iterations; ++i) {
for (int j=0; j<300; ++j) sum += array[j].length();
}
System.out.println( (System.currentTimeMillis() - start_ms) + " ms (array)" );
// Prints ~13,500 ms on my system
start_ms = System.currentTimeMillis();
sum = 0;
for (int i=0; i<iterations; ++i) {
for (int j=0; j<300; ++j) sum += list.get(j).length();
}
System.out.println( (System.currentTimeMillis() - start_ms) + " ms (ArrayList)" );
// Prints ~20,800 ms on my system - about 1.5x slower than direct array access
}
}
最初に明確にする価値があるのは、古典的なcomp sciデータ構造の意味での「リスト」(つまりリンクリスト)を意味するのか、それともJava.util.Listを意味するのか?あなたがJava.util.Listを意味するなら、それはインターフェースです。もしあなたが配列を使いたいのであれば、単にArrayListの実装を使うだけで、配列のような振る舞いと意味が得られます。問題が解決しました。
あなたが配列対リンクリストを意味するならば、それは我々がBig Oに戻る少し異なる議論である(これがなじみのない用語であるならば、これは 平易な英語の説明 である)。
アレイ;
リンクリスト:
そのため、配列のサイズを変更する方法に最も適したものを選択します。サイズを変更したり、たくさん挿入したり削除したりする場合は、リンクリストを使用することをお勧めします。ランダムアクセスがまれな場合も同様です。あなたはシリアルアクセスに言及します。主にシリアルアクセスをほとんど変更せずに行っているのであれば、どちらを選んでもかまいません。
あなたが言っているように、あなたはメモリの潜在的に隣接していないブロックと次の要素への(効果的に)ポインタを扱っているので、リンクリストはわずかにより高いオーバーヘッドを持ちます。ただし、何百万ものエントリを扱っていない限り、これはおそらく重要な要素ではありません。
私は、ArrayListsとArraysを比較するための小さなベンチマークを書きました。私の昔風のラップトップでは、5000要素の配列リストをたどる時間は1000倍で、同等の配列コードよりも約10ミリ秒遅くなりました。
ですから、リストを繰り返す以外に何もしていなくて、たくさんしているのであれば、多分最適化する価値があります。それ以外の場合は、リストを使用します。コードを最適化する必要がある場合は、doの方が簡単になります。
n.b Ididは、for String s: stringsList
を使用する方が古い形式のforループを使用してリストにアクセスするよりも約50%遅くなることに注意してください。図を行く...これが私が時間を計った2つの関数です。配列とリストは5000個のランダムな(異なる)文字列で埋められました。
private static void readArray(String[] strings) {
long totalchars = 0;
for (int j = 0; j < ITERATIONS; j++) {
totalchars = 0;
for (int i = 0; i < strings.length; i++) {
totalchars += strings[i].length();
}
}
}
private static void readArrayList(List<String> stringsList) {
long totalchars = 0;
for (int j = 0; j < ITERATIONS; j++) {
totalchars = 0;
for (int i = 0; i < stringsList.size(); i++) {
totalchars += stringsList.get(i).length();
}
}
}
いいえ、技術的には、配列は文字列への参照のみを格納するためです。文字列自体は別の場所に割り当てられています。 1000アイテムの場合は、リストの方が良いと思いますが、遅くなりますが、柔軟性が増し、特にサイズを変更する場合は使いやすくなります。
あなたが数千人いる場合は、トライを使用することを検討してください。トライは、格納された文字列の共通のプレフィックスをマージするツリーのような構造です。
たとえば、文字列が
intern
international
internationalize
internet
internets
トライは保存するでしょう:
intern
-> \0
international
-> \0
-> ize\0
net
->\0
->s\0
文字列の格納には57文字(nullターミネータ '\ 0'を含む)と、それを保持するStringオブジェクトのサイズに関係なく必要です。 (実際には、おそらくすべてのサイズを16の倍数に切り上げるべきですが、それを...)おおよそ57 + 5 = 62バイトと呼びます。
トライには29(nullターミネータ '\ 0'を含む)と、トライノードのサイズ(配列への参照および子トライノードのリスト)が必要です。
この例では、おそらく同じことが起こります。何千もの人にとっては、一般的なプレフィックスがある限り、おそらくあまり出てきません。
さて、他のコードでトライを使用するときは、おそらくStringBufferを仲介者として使用して、Stringに変換する必要があります。トライの外で、多くのストリングが一度にストリングとして使用されていると、それは損失です。
しかし、たとえば辞書で物事を調べるために、その時に使用しているのがほんの少しの場合、トライを使用するとスペースを大幅に節約できます。 HashSetに格納するよりも明らかに少ないスペースです。
あなたはあなたがそれらを「連続的に」アクセスしていると言います - それがアルファベット順に連続していることを意味するなら、トライを深さ優先で繰り返すなら明らかに無料であなたにアルファベット順を与えます。
ここにはすでにたくさんの良い答えがありますので、実用的な見方についての他の情報、つまり挿入と繰り返しのパフォーマンスの比較を示したいと思います。
これは実際の簡単なパフォーマンスチェックです。
したがって、結果はマシンのパフォーマンスによって異なります。
これに使用されるソースコードは以下の通りです。
import Java.util.Iterator;
import Java.util.LinkedList;
public class Array_vs_LinkedList {
private final static int MAX_SIZE = 40000000;
public static void main(String[] args) {
LinkedList lList = new LinkedList();
/* insertion performance check */
long startTime = System.currentTimeMillis();
for (int i=0; i<MAX_SIZE; i++) {
lList.add(i);
}
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println("[Insert]LinkedList insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
int[] arr = new int[MAX_SIZE];
startTime = System.currentTimeMillis();
for(int i=0; i<MAX_SIZE; i++){
arr[i] = i;
}
stopTime = System.currentTimeMillis();
elapsedTime = stopTime - startTime;
System.out.println("[Insert]Array Insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
/* iteration performance check */
startTime = System.currentTimeMillis();
Iterator itr = lList.iterator();
while(itr.hasNext()) {
itr.next();
// System.out.println("Linked list running : " + itr.next());
}
stopTime = System.currentTimeMillis();
elapsedTime = stopTime - startTime;
System.out.println("[Loop]LinkedList iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
startTime = System.currentTimeMillis();
int t = 0;
for (int i=0; i < MAX_SIZE; i++) {
t = arr[i];
// System.out.println("array running : " + i);
}
stopTime = System.currentTimeMillis();
elapsedTime = stopTime - startTime;
System.out.println("[Loop]Array iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
}
}
パフォーマンス結果は以下の通りです:
更新:
Markが指摘したように、JVMがウォームアップされた後に大きな違いはありません(いくつかのテストパス)。再作成された配列、または行列の新しい行から始まる新しいパスでチェックされています。大きな確率で、これはインデックスアクセスを持つ単純な配列に署名することはコレクションのために使われるべきではありません。
それでも最初の1-2パスの単純な配列は2-3倍高速です。
元の投稿:
確認するにはあまりにも多くの件名のための言葉。 何の質問もなく、配列はどのクラスコンテナよりも数倍速い。私は私のパフォーマンスクリティカルなセクションのための代替案を探してこの質問を実行します。これが私が実際の状況をチェックするために作ったプロトタイプコードです:
import Java.util.List;
import Java.util.Arrays;
public class IterationTest {
private static final long MAX_ITERATIONS = 1000000000;
public static void main(String [] args) {
Integer [] array = {1, 5, 3, 5};
List<Integer> list = Arrays.asList(array);
long start = System.currentTimeMillis();
int test_sum = 0;
for (int i = 0; i < MAX_ITERATIONS; ++i) {
// for (int e : array) {
for (int e : list) {
test_sum += e;
}
}
long stop = System.currentTimeMillis();
long ms = (stop - start);
System.out.println("Time: " + ms);
}
}
そして、これが答えです。
配列に基づいて(16行目がアクティブ)
Time: 7064
リストに基づいて(17行目がアクティブ):
Time: 20950
「もっと速い」についてもうコメントがありますか?これはかなり理解されています。問題は、Listの柔軟性よりも約3倍速いほうが良い場合です。しかし、これはまた別の問題です。ちなみに私もこれを手作業で作成したArrayList
に基づいてチェックしました。ほぼ同じ結果です。
文字列オブジェクトを格納する場合、配列とリストの選択はそれほど重要ではありません(パフォーマンスを考慮して)。配列とリストの両方が、実際のオブジェクトではなく、文字列オブジェクト参照を格納するためです。
ArrayListは配列をカプセル化するので、プリミティブ配列を使用するのと比べてほとんど違いはありません(ただし、ListのほうがJavaで作業する方がはるかに簡単です)。
ArrayListよりも配列を優先するのが理にかなっているのは、バイト、intなどのプリミティブを格納しているときで、プリミティブ配列を使用することによって得られる特定のスペース効率が必要なときだけです。
データの大きさが事前にわかっていれば、配列のほうが速くなります。
リストはより柔軟です。あなたは、配列によって支えられているArrayListを使うことができます。
効率が必要な場合は配列を使用してください。柔軟性が必要な場合はlistを使用してください。
あなたが固定サイズで生きることができるならば、配列はより速くなり、より少ないメモリを必要とするでしょう。
要素を追加したり削除したりすることでListインタフェースの柔軟性が必要な場合は、どちらの実装を選択すべきかという問題が残ります。多くの場合、ArrayListが推奨され使用されますが、リストの先頭または中央にある要素を削除または挿入する必要がある場合も、ArrayListのパフォーマンスに問題があります。
そのため、GapListを紹介する http://Java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list をご覧になることをお勧めします。この新しいリストの実装は、ArrayListとLinkedListの両方の長所を兼ね備え、ほぼすべての操作で非常に優れたパフォーマンスを発揮します。
私が興味を持っていたという情報は、答えのどれにもありませんでした - 何度も同じ配列を繰り返しスキャンしました。このためにJMHテストを作成しなければなりませんでした。
結果(Java 1.8.0_66 x 32、プレーン配列の繰り返し処理は、ArrayListよりも少なくとも5倍高速です)。
Benchmark Mode Cnt Score Error Units
MyBenchmark.testArrayForGet avgt 10 8.121 ? 0.233 ms/op
MyBenchmark.testListForGet avgt 10 37.416 ? 0.094 ms/op
MyBenchmark.testListForEach avgt 10 75.674 ? 1.897 ms/op
テスト
package my.jmh.test;
import Java.util.ArrayList;
import Java.util.List;
import Java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MyBenchmark {
public final static int ARR_SIZE = 100;
public final static int ITER_COUNT = 100000;
String arr[] = new String[ARR_SIZE];
List<String> list = new ArrayList<>(ARR_SIZE);
public MyBenchmark() {
for( int i = 0; i < ARR_SIZE; i++ ) {
list.add(null);
}
}
@Benchmark
public void testListForEach() {
int count = 0;
for( int i = 0; i < ITER_COUNT; i++ ) {
for( String str : list ) {
if( str != null )
count++;
}
}
if( count > 0 )
System.out.print(count);
}
@Benchmark
public void testListForGet() {
int count = 0;
for( int i = 0; i < ITER_COUNT; i++ ) {
for( int j = 0; j < ARR_SIZE; j++ ) {
if( list.get(j) != null )
count++;
}
}
if( count > 0 )
System.out.print(count);
}
@Benchmark
public void testArrayForGet() {
int count = 0;
for( int i = 0; i < ITER_COUNT; i++ ) {
for( int j = 0; j < ARR_SIZE; j++ ) {
if( arr[j] != null )
count++;
}
}
if( count > 0 )
System.out.print(count);
}
}
リストは総称を使用できるため、Java 1.5以降で推奨される方法です。配列は総称を持つことはできません。また、配列には事前に定義された長さがあり、これを動的に増やすことはできません。大きなサイズの配列を初期化することはお勧めできません。 ArrayListは、総称を使用して配列を宣言する方法であり、動的に拡張できます。しかし、削除と挿入がより頻繁に使用される場合は、リンクリストが使用される最も速いデータ構造です。
特に項目数やサイズが変わらないことがわかっている場合は、配列の代わりに配列を使用することをお勧めします。
Oracle Javaのベストプラクティスを参照してください。 http://docs.Oracle.com/cd/A97688_16/generic.903/bp/Java.htm#1007056
もちろん、コレクションにオブジェクトを追加したり削除したりする必要がある場合は、何度も簡単に使用できます。
「数千」はそれほど多くはありません。数千段落長の文字列のサイズは、数メガバイト程度です。あなたがしたいことがこれらをシリアルにアクセスすることだけであるならば、 不変の片方向リンクリスト を使ってください。
配列の上でリストを使用することによるパフォーマンスへの影響をよりよく感じるために、ここに来ました。私のシナリオでは、ここではコードを修正しなければなりませんでした。大部分はゲッターを使用した〜1000インチの配列/リスト、つまりarray [j]とlist.get(j)の組み合わせです。
それについて非科学的であるために7のベストを取ると(2.5倍遅いリストと最初のいくつか)私はこれを得る:
array Integer[] best 643ms iterator
ArrayList<Integer> best 1014ms iterator
array Integer[] best 635ms getter
ArrayList<Integer> best 891ms getter (strange though)
- つまり、配列を使うと約30%速くなります
投稿する2番目の理由は、入れ子になったループを使って数学/行列/シミュレーション/最適化のコードを実行した場合、その影響についてだれも言及していないことです。
3つのネストレベルがあり、内側のループが8倍のパフォーマンスヒットを見ているのに比べて2倍遅いとします。一日で実行されるものは今1週間かかります。
* EDITかなり衝撃を受けました。キックのために整数[1000]ではなくint [1000]を宣言してみました。
array int[] best 299ms iterator
array int[] best 296ms getter
Integer []とint []を使用すると、パフォーマンスが2倍になります。イテレータを使用したListArrayは、int []よりも3倍遅くなります。本当にJavaのリスト実装はネイティブ配列に似ていると思っていました...
参照用コード(複数回呼び出す):
public static void testArray()
{
final long MAX_ITERATIONS = 1000000;
final int MAX_LENGTH = 1000;
Random r = new Random();
//Integer[] array = new Integer[MAX_LENGTH];
int[] array = new int[MAX_LENGTH];
List<Integer> list = new ArrayList<Integer>()
{{
for (int i = 0; i < MAX_LENGTH; ++i)
{
int val = r.nextInt();
add(val);
array[i] = val;
}
}};
long start = System.currentTimeMillis();
int test_sum = 0;
for (int i = 0; i < MAX_ITERATIONS; ++i)
{
// for (int e : array)
// for (int e : list)
for (int j = 0; j < MAX_LENGTH; ++j)
{
int e = array[j];
// int e = list.get(j);
test_sum += e;
}
}
long stop = System.currentTimeMillis();
long ms = (stop - start);
System.out.println("Time: " + ms);
}
実装次第です。プリミティブ型の配列がArrayListよりも小さく効率的になる可能性があります。これは、最も単純なArrayList実装では各値へのポインタが格納されるのに対し、配列は連続したメモリブロックに値を直接格納するためです。特に64ビットプラットフォームでは、これは大きな違いを生む可能性があります。
もちろん、jvmの実装がこの状況に対して特別なケースを持つことも可能です。その場合、パフォーマンスは同じになります。
適切なベンチマークを行わずに最適化の罠に陥らないでください。他の人たちが示唆するように、どんな仮定をする前にプロファイラーを使うこと。
列挙したさまざまなデータ構造にはさまざまな目的があります。リストは最初と最後に要素を挿入するのに非常に効率的ですが、ランダムな要素にアクセスするときには多くの問題があります。配列には固定記憶域がありますが、高速ランダムアクセスを提供します。最後に、ArrayListは配列を大きくすることで配列へのインタフェースを改善します。通常、使用されるデータ構造は、格納されたデータへのアクセス方法または追加方法によって決まります。
メモリ消費量についてあなたはいくつかのことを混ぜているようです。配列はあなたが持っているデータのタイプのためのメモリの連続的な塊をあなたに与えるだけです。 Javaには固定データ型があることを忘れないでください。ブール値、char、int、long、float、およびObject(これにはすべてのオブジェクトが含まれ、配列でもObjectです)。つまり、Stringの文字列[1000]またはMyObject myObjects [1000]の配列を宣言すると、オブジェクトの場所(参照またはポインタ)を格納するのに十分な大きさの1000個のメモリボックスしか得られません。あなたは、オブジェクトのサイズに合うのに十分な大きさの1000個のメモリボックスを持っていません。あなたのオブジェクトが最初に "new"で作成されることを忘れないでください。これは、メモリ割り当てが行われ、後で参照(それらのメモリアドレス)が配列に格納されるときです。オブジェクトは参照のみで配列にコピーされません。
配列は高速です - すべてのメモリは事前に事前に割り当てられています。
私はそれが文字列にとって本当の違いを生むとは思わない。文字列の配列で連続しているのは文字列への参照です。文字列自体はメモリ内のランダムな場所に格納されます。
配列とリストは、オブジェクトではなくプリミティブ型に影響を与える可能性があります。 IFあなたはあらかじめ要素の数を知っていますが、柔軟性を必要としません。数百万の整数や倍精度の配列は、リストよりもメモリの効率が良く、速度もわずかですそれらは連続して保存され、即座にアクセスされます。 Javaが文字列にcharの配列、画像データにintの配列などをまだ使用しているのはそのためです。
ここに示されている多くのマイクロベンチマークは、array/ArrayListの読み取りなどのために数ナノ秒の数を見つけました。すべてがL1キャッシュにある場合、これは非常に合理的です。
より高いレベルのキャッシュまたはメインメモリアクセスは、10nS-100nSのような何倍もの大きさの時間があるのに対して、L1キャッシュでは1nSのようなものです。 ArrayListにアクセスすると、余分なメモリの間接化が発生します。実際のアプリケーションでは、アクセスの合間にコードが何をしているかに応じて、このコストをほぼ常に支払うことができます。そしてもちろん、あなたがたくさんの小さなArrayListsを持っているなら、これはあなたのメモリ使用量を増加させるかもしれず、そしてそれはあなたがキャッシュミスを起こす可能性をより高くするでしょう。
オリジナルのポスターは1つだけを使用して短時間で多くのコンテンツにアクセスしているように見えるので、それは大きな困難ではないはずです。しかし、それは他の人々にとっては異なるかもしれません、そしてマイクロベンチマークを解釈するときあなたは注意するべきです。
ただし、Javaの文字列は、特に小さな文字列を多数格納する場合は、明らかに無駄になります(メモリアナライザで見れば、数文字の文字列では60バイトを超えるようです)。文字列の配列は、Stringオブジェクトへの間接参照と、Stringオブジェクトから文字列自体を含むchar []への間接参照を持ちます。何かがあなたのL1キャッシュを爆破しようとしているなら、それはこれであり、何千または何万もの文字列と組み合わされています。ですから、もしあなたが本気で - 本当に真剣に - 可能な限り多くのパフォーマンスを削り取ることを考えているなら、あなたはそれを違うやり方で行うのを見ることができます。たとえば、2つの配列を保持することができます。すべての文字列を次々に含むchar []と、開始位置へのオフセットを持つint []です。これは何かをするPITAになるでしょう、そしてあなたはほとんど確実にそれを必要としません。あなたがそうするならば、あなたは間違った言語を選びました。
ArrayListは内部的に配列オブジェクトを使って要素を追加(または格納)します。つまり、ArrayListはArray data -structureによってサポートされています。ArrayListの配列はサイズ変更可能(または動的)です。
ArrayはArrayより速い ArrayListは内部的にarrayを使用しているので。 ArrayListを介して直接Array内の要素を追加し、間接的にArray内の要素を追加することができる場合は、常に直接機構が間接機構よりも高速です。
ArrayListクラスには2つのオーバーロードされたadd()メソッドがあります。
1。 add(Object)
:リストの末尾にオブジェクトを追加します。
2。 add(int index , Object )
:指定されたオブジェクトをリストの指定された位置に挿入します。
ArrayListのサイズはどのように動的に大きくなりますか?
public boolean add(E e)
{
ensureCapacity(size+1);
elementData[size++] = e;
return true;
}
上記のコードから注意すべき重要な点は、要素を追加する前に、ArrayListの容量をチェックしているということです。 confirmCapacity()は、現在使用されている要素のサイズと、配列の最大サイズを決定します。塗りつぶされた要素のサイズ(ArrayListクラスに追加される新しい要素を含む)が配列の最大サイズよりも大きい場合は、arrayのサイズを大きくします。しかし、配列のサイズを動的に増やすことはできません。内部で起こることは新しいArrayが容量を持って作成されるということです
Java 6まで
int newCapacity = (oldCapacity * 3)/2 + 1;
(アップデート)Java 7以降
int newCapacity = oldCapacity + (oldCapacity >> 1);
また、古い配列のデータが新しい配列にコピーされます。
ArrayListにオーバーヘッドメソッドを含めることが、ArrayがArrayList
よりも速い理由です。
配列 - 結果のより速い取得を達成しなければならないときはいつもより良いでしょう
Lists- O(1)でできるので挿入と削除の結果を実行します。これはデータを簡単に追加、取得、削除するためのメソッドも提供します。はるかに使いやすいです。
ただし、データが格納されている配列内のインデックス位置がわかっている場合は、データのフェッチが速くなることを常に覚えておいてください。
これは配列をソートすることによってうまく達成できます。それ故、これはデータをフェッチするための時間を増加させる(すなわち、データを記憶すること+データを分類すること+データが見つけられる位置を探すこと)。したがって、データをより早くフェッチするのに長けていても、これによって配列からデータをフェッチするための追加の待ち時間が長くなります。
したがって、これは3つのデータ構造または3つのデータ構造で解決できます。上述のように、トライデータ構造は、データの検索において非常に効率的であり、特定のワードの検索は、O(1)の大きさで行うことができる。時間が重要なときデータをすばやく検索および取得する必要がある場合は、データ構造を試してください。
メモリスペースの消費量を減らし、パフォーマンスを向上させたい場合は、3値データ構造を使用してください。どちらも膨大な数の文字列を格納するのに適しています(例:辞書に含まれる単語のように)。
アクセス方法によって異なります。
格納後、ほとんど挿入操作または削除操作を行わずに検索操作を主に行いたい場合は、(配列内のO(1)で検索が行われるため、追加または削除が必要になる場合があります)要素の順序付け).
保存した後、あなたの主な目的がほとんどまたは全く検索操作なしで、文字列を追加/削除することであれば、それからリストに行きます。