2009-02-07 22 views
105

Qual è il modo migliore per concatenare un elenco di oggetti String? Sto pensando di fare in questo modo:Il modo migliore per concatenare Elenco di oggetti stringa?

List<String> sList = new ArrayList<String>(); 

// add elements 

if (sList != null) 
{ 
    String listString = sList.toString(); 
    listString = listString.subString(1, listString.length() - 1); 
} 

in qualche modo trovato questo per essere più ordinato che utilizzare il metodo StringBuilder/StringBuffer.

Qualche idea/commento?

risposta

42

L'approccio dipende dall'implementazione di ArrayList # toString() di Java.

Mentre l'implementazione è documentata nell'API Java e molto improbabile da modificare, c'è una possibilità che potrebbe. È molto più affidabile implementarlo da solo (loop, StringBuilder, ricorsione qualunque cosa ti piaccia di più).

Sicuramente questo approccio può sembrare "più ordinato" o più "troppo dolce" o "denaro" ma è, a mio parere, un approccio peggiore.

+8

Sono d'accordo sul fatto che non mi piace il modo in cui l'OP lo fa, ma per ragioni diverse. (1) Facendolo in questo modo si nasconde l'intento del codice (mentre qualcosa come "StringUtils.join" è perfettamente leggibile). (2) Non è flessibile, poiché potresti voler cambiare il delimitatore. (Sostituire semplicemente '", "' nella stringa finale non è un buon modo, dal momento che potrebbe esserci '", "' incorporato nelle stringhe originali.) Quindi anche se, come dici tu, 'ArrayList.toString' probabilmente ha vinto Cambiare, non vorrei ancora seguire questa strada. –

+2

Puoi essere sicuro al 99,9% che ArrayList.toString non cambierà a meno che non diventi un vettore di attacco in qualche modo; la retrocompatibilità lo vedrà. Ma sono d'accordo che l'uso di toString() è ancora una cattiva idea. –

2

in qualche modo trovato questo per essere più ordinato rispetto utilizzando l'approccio StringBuilder/StringBuffer .

Suppongo che dipenda dall'approccio scelto.

Il metodo AbstractCollection # toString() esegue semplicemente un'iterazione su tutti gli elementi e li aggiunge a uno StringBuilder. Quindi il tuo metodo potrebbe salvare poche righe di codice ma a costo di una manipolazione extra di String. Che il tradeoff sia buono dipende da te.

+0

La manipolazione corda in più si sta parlando è la creazione di sottoStringa alla fine, giusto? – Jagmal

+0

@Jagmal, sì. Se questo codice viene eseguito molto, allora dovresti profilare il profilo per vedere se è un problema per te. Altrimenti dovresti essere OK. Una cosa da tenere a mente è che il formato di toString() interno potrebbe cambiare in una futura versione di JDK. – Kevin

+0

@Kevin: Sì, suppongo che l'implementazione di toString che cambia nella futura versione di JDK sia una ragione sufficiente per non farlo. :) – Jagmal

0

A seconda dell'esigenza di prestazioni e quantità di elementi da aggiungere, questa potrebbe essere una soluzione ok. Se la quantità di elementi è elevata, la ridistribuzione della memoria di Arraylist potrebbe essere leggermente più lenta di StringBuilder.

+0

Puoi spiegare "la riallocazione della memoria da parte degli Arraylists potrebbe essere un po 'più lenta di StringBuilder". – Jagmal

+0

Arraylist è costruito su array standard e richiede di impostare una dimensione dell'array (come 'int [] a = new int [10];'). Questo è ciò che l'ArrayList sta facendo per te e, inserendo nuovi elementi, ArrayList dovrà creare un array più grande e copiare tutti gli elementi nel nuovo. – georg

1

ArrayList eredita la sua toString() -Metodo da AbstractCollection, vale a dire:

public String toString() { 
    Iterator<E> i = iterator(); 
    if (! i.hasNext()) 
     return "[]"; 

    StringBuilder sb = new StringBuilder(); 
    sb.append('['); 
    for (;;) { 
     E e = i.next(); 
     sb.append(e == this ? "(this Collection)" : e); 
     if (! i.hasNext()) 
      return sb.append(']').toString(); 
     sb.append(", "); 
    } 
} 

Costruire la stringa da soli sarà molto più efficiente.


Se davvero si vuole aggregare le stringhe in anticipo in una sorta di lista, è necessario fornire il proprio metodo per accedere in modo efficiente loro, ad esempio, In questo modo:

static String join(Collection<?> items, String sep) { 
    if(items.size() == 0) 
     return ""; 

    String[] strings = new String[items.size()]; 
    int length = sep.length() * (items.size() - 1); 

    int idx = 0; 
    for(Object item : items) { 
     String str = item.toString(); 
     strings[idx++] = str; 
     length += str.length(); 
    } 

    char[] chars = new char[length]; 
    int pos = 0; 

    for(String str : strings) { 
     str.getChars(0, str.length(), chars, pos); 
     pos += str.length(); 

     if(pos < length) { 
      sep.getChars(0, sep.length(), chars, pos); 
      pos += sep.length(); 
     } 
    } 

    return new String(chars); 
} 
+1

Spero che abbiate dei benchmark per confermare che la vostra implementazione è migliore perché non è molto più pulita dell'implementazione toString di AbstractCollection. Il lettore deve davvero concentrarsi sulla tua implementazione per capire qualcosa mentre l'altro è davvero facile da leggere. –

2

Mi sembra che lo StringBuilder sia veloce ed efficiente.

La forma di base sarebbe simile a questa:

public static String concatStrings(List<String> strings) 
{ 
    StringBuilder sb = new StringBuilder(); 
    for(String s: strings) 
    { 
     sb.append(s); 
    } 
    return sb.toString();  
} 

Se questo è troppo semplicistico (e probabilmente lo è), è possibile utilizzare un approccio simile e aggiungere un separatore come questo:

public static String concatStringsWSep(List<String> strings, String separator) 
{ 
    StringBuilder sb = new StringBuilder(); 
    for(int i = 0; i < strings.size(); i++) 
    { 
     sb.append(strings.get(i)); 
     if(i < strings.size() - 1) 
      sb.append(separator); 
    } 
    return sb.toString();    
} 

Sono d'accordo con gli altri che hanno risposto a questa domanda quando dicono che non si deve fare affidamento sul metodo toString() di ArrayList di Java.

39

Una variante di risposta di codefin

public static String concatStringsWSep(Iterable<String> strings, String separator) { 
    StringBuilder sb = new StringBuilder(); 
    String sep = ""; 
    for(String s: strings) { 
     sb.append(sep).append(s); 
     sep = separator; 
    } 
    return sb.toString();       
} 
+2

Chiazza di petrolio! Non ho mai visto la soluzione in questa luce. Molto bella. – codefin

+1

@codefine Vedi http://stackoverflow.com/questions/58431/algorithm-for-joining-eg-an-array-of-strings – Jagmal

+0

Mi piace molto questa risposta b/c è possibile utilizzare un foreach ed è molto semplice , ma è anche più inefficiente. Come puoi renderlo un ciclo più stretto? – Jess

252

Utilizzare uno dei i StringUtils.join metodi di Apache Commons Lang.

import org.apache.commons.lang3.StringUtils; 

String result = StringUtils.join(list, ", "); 

Se siete abbastanza fortunati da essere utilizzando Java 8, quindi è ancora più facile ... basta usare String.join

String result = String.join(", ", list); 
+1

In alternativa per gli utenti di Guava: 'Joiner.on (',').join (Collezione) ' – froginvasion

+2

Sì, ogni volta: Bloch," Java efficace ", Articolo 47:" Conoscere e utilizzare le librerie ". Le librerie di Apache Commons dovrebbero essere la prima cosa da inserire nel file di build (si spera, Gradle). Come conclude l'articolo 47: "Per riassumere, non reinventare la ruota". –

+2

Utilizzo dell'API di Guava: 'Joiner.on (',') .skipNulls(). Join (Iterable )'. Per rappresentare valori nulli puoi usare 'Joiner.on (','). UseForNull (" # "). Join (Iterable )' – savanibharat

0

Utilizzando la libreria Functional Java, importarli:

import static fj.pre.Monoid.stringMonoid; 
import static fj.data.List.list; 
import fj.data.List; 

... allora puoi fare questo:

List<String> ss = list("foo", "bar", "baz"); 
String s = stringMonoid.join(ss, ", "); 

In alternativa, la via generica, se non si dispone di una lista di stringhe:

public static <A> String showList(List<A> l, Show<A> s) { 
    return stringMonoid.join(l.map(s.showS_()), ", "); 
} 
2

Supponendo è più veloce per spostare solo un puntatore/set un byte null (o comunque Java implementa StringBuilder # setlength), piuttosto che verificare una condizione di volta in volta attraverso il ciclo di vedere quando per aggiungere il delimitatore, è possibile utilizzare questo metodo:

public static String Intersperse (Collection<?> collection, String delimiter) 
{ 
    StringBuilder sb = new StringBuilder(); 
    for (Object item : collection) 
    { 
     if (item == null) continue; 
     sb.append (item).append (delimiter); 
    } 
    sb.setLength (sb.length() - delimiter.length()); 
    return sb.toString(); 
}
15

Guava è una libreria piuttosto pulito da Google:

Joiner joiner = Joiner.on(", "); 
joiner.join(sList); 
2

variazione Avanti sulla risposta di Pietro Lawrey senza inizializzazione di una nuova stringa ogni ciclo girare

String concatList(List<String> sList, String separator) 
{ 
    Iterator<String> iter = sList.iterator(); 
    StringBuilder sb = new StringBuilder(); 

    while (iter.hasNext()) 
    { 
     sb.append(iter.next()).append(iter.hasNext() ? separator : ""); 
    } 
    return sb.toString(); 
} 
84

Utilizzo di Java 8+

String str = list.stream().collect(Collectors.joining()) 

o anche

String str = String.join("", list); 
4

preferisco string.join (elenco) in Java 8

18

Se si sta sviluppando per Android, ci sono s TextUtils.join fornito dall'SDK.

0

se avete problemi con le dipendenze.è possibile utilizzare new JSONArray(list).toString()

1

in Java 8 è possibile utilizzare anche un riduttore, qualcosa come:

public static String join(List<String> strings, String joinStr) { 
    return strings.stream().reduce("", (prev, cur) -> prev += (cur + joinStr)); 
} 
3

Piuttosto che a seconda ArrayList.toString() implementazione, si potrebbe scrivere un one-liner, se si utilizza Java 8:

String result = sList.stream() 
        .reduce("", String::concat); 

Se si preferisce usare StringBuffer invece di String dal String::concat ha un'autonomia di O(n^2), è possibile convertire tutti i String-StringBuffer prima.

StringBuffer result = sList.stream() 
          .map(StringBuffer::new) 
          .reduce(new StringBuffer(""), StringBuffer::append); 
+1

FYI questa implementazione è O (n^2) come String :: concat copia entrambe le stringhe su un nuovo buffer: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String.java#String.concat%28java.lang.String% 29 – tep

11

Questo è il modo più elegante e pulito che ho trovato finora:

list.stream().collect(Collectors.joining(delimiter)); 
Problemi correlati