2016-10-15 6 views
2

リストを別のリストに追加する場合は、target.adAll(source)に電話します。ソースリストを変更する必要がある場合、addAllと最も効率的なのは何ですか?

しかし、最初にリストから各値を処理する必要がある場合はどうすればよいですか?

私は

for(String s: source) { 
    target.add(s.toLowerCase()); 
} 

または使用してJava 8のような何かを行うことができます:

source.stream().map(x->x.toLowerCase()).forEachOrdered(target::add); 

は、しかし、いずれかの方法は、私はaddAllのパフォーマンス上の利点を失うように見えます。これを行う最も効果的な方法は何ですか?

+3

JITは、2番目のコードを最初のものに変換できるはずです。 http://stackoverflow.com/questions/39495347/whats-the-better-way-to-add-elements-from-a-stream-to-an-existing-listを参照してください。 – kennytm

+0

できることは何もありません。 –

答えて

2

「パフォーマンスのメリットaddAll」とは何ですか?最後に、addAllは、すべての要素をターゲットCollectionに追加する必要があります。ターゲットがArrayListである場合、主な利点は、不必要な容量増加操作がないことを保証することです。

ただし、これは一時的な配列を作成することを犠牲にして行われます(implementation of ArrayList.addAllを参照)。この費用を上回るためには、かなりの数の要素を追加する必要があります。

ターゲットの現在の容量よりも多くの要素を追加しようとすると、操作を増やすことは避けられません。したがってaddAllは、addを単に使用すると、を1回以上増やしてを増やす必要がある場合にのみ、利点があります。容量は1.5倍に増加し、容量は現在のサイズと同じかそれ以上であるため、不必要な容量増加操作を期待するために、現在のサイズの半分以上の要素を追加する必要があります。

あなたが本当に思うなら、これは問題になるだろう、それは修正するのは簡単です:もちろん

if(target instanceof ArrayList) 
    ((ArrayList)target).ensureCapacity(target.size()+source.size()); 
source.stream().map(String::toLowerCase).forEachOrdered(target::add); 

addのコストがはるかに高いいくつかのコーナーケースは、例えば、ありますCopyOnWriteArrayList。このターゲット収集タイプでは、Listcollect(Collectors.toList())を最初に収集し、次にaddAllを収集すると効果的です。コレクションが前に、最初のsize()を要求した場合、このアプローチは、二回ストリームを評価苦しむでしょう

target.addAll(lazyCollection(() -> source.stream().map(String::toLowerCase))); 

:のように使用することができ

public static <T> Collection<T> lazyCollection(Supplier<? extends Stream<T>> s) { 
    return new AbstractCollection<T>() { 
     public Iterator<T> iterator() { return s.get().iterator(); } 
     public int size() { return (int)s.get().count(); } 
     public Object[] toArray() { return s.get().toArray(); } 
    }; 
} 

:それとも、中間ステップとして、単純な怠惰Collectionを作成しますIteratorを取得しますが、afaik、標準コレクションはありません。それらは、予測されるサイズに依存せずにイテレータを使用するか、ArrayList.addAllまたはCopyOnWriteArrayList.addAllなどのtoArray()に頼ってください。

+0

ありがとう、それはとても良い説明です。 LinkedList.addAll実装がソースコレクションでtoArrayを呼び出す理由を説明できますか?それは私がそれ以外の理由がないと思うので、私が(L2キャッシングのような)行方不明になるかもしれない他の "パフォーマンスマジック"があるかどうか疑問に思った。 – ykaganovich

+1

ソースがスレッドセーフなコレクションである場合、単一の呼び出しですべての要素を取得する*はソースの種類に応じてアトミックかもしれませんが、特定の組み合わせは安全ですが、そのような動作は指定されていないため、それを実装する。また、これは、それが動作することを保証します。もしあなた自身にリストを追加するなら、この特殊なケースをテストする安価な方法があるので、そうした方法ですべてのソースを扱う必要はありません。一般的に、完全な配列を要求すると、ソースの反復子に対する不信がありますが、何がこの決定をもたらしたのかはわかりません。 – Holger

2

Eclipse Collectionscollect()パターンを使用できます。collect()は、JDKのmap()に相当します。

MutableList<String> source = Lists.mutable.with("A", "B", "C"); 
MutableList<String> target = source.collect(String::toLowerCase); 

既存のターゲットリストを持っている場合は、ターゲットコレクション受け入れるcollect()のバリアントを使用することができます

List<String> source = Arrays.asList("A", "B", "C"); 
List<String> target = ListAdapter.adapt(source).collect(String::toLowerCase); 

MutableList<String> source = Lists.mutable.with("A", "B", "C"); 
source.collect(String::toLowerCase, target); 

をあなたはListからリストを変更できない場合

ListについてreplaceAll()を使用することもできます。

List<String> source = Arrays.asList("A", "B", "C"); 
List<String> target = new ArrayList<>(source); 
target.replaceAll(String::toLowerCase); 

上記の解決策はより効果的かもしれませんが、それらはすべてターゲットリストのサイズを事前に設定することがあります。

注:私はEclipse Collectionsに貢献しています。

関連する問題