Javaの配列またはリスト。どちらが速いですか?


351

Javaでシリアルにアクセスするには、何千もの文字列をメモリに保持する必要があります。それらを配列に格納する必要がありますか、それとも何らかのリストを使用する必要がありますか?

配列は(Listsとは異なり)連続したメモリのチャンクにすべてのデータを保持するため、配列を使用して数千の文字列を格納すると問題が発生しますか?


5
「配列はすべてのデータを連続したメモリのチャンクに保持するため」これをJava用にバックアップするための引用はありますか?
マットb

1
マットなし。私はCでこれを知っています。Javaも同じメソッドを使用すると思います。
euphoria83

それらが単一のメモリチャンクに保持されるとは思えません。
Fortyrunner 2009

3
たとえそれがメモリの単一のブロックであっても、それはまだ約1000 * 4 = 4kbの価値しかないでしょう、それは多くのメモリではありません。
CookieOfFortune 2009

3
@mattbそれはCS全体で「配列」が意味することです。引用は必要ありません。JLSおよび[JVM仕様]()での配列の長さへの多数の参照は、配列が隣接している場合にのみ理解できます。
ローンズオブローン

回答:


358

プロファイラーを使用して、どちらが速いかをテストすることをお勧めします。

私の個人的な意見では、リストを使用する必要があります。

私は大規模なコードベースで作業しており、以前の開発者グループはあらゆる場所で配列を使用していました。それはコードを非常に柔軟性のないものにしました。その大きなチャンクをリストに変更した後、速度に違いがないことに気付きました。


2
@Fortyrunner-あなたの経験から、Javaで抽象化と生データフォームの間にパフォーマンスに大きな違いをもたらすような選択肢はありますか?
euphoria83

4
パフォーマンス測定の問題の1つは、新しいバージョンのJavaに対して常に再テストする必要があることです。現在、誰かがマップのキーにintを使用している(スペース/時間を節約するため)問題に取り組んでいます。次に、すべての行を新しいオブジェクトに変更する必要があります。
Fortyrunner 2009

9
だから私は今、生データを避けようとしています。それはめったに顕著な違いを生みません。ホットスポットは驚くべきテクノロジーであり、二度と推測しないでください。シンプルでメンテナンス可能なコードを書くだけで、あとはHotspotが行います。
Fortyrunner 2009

4
プロファイラーの結果は、プロファイラーを実行しているJavaプラットフォームでのみ有効であることに注意してください。これは、お客様とは異なる場合があります。
MikkelLøkke2013年

4
効果的なJavaでは、APIの相互運用性を支援し、型の安全性を確保するため、リストを推奨しています。
juanmf 14

164

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#ensureCapacityの使用を検討する必要があります。
JesperE 2009

6
ここで動的バインディングのコストを支払いませんか?
ウリ

2
例えば容量が倍の代わりに、ちょうど1だけ増加させ、複数回追加したとき、私ではありませんO(N)のArrayListに追加することを推測すると思い、いくつかのammortization効果があるはず
zedoo

@zedoo真ん中で足し算と引き算をするつもりだったと思う
MalcolmOcean 2012

「今日、Javaが設計されていた場合、ArrayList構造が優先され、配列が完全に除外された可能性があります。」...私はこれが真実であることを真剣に疑っています。それが今日書き換えられているJVMであるなら、あなたが言ったことは確かに可能です。しかし、私たちが持っているJVMでは、配列はJavaの基本的な型です。
scottb

100

ArrayListの使用を提案する答えはほとんどのシナリオで意味がありますが、相対的なパフォーマンスの実際の問題は実際には答えられていません。

配列でできることはいくつかあります。

  • それを作成する
  • アイテムを設定する
  • アイテムを入手する
  • クローン/コピー

一般的な結論

ArrayListではgetおよびset操作が多少遅くなりますが(私のマシンでは、呼び出しごとに1および3ナノ秒)、非集中的に使用するためにArrayListと配列を使用するオーバーヘッドはほとんどありません。ただし、注意すべき点がいくつかあります。

  • リストの操作のサイズ変更(呼び出し時) list.add(...))はコストがかかるため、可能な場合は初期容量を適切なレベルに設定する必要があります(配列を使用する場合にも同じ問題が発生することに注意してください)
  • プリミティブを処理する場合、配列は、多くのボックス化/ボックス化解除の変換を回避できるため、非常に高速になる可能性があります。
  • ArrayListの値を取得/設定するだけのアプリケーション(あまり一般的ではありません!)は、配列に切り替えることで、25%を超えるパフォーマンスの向上を実現できます。

詳細な結果

以下は、標準のx86デスクトップマシン上のJDK 7でjmhベンチマークライブラリ(時間はナノ秒単位)を使用してこれら3つの操作を測定した結果です。結果が比較可能であることを確認するために、テストではArrayListのサイズが変更されることはありません。ここで利用可能なベンチマークコード

配列/配列リストの作成

4つのテストを実行し、次のステートメントを実行しました。

  • createArray1: Integer[] array = new Integer[1];
  • createList1: List<Integer> list = new ArrayList<> (1);
  • createArray10000: Integer[] array = new Integer[10000];
  • createList10000: 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つのテストを実行し、次のステートメントを実行しました。

  • getList: return list.get(0);
  • getArray: 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);
  • setArray: 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のパフォーマンスは、配列のコピーと同じになるように(経由配列をコピーするcloneArrays.copyOfまたはSystem.arrayCopy 重大な違い性能面を行うものではありません)。


1
素晴らしい分析。ただし、「プリミティブを処理する場合、多くのボクシング/アンボクシング変換を回避できるため、配列を大幅に高速化できます」というコメントに関しては、プリミティブ配列でサポートされたリストを使用して、ケーキを食べて食べることもできます。実装; 例:github.com/scijava/scijava-common/blob/master/src/main/java/org/…。そのようなことがコアJavaになっていないことに、私は実際にかなり驚いています。
ctrueden 2013

2
@ctrueden yes標準のJDK ArrayListに適用されるコメント。trove4jは、プリミティブリストをサポートするよく知られたライブラリです。Java 8は、いくつかのプリミティブ専用のストリームでいくつかの改善をもたらします。
アッシリア2013

jmhベンチマークのしくみはわかりませんが、発生する可能性のあるJITコンパイルは考慮されますか?Javaアプリケーションのパフォーマンスは、JVMがコードをコンパイルするときに変化する可能性があります。
ホフマン

@Hoffmannはい-測定から除外されるウォームアップフェーズが含まれます。
assylias 2014年

97

配列よりもジェネリック型を優先する必要があります。他の人が述べたように、配列には柔軟性がなく、ジェネリック型の表現力はありません。(ただし、ランタイム型チェックはサポートしていますが、ジェネリック型との混同が激しいです。)

しかし、いつものように、最適化するときは常に次の手順に従ってください。

  • 美しく、きれいで、機能するまで最適化しないでくださいコードのバージョンがでください。ジェネリック型に変更することは、すでにこのステップで十分に動機付けられている可能性があります。
  • 素敵でクリーンなバージョンがある場合、それが十分に速いかどうかを判断します。
  • 十分に速くない場合は、そのパフォーマンスを測定します。このステップは2つの理由で重要です。測定しないと、(1)行った最適化の影響がわかりませんし、(2)どこを最適化すればよいかわかりません。
  • コードの最もホットな部分を最適化します。
  • もう一度測定します。これは、以前の測定と同じくらい重要です。最適化で改善されなかった場合は、元に戻します。最適化を行わなかったコードはクリーンで素晴らしく、機能していたことを覚えておいてください

24

元のポスターが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の実装方法です。


1
答えてくれてありがとう。しかし、いいえ、私はC ++リストをJavaのインターフェースリストと混同していません。ArrayListやVectorのようなList実装のパフォーマンスを生の配列と比較したかったので、私はそのような方法で質問をしました。
euphoria83

ArrayListとVectorはどちらも「すべてのデータを連続したメモリのチャンクに保持します」。
トム・ホーティン-09

13

ほとんどの場合、配列ではなく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
    }
}

これは興味深い答えですが、ArrayListがメモリ内の初期サイズで初期化されていない場合はさらに悪いのではないでしょうか。一般的に、ある意味でネイティブ配列よりもArrayListを使用する利点は、あなたが知らないため、心配する必要がないことです。ArrayListはデフォルトで初期長10で作成され、サイズが変更されます。サイズ変更は高いと思います。明らかにベンチマークを試みていません。
Zak Patterson

4
このマイクロベンチマークには欠陥があります(ウォームアップなし、別のメソッドではない操作なので、arraylist部分はJITによって最適化されることはありませんなど)
assylias

私はアサイリアに同意します。このベンチマークの結果は信頼すべきではありません。
スティーブンC

@StephenC私は適切なマイクロベンチマークを追加しました(それはget操作が同等であることを示しています)。
アッシリア2013年

11

まず、明確にする価値があります。これは、古典的なcomp sciデータ構造の意味で「リスト」を意味するのか(つまり、リンクされたリスト)、それともjava.util.Listを意味するのですか?java.util.Listを意味する場合、それはインターフェースです。配列を使用したい場合は、ArrayList実装を使用するだけで、配列のような動作とセマンティクスが得られます。問題が解決しました。

配列とリンクリストのどちらかを意味する場合、Big Oに戻るのは少し異なる引数です(これがなじみのない用語である場合は、英語での簡単な説明です)。

アレイ;

  • ランダムアクセス:O(1);
  • 挿入:O(n);
  • 削除:O(n)。

リンクされたリスト:

  • ランダムアクセス:O(n);
  • 挿入:O(1);
  • 削除:O(1)。

したがって、配列のサイズを変更する方法に最適な方を選択します。サイズを変更、挿入、削除する場合は、リンクリストの方が適しています。ランダムアクセスがまれな場合も同様です。あなたはシリアルアクセスについて言及します。主に変更をほとんど加えずにシリアルアクセスを行っている場合は、どちらを選択してもかまいません。

あなたが言うように、メモリの連続していない可能性のあるブロックと(効果的に)次の要素へのポインタを扱っているので、リンクされたリストは少し高いオーバーヘッドを持っています。ただし、何百万ものエントリを処理している場合を除いて、これはおそらく重要な要素ではありません。


私はjava.util.Listインターフェイスを意味します
euphoria83

1
linkedlistでのランダムアクセスO(n)は、私にとって大きな問題のようです。
ビョルン、2011年

11

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

@クリスメイ:素晴らしい仕事!両方の実際の実行時間はどれくらいですか?使っていた弦のサイズを教えていただけますか?また、「String s:stringsList」を使用すると時間がかかるようになったため、Javaでより高い抽象化を一般的に使用することに対する私の最大の懸念です。
euphoria83

このmcirobenchmarkの文字列の長さは問題ではありません。gcはなく、char[]これには触れません(これはCではありません)。
トム・ホーティン-09

私の典型的な時間は、アレイバージョンの場合は〜25ms、ArrayListバージョンの場合は〜35msでした。文字列は15〜20文字でした。トムが言うように、文字列のサイズはそれほど大きな違いはありません。約100文字の文字列では、タイミングはほぼ同じでした。
クリス月

3
どのように測定しましたか?Javaマイクロベンチマークでの単純な測定では、通常、情報よりも多くの誤った情報が生成されます。上記のステートメントに注意してください。
jmg

6

いいえ、技術的には、配列は文字列への参照のみを格納するためです。文字列自体は別の場所に割り当てられます。1000アイテムの場合、リストの方が優れていて遅くなりますが、柔軟性が増し、特にサイズを変更する場合は使いやすくなります。


5
リストには、文字列への参照のみが格納されます。
PeterŠtibraný2009

6

何千人もいる場合は、トライの使用を検討してください。トライは、保存された文字列の共通のプレフィックスをマージするツリーのような構造です。

たとえば、文字列が

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に格納するよりも明らかに少ないスペース。

あなたはそれらを「シリアルに」アクセスしていると言います-それがアルファベット順に連続することを意味する場合、トライは深さ優先で反復すれば明らかに無料でアルファベット順も提供します。


1
トライはライブラリのようなものですか、それをどのように作成しますか?
euphoria83

トライは、トークン化された文字列の場合にのみ役立ちます。誰かが実行中のテキストを文字列として保存している場合は役に立ちません。
MN

5

更新:

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。ほぼ同じ結果。


2
3倍の速さは真ですが、重要ではありません。14ms長い時間ではありません
0x6C38 2013

1
ベンチマークは、JVMのウォームアップを考慮していません。main()をtest()に変更し、mainからtestを繰り返し呼び出します。テストの3回目または4回目の実行で、何倍も速く実行されます。その時点で、配列が配列よりも約9倍高速であることがわかりました。
マイク

5

ここにはすでに良い答えがたくさんあるので、挿入と反復のパフォーマンスの比較である実用的な見方の他の情報をいくつか提供したいと思います。プリミティブ配列と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.");
    }
}

パフォーマンス結果は以下のとおりです。

ここに画像の説明を入力してください


4

リストは配列よりも遅いです。効率が必要な場合は配列を使用してください。柔軟性が必要な場合はリストを使用してください。


4

ArrayListは配列をカプセル化するので、プリミティブ配列を使用する場合とほとんど違いはありません(JavaでListを使用するほうがはるかに簡単であるという事実を除いて)。

ArrayListよりも配列を優先するのが理にかなっているのは、byte、intなどのプリミティブを格納していて、プリミティブ配列を使用して得られる特定のスペース効率が必要な場合だけです。


4

文字列オブジェクトを格納する場合、配列とリストの選択はそれほど重要ではありません(パフォーマンスを考慮すると)。配列とリストの両方が実際のオブジェクトではなく文字列オブジェクト参照を格納するためです。

  1. 文字列の数がほぼ一定の場合は、配列(またはArrayList)を使用します。ただし、数値の変動が大きすぎる場合は、LinkedListを使用することをお勧めします。
  2. 途中で要素を追加または削除する必要がある場合(または今後必要になる場合)、必ずLinkedListを使用する必要があります。

4

配列よりもリストを使用した場合のパフォーマンスへの影響をよく理解するためにここに来ました。私はここで私のシナリオに合わせてコードを適応させる必要がありました。配列/リストは、主にゲッターを使用して、配列[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);
    }

3

データの大きさが事前にわかっている場合は、配列の方が高速になります。

リストはより柔軟です。配列に裏打ちされたArrayListを使用できます。


ArrayListには、バッキング配列を指定したサイズに事前に割り当てるEnsureCapacity()メソッドがあります。
JesperE 2009

または、構築時にサイズを指定できます。また、ここで「より高速」とは、「1つではなく2つのメモリ領域を割り当てるために数マイクロ秒」を意味します
Aaron Digulla

3

固定サイズで使用できる場合、配列はより高速になり、必要なメモリも少なくなります。

要素を追加および削除するListインターフェースの柔軟性が必要な場合は、どの実装を選択するかという問題が残ります。多くの場合、ArrayListが推奨され、どのような場合にも使用されますが、リストの最初または途中の要素を削除または挿入する必要がある場合、ArrayListにもパフォーマンスの問題があります。

したがって、GapListを紹介するhttp://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-listを確認することをお勧めします。この新しいリストの実装は、ArrayListとLinkedListの両方の長所を組み合わせており、ほぼすべての操作で非常に優れたパフォーマンスを実現します。


2

実装によって異なります。プリミティブ型の配列は、ArrayListよりも小さく、効率的である可能性があります。これは、最も単純なArrayList実装が各値へのポインタを格納するのに対し、配列はメモリの連続したブロックに直接値を格納するためです。特に64ビットプラットフォームでは、これは大きな違いを生む可能性があります。

もちろん、jvm実装がこの状況に対して特別なケースを持つ可能性があります。その場合、パフォーマンスは同じになります。


2

リストは、ジェネリックを使用できるため、Java 1.5以降では推奨される方法です。配列にジェネリックを含めることはできません。また、配列には事前定義された長さがあり、動的に拡大することはできません。サイズの大きい配列を初期化することはお勧めできません。ArrayListはジェネリックで配列を宣言する方法であり、動的に拡張できます。しかし、削除と挿入がより頻繁に使用される場合、リンクされたリストが使用される最も速いデータ構造です。


2

特に、アイテムの数とサイズが変化しないことがわかっている場合は、リストの代わりにそれらを使用できるすべての場所で配列を推奨します。

Oracle Javaのベストプラクティスを参照してください:http : //docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056

もちろん、コレクションにオブジェクトを何度も追加したり削除したりする必要がある場合は、使いやすいリストを使用してください。


リンクしたドキュメントは10年以上前のものです。つまり、Java 1.3に適用されます。それ以降、パフォーマンスが大幅に改善されました...
assylias 2014年

@assyliasは上記の回答を参照してください。これらにはパフォーマンステストが含まれており、アレイがより高速であることが示されています
Nik

3
私はそれらの1つを書いたことを知っています。しかし、「リストの代わりに配列を使用できるすべての場所で配列が推奨される」とは良いアドバイスではないと思います。プリミティブを扱い、コードがパフォーマンスに敏感でない限り、ほとんどの状況でArrayListがデフォルトの選択になります。
assylias 2014年

2

回答のどれにも、私が興味を持った情報がありませんでした-同じアレイを何度も繰り返しスキャンすることです。このための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);
    }

}

2

「千」は大きな数ではありません。数千の段落長の文字列は、数メガバイトのサイズです。これらをシリアルにアクセスするだけの場合は、不変の単一リンクリストを使用します。


ほとんどの64ビット実装では8バイト。
トム・ホーティン-タックライン

これがjava.util.LinkedListよりも速いという証拠はありますか?どちらも「インメモリ」ですか?また、違いがあるかのように不変にすることもできます。
ローンの侯爵2012

1

適切なベンチマークなしに最適化の罠に陥らないでください。他の人が提案するように、仮定を行う前にプロファイラーを使用します。

列挙したさまざまなデータ構造には、さまざまな目的があります。リストは、最初と最後に要素を挿入するのに非常に効率的ですが、ランダムな要素にアクセスする場合、多くの問題があります。アレイは固定ストレージを備えていますが、高速ランダムアクセスを提供します。最後に、ArrayListは配列を拡張できるようにすることで、配列へのインターフェースを改善します。通常、使用されるデータ構造は、格納されたデータへのアクセスまたは追加方法によって決定されます。

メモリ消費について。あなたはいくつかのものを混ぜているようです。配列は、あなたが持っているデータのタイプのための連続したメモリのチャンクを与えるだけです。javaには固定のデータ型があることを忘れないでください:boolean、char、int、long、float、およびObject(これにはすべてのオブジェクトが含まれますが、配列もオブジェクトです)。つまり、文字列の配列[1000]またはMyObject myObjects [1000]を宣言した場合、オブジェクトの場所(参照またはポインタ)を格納するのに十分な大きさの1000個のメモリボックスしか取得できません。オブジェクトのサイズに合わせるのに十分な大きさの1000個のメモリボックスはありません。オブジェクトが最初に「新規」で作成されることを忘れないでください。これは、メモリ割り当てが行われ、後で参照(そのメモリアドレス)が配列に格納される場合です。オブジェクトは参照のみで配列にコピーされません。


1

それが文字列に実際の違いをもたらすとは思いません。文字列の配列で連続しているのは、文字列への参照です。文字列自体は、メモリ内のランダムな場所に格納されます。

配列とリストは、オブジェクトではなくプリミティブ型に違いをもたらす可能性があります。IF事前に要素の数を知っているし、柔軟性を必要としない、実際、彼らは連続して格納され、瞬時にアクセスするために、整数または倍精度数百万の配列は、メモリ内とわずかにリストよりも高速で、より効率的になります。これが、Javaが文字列に文字の配列、画像データに整数の配列などを使用する理由です。



1

ここに記載されている多くのマイクロベンチマークでは、array / ArrayListの読み取りなどの数ナノ秒の数値が見つかりました。すべてがL1キャッシュにある場合、これはかなり合理的です。

より高いレベルのキャッシュまたはメインメモリアクセスは、10nS-100nSのような桁数の時間を持つことができますが、L1キャッシュの場合は1nSのようになります。ArrayListへのアクセスには追加のメモリ間接があり、実際のアプリケーションでは、アクセス間でコードが何をしているかに応じて、このコストをほとんど発生しない場合から毎回支払う場合があります。そしてもちろん、小さなArrayListがたくさんある場合、これはメモリ使用量に追加され、キャッシュミスが発生する可能性が高くなります。

元のポスターは1つだけを使用しており、短時間で多くのコンテンツにアクセスしているように見えるので、大きな苦労はないはずです。しかし、それは他の人々とは異なる可能性があるため、マイクロベンチマークを解釈するときには注意が必要です。

ただし、Java文字列は、特に多くの小さな文字列を格納する場合は、恐ろしいほど無駄になります(メモリアナライザーで文字列を見ると、数文字の文字列では60バイトを超えるようです)。文字列の配列には、Stringオブジェクトへの間接指定と、Stringオブジェクトから文字列自体を含むchar []への間接指定があります。L1キャッシュを破壊するものがある場合、これは数千または数万の文字列と組み合わされたものです。だから、もしあなたが真剣に-本当に真剣に-可能な限り多くのパフォーマンスを削り出すことについてなら、あなたは違うやり方でそれを見ることができるでしょう。たとえば、2つの配列、つまりすべての文字列が次々に含まれるchar []と、開始位置へのオフセットを持つint []を保持できます。これは何かをするためのPITAであり、ほぼ間違いなく必要ありません。そして、あなたがそうするなら、あなたは


0

それはあなたがそれにアクセスしなければならない方法に依存します。

格納後、挿入または削除をほとんどまたはまったく行わずに主に検索操作を実行する場合は、配列に移動します(検索は配列のO(1)で行われますが、追加/削除では要素の並べ替えが必要になる場合があります)。 。

保存後、主な目的がほとんどまたはまったく検索操作なしで文字列を追加/削除することである場合は、リストに移動します。


0

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


0

配列-結果のフェッチを高速化する必要がある場合は常に優れています

リスト-O(1)で実行できるため、挿入と削除の結果を実行し、データを簡単に追加、フェッチ、および削除するメソッドも提供します。はるかに使いやすい。

ただし、データが格納されている配列内のインデックス位置がわかっている場合は、データのフェッチが高速であることを常に覚えておいてください。

これは、配列をソートすることで十分に達成できます。したがって、これによりデータをフェッチする時間が増加します(つまり、データの保存+データのソート+データが見つかった位置のシーク)。したがって、データをより早くフェッチできる場合でも、アレイからデータをフェッチするためのレイテンシが増加します。

したがって、これはトライデータ構造または3値データ構造で解決できます。上述のように、トライデータ構造はデータを検索するのに非常に効率的であり、特定の単語の検索はO(1)の大きさで実行できます。時間が重要な場合、つまり データをすばやく検索して取得する必要がある場合は、trieデータ構造を使用できます。

メモリスペースの消費量を減らしてパフォーマンスを向上させたい場合は、3値データ構造を使用します。これらはどちらも、膨大な数の文字列(辞書に含まれる単語など)を格納するのに適しています。

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