Javaでシリアルにアクセスするには、何千もの文字列をメモリに保持する必要があります。それらを配列に格納する必要がありますか、それとも何らかのリストを使用する必要がありますか?
配列は(Listsとは異なり)連続したメモリのチャンクにすべてのデータを保持するため、配列を使用して数千の文字列を格納すると問題が発生しますか?
Javaでシリアルにアクセスするには、何千もの文字列をメモリに保持する必要があります。それらを配列に格納する必要がありますか、それとも何らかのリストを使用する必要がありますか?
配列は(Listsとは異なり)連続したメモリのチャンクにすべてのデータを保持するため、配列を使用して数千の文字列を格納すると問題が発生しますか?
回答:
プロファイラーを使用して、どちらが速いかをテストすることをお勧めします。
私の個人的な意見では、リストを使用する必要があります。
私は大規模なコードベースで作業しており、以前の開発者グループはあらゆる場所で配列を使用していました。それはコードを非常に柔軟性のないものにしました。その大きなチャンクをリストに変更した後、速度に違いがないことに気付きました。
Javaの方法では、ニーズに最も適したデータ抽象化を検討する必要があります。Javaでは、リストは抽象であり、具象データ型ではないことに注意してください。文字列をリストとして宣言し、ArrayList実装を使用して初期化する必要があります。
List<String> strings = new ArrayList<String>();
この抽象データ型と特定の実装の分離は、オブジェクト指向プログラミングの重要な側面の1つです。
ArrayListは、基になる実装として配列を使用して、List Abstract Data Typeを実装します。アクセス速度は実質的に配列と同じですが、リストに要素を追加および減算できるという追加の利点があります(これはArrayListを使用したO(n)操作です)。これは、後で基礎となる実装を変更する場合あなたはできる。たとえば、同期化されたアクセスが必要であるとわかった場合、すべてのコードを書き直すことなく、実装をベクターに変更できます。
実際、ArrayListは、ほとんどのコンテキストで低レベルの配列構成を置き換えるように特別に設計されています。今日Javaが設計されていたとしたら、ArrayList構造が優先されて配列が完全に除外されていた可能性は十分にあります。
配列は(Listsとは異なり)連続したメモリのチャンクにすべてのデータを保持するため、配列を使用して数千の文字列を格納すると問題が発生しますか?
Javaでは、すべてのコレクションにオブジェクトへの参照のみが格納され、オブジェクト自体は格納されません。配列とArrayListはどちらも数千の参照を連続した配列に格納するため、基本的には同じです。最近のハードウェアでは、数千の32ビット参照の連続したブロックが常にすぐに利用できると考えることができます。もちろん、これはメモリが完全に不足しないことを保証するものではありません。もちろん、メモリ要件の連続したブロックを実行することが難しくないということだけです。
ArrayListの使用を提案する答えはほとんどのシナリオで意味がありますが、相対的なパフォーマンスの実際の問題は実際には答えられていません。
配列でできることはいくつかあります。
ArrayListではgetおよびset操作が多少遅くなりますが(私のマシンでは、呼び出しごとに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]
結論:配列からの取得は約25%高速です取得は、ArrayListからの取得もですが、違いは1ナノ秒程度です。
2つのテストを実行し、次のステートメントを実行しました。
list.set(0, value);
array[0] = value;
結果(呼び出しあたりのナノ秒):
a.p.g.a.ArrayVsList.setArray [4.201, 4.236]
a.p.g.a.ArrayVsList.setList [6.783, 6.877]
結論:配列のセット操作はリストよりも約40%高速ですが、取得に関しては、各セット操作に数ナノ秒かかるため、差が1秒に達するには、リスト/配列のアイテムを数百個設定する必要があります何百万回も!
ArrayListのコピーコンストラクタ委譲Arrays.copyOf
のパフォーマンスは、配列のコピーと同じになるように(経由配列をコピーするclone
、Arrays.copyOf
またはSystem.arrayCopy
重大な違い性能面を行うものではありません)。
配列よりもジェネリック型を優先する必要があります。他の人が述べたように、配列には柔軟性がなく、ジェネリック型の表現力はありません。(ただし、ランタイム型チェックはサポートしていますが、ジェネリック型との混同が激しいです。)
しかし、いつものように、最適化するときは常に次の手順に従ってください。
元のポスターがC ++ / STLの背景から来ているため、混乱が生じていると思います。C ++ではstd::list
、二重にリンクされたリストです。
Java [java.util.]List
では、実装不要のインターフェース(C ++用語では純粋な抽象クラス)です。List
二重にリンクされたリストにすることができます- java.util.LinkedList
提供されています。ただし、新しいを作成する場合は100のうち99倍List
を使用します。java.util.ArrayList
代わりに使用しますstd::vector
。これはC ++にほぼ相当します。java.util.Collections.emptyList()
およびによって返されるものなど、他の標準実装がありますjava.util.Arrays.asList()
。
パフォーマンスの観点からは、インターフェイスと追加のオブジェクトを経由する必要があることによる影響はごくわずかですが、ランタイムのインライン化は、これがほとんど意味を持たないことを意味します。また、String
通常はオブジェクトと配列であることに注意してください。したがって、各エントリには、おそらく他に2つのオブジェクトがあります。C ++ではstd::vector<std::string>
、ポインタを使用せずに値でコピーしますが、文字配列は文字列のオブジェクトを形成します(これらは通常は共有されません)。
この特定のコードが本当にパフォーマンスに敏感な場合、すべての文字列のすべての文字に対して単一の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ミリ秒遅かった。
だから、あなたは何もしないが、リストを反復し、あなたはそれをたくさんやっているし、している場合、多分それの価値の最適化。あなたはときに、それが容易になりますので、それ以外の場合、私は、リストを使用したい行うコードを最適化する必要性を。
NB私がやった使用はという通知for String s: stringsList
遅くリストにアクセスするためにループ古いスタイルを使用するよりも約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();
}
}
}
char[]
これには触れません(これはCではありません)。
いいえ、技術的には、配列は文字列への参照のみを格納するためです。文字列自体は別の場所に割り当てられます。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(ヌルターミネーター '\ 0'を含む)とトライノードのサイズが必要です。これは配列への参照と子トライノードのリストです。
この例では、おそらく同じことが出てきます。何千もの場合、共通の接頭辞がある限り、おそらくそれは少なくなります。
さて、他のコードでトライを使用するとき、おそらく仲介としてStringBufferを使用して、Stringに変換する必要があります。多くの文字列が文字列として一度に使用されている場合、トライの外では、それは損失です。
しかし、一度に少数しか使用していない場合(たとえば、辞書で物事を調べる場合)、トライを使用すると多くのスペースを節約できます。HashSetに格納するよりも明らかに少ないスペース。
あなたはそれらを「シリアルに」アクセスしていると言います-それがアルファベット順に連続することを意味する場合、トライは深さ優先で反復すれば明らかに無料でアルファベット順も提供します。
更新:
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
。ほぼ同じ結果。
3
倍の速さは真ですが、重要ではありません。14ms
長い時間ではありません
ここにはすでに良い答えがたくさんあるので、挿入と反復のパフォーマンスの比較である実用的な見方の他の情報をいくつか提供したいと思います。プリミティブ配列とJavaのリンクリストです。
これは実際の単純なパフォーマンスチェックです。
したがって、結果はマシンのパフォーマンスに依存します。
これに使用されるソースコードは以下のとおりです。
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.");
}
}
パフォーマンス結果は以下のとおりです。
配列よりもリストを使用した場合のパフォーマンスへの影響をよく理解するためにここに来ました。私はここで私のシナリオに合わせてコードを適応させる必要がありました。配列/リストは、主にゲッターを使用して、配列[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日で実行されるものは、今では1週間かかります。
* EDITかなりショックを受けました。キックのために、Integer [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を使用できます。
固定サイズで使用できる場合、配列はより高速になり、必要なメモリも少なくなります。
要素を追加および削除するListインターフェースの柔軟性が必要な場合は、どの実装を選択するかという問題が残ります。多くの場合、ArrayListが推奨され、どのような場合にも使用されますが、リストの最初または途中の要素を削除または挿入する必要がある場合、ArrayListにもパフォーマンスの問題があります。
したがって、GapListを紹介するhttp://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-listを確認することをお勧めします。この新しいリストの実装は、ArrayListとLinkedListの両方の長所を組み合わせており、ほぼすべての操作で非常に優れたパフォーマンスを実現します。
特に、アイテムの数とサイズが変化しないことがわかっている場合は、リストの代わりにそれらを使用できるすべての場所で配列を推奨します。
Oracle Javaのベストプラクティスを参照してください:http : //docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056
もちろん、コレクションにオブジェクトを何度も追加したり削除したりする必要がある場合は、使いやすいリストを使用してください。
回答のどれにも、私が興味を持った情報がありませんでした-同じアレイを何度も繰り返しスキャンすることです。このためのJMHテストを作成する必要がありました。
結果(Java 1.8.0_66 x32、プレーン配列の反復は、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);
}
}
「千」は大きな数ではありません。数千の段落長の文字列は、数メガバイトのサイズです。これらをシリアルにアクセスするだけの場合は、不変の単一リンクリストを使用します。
適切なベンチマークなしに最適化の罠に陥らないでください。他の人が提案するように、仮定を行う前にプロファイラーを使用します。
列挙したさまざまなデータ構造には、さまざまな目的があります。リストは、最初と最後に要素を挿入するのに非常に効率的ですが、ランダムな要素にアクセスする場合、多くの問題があります。アレイは固定ストレージを備えていますが、高速ランダムアクセスを提供します。最後に、ArrayListは配列を拡張できるようにすることで、配列へのインターフェースを改善します。通常、使用されるデータ構造は、格納されたデータへのアクセスまたは追加方法によって決定されます。
メモリ消費について。あなたはいくつかのものを混ぜているようです。配列は、あなたが持っているデータのタイプのための連続したメモリのチャンクを与えるだけです。javaには固定のデータ型があることを忘れないでください:boolean、char、int、long、float、およびObject(これにはすべてのオブジェクトが含まれますが、配列もオブジェクトです)。つまり、文字列の配列[1000]またはMyObject myObjects [1000]を宣言した場合、オブジェクトの場所(参照またはポインタ)を格納するのに十分な大きさの1000個のメモリボックスしか取得できません。オブジェクトのサイズに合わせるのに十分な大きさの1000個のメモリボックスはありません。オブジェクトが最初に「新規」で作成されることを忘れないでください。これは、メモリ割り当てが行われ、後で参照(そのメモリアドレス)が配列に格納される場合です。オブジェクトは参照のみで配列にコピーされません。
ここに記載されている多くのマイクロベンチマークでは、array / ArrayListの読み取りなどの数ナノ秒の数値が見つかりました。すべてがL1キャッシュにある場合、これはかなり合理的です。
より高いレベルのキャッシュまたはメインメモリアクセスは、10nS-100nSのような桁数の時間を持つことができますが、L1キャッシュの場合は1nSのようになります。ArrayListへのアクセスには追加のメモリ間接があり、実際のアプリケーションでは、アクセス間でコードが何をしているかに応じて、このコストをほとんど発生しない場合から毎回支払う場合があります。そしてもちろん、小さなArrayListがたくさんある場合、これはメモリ使用量に追加され、キャッシュミスが発生する可能性が高くなります。
元のポスターは1つだけを使用しており、短時間で多くのコンテンツにアクセスしているように見えるので、大きな苦労はないはずです。しかし、それは他の人々とは異なる可能性があるため、マイクロベンチマークを解釈するときには注意が必要です。
ただし、Java文字列は、特に多くの小さな文字列を格納する場合は、恐ろしいほど無駄になります(メモリアナライザーで文字列を見ると、数文字の文字列では60バイトを超えるようです)。文字列の配列には、Stringオブジェクトへの間接指定と、Stringオブジェクトから文字列自体を含むchar []への間接指定があります。L1キャッシュを破壊するものがある場合、これは数千または数万の文字列と組み合わされたものです。だから、もしあなたが真剣に-本当に真剣に-可能な限り多くのパフォーマンスを削り出すことについてなら、あなたは違うやり方でそれを見ることができるでしょう。たとえば、2つの配列、つまりすべての文字列が次々に含まれるchar []と、開始位置へのオフセットを持つint []を保持できます。これは何かをするためのPITAであり、ほぼ間違いなく必要ありません。そして、あなたがそうするなら、あなたは
ArrayListは内部的に配列オブジェクトを使用して要素を追加(または格納)します。つまり、ArrayListはArray data -structureによってサポートされています。ArrayListの配列はサイズ変更可能(または動的)です。
配列は配列より速いArrayListは内部的に配列を使用するためです。Arrayに直接要素を追加でき、ArrayListを介して間接的にArrayに要素を追加できる場合、常に直接メカニズムは間接メカニズムより高速です。
ArrayListクラスに2つのオーバーロードされたアドオン()メソッドがあります:
1 add(Object)
:リストの末尾にオブジェクトを追加します。
2 add(int index , Object )
.:リストの指定された位置に指定されたオブジェクトを挿入します。
ArrayListのサイズはどのように動的に大きくなりますか?
public boolean add(E e)
{
ensureCapacity(size+1);
elementData[size++] = e;
return true;
}
上記のコードで注意すべき重要な点は、要素を追加する前に、ArrayListの容量をチェックしていることです。EnsureCapacity()は、占有されている要素の現在のサイズと配列の最大サイズを決定します。満たされた要素(ArrayListクラスに追加される新しい要素を含む)のサイズが配列の最大サイズより大きい場合は、配列のサイズを増やします。ただし、配列のサイズを動的に増やすことはできません。内部で何が起こるかというと、新しいアレイが容量で作成されます
Java 6まで
int newCapacity = (oldCapacity * 3)/2 + 1;
(更新)Java 7から
int newCapacity = oldCapacity + (oldCapacity >> 1);
また、古い配列のデータが新しい配列にコピーされます。
ArrayListにオーバーヘッドメソッドがあるため、Arrayはよりも高速ですArrayList
。
配列-結果のフェッチを高速化する必要がある場合は常に優れています
リスト-O(1)で実行できるため、挿入と削除の結果を実行し、データを簡単に追加、フェッチ、および削除するメソッドも提供します。はるかに使いやすい。
ただし、データが格納されている配列内のインデックス位置がわかっている場合は、データのフェッチが高速であることを常に覚えておいてください。
これは、配列をソートすることで十分に達成できます。したがって、これによりデータをフェッチする時間が増加します(つまり、データの保存+データのソート+データが見つかった位置のシーク)。したがって、データをより早くフェッチできる場合でも、アレイからデータをフェッチするためのレイテンシが増加します。
したがって、これはトライデータ構造または3値データ構造で解決できます。上述のように、トライデータ構造はデータを検索するのに非常に効率的であり、特定の単語の検索はO(1)の大きさで実行できます。時間が重要な場合、つまり データをすばやく検索して取得する必要がある場合は、trieデータ構造を使用できます。
メモリスペースの消費量を減らしてパフォーマンスを向上させたい場合は、3値データ構造を使用します。これらはどちらも、膨大な数の文字列(辞書に含まれる単語など)を格納するのに適しています。