Was ist der schnellste Weg, zwei Strings in Java zu verketten?
d.h.
String ccyPair = ccy1 + ccy2;
Ich verwende cyPair
als Schlüssel in einer HashMap
und es wird in einer sehr engen Schleife aufgerufen, um Werte abzurufen.
Wenn ich dann profiliere, ist das der Engpass
Java.lang.StringBuilder.append(StringBuilder.Java:119)
Java.lang.StringBuilder.(StringBuilder.Java:93)
Der Grund, warum diese Routinen im Benchmark auftauchen, liegt darin, dass der Compiler das "+" unter den Covers implementiert.
Wenn Sie die verkettete Zeichenfolge wirklich benötigen, sollten Sie den Compiler mit "+" zaubern lassen. Wenn Sie nur einen Schlüssel für die Suche nach Karten benötigen, kann eine Schlüsselklasse, die beide Zeichenfolgen mit geeigneten equals
- und hashMap
-Implementierungen enthält, eine gute Idee sein, da der Kopierschritt vermieden wird.
Viel Theorie - Zeit für etwas Übung!
private final String s1 = new String("1234567890");
private final String s2 = new String("1234567890");
Verwenden Sie einfache Loops von 10.000.000, auf einem aufgewärmten 64-Bit-Hotspot 1.6.0_22 unter Intel Mac OS.
z.B
@Test public void testConcatenation() {
for (int i = 0; i < COUNT; i++) {
String s3 = s1 + s2;
}
}
Mit den folgenden Aussagen in den Schleifen
String s3 = s1 + s2;
1,33 s
String s3 = new StringBuilder(s1).append(s2).toString();
1,28 s
String s3 = new StringBuffer(s1).append(s2).toString();
1,92 s
String s3 = s1.concat(s2);
0,70 s
String s3 = "1234567890" + "1234567890";
0,0 s
Concat ist also der klare Gewinner, es sei denn, Sie haben statische Zeichenketten. In diesem Fall hat der Compiler Sie bereits erledigt.
Ich glaube, dass die Antwort möglicherweise bereits festgelegt wurde, aber ich gebe den Code bekannt.
Kurze Antwort, wenn Sie nur nach reiner Verkettung suchen, lautet: String.concat (...)
Ausgabe:
ITERATION_LIMIT1: 1
ITERATION_LIMIT2: 10000000
s1: STRING1-1111111111111111111111
s2: STRING2-2222222222222222222222
iteration: 1
null: 1.7 nanos
s1.concat(s2): 106.1 nanos
s1 + s2: 251.7 nanos
new StringBuilder(s1).append(s2).toString(): 246.6 nanos
new StringBuffer(s1).append(s2).toString(): 404.7 nanos
String.format("%s%s", s1, s2): 3276.0 nanos
Tests complete
Beispielcode:
package net.fosdal.scratch;
public class StringConcatenationPerformance {
private static final int ITERATION_LIMIT1 = 1;
private static final int ITERATION_LIMIT2 = 10000000;
public static void main(String[] args) {
String s1 = "STRING1-1111111111111111111111";
String s2 = "STRING2-2222222222222222222222";
String methodName;
long startNanos, durationNanos;
int iteration2;
System.out.println("ITERATION_LIMIT1: " + ITERATION_LIMIT1);
System.out.println("ITERATION_LIMIT2: " + ITERATION_LIMIT2);
System.out.println("s1: " + s1);
System.out.println("s2: " + s2);
int iteration1 = 0;
while (iteration1++ < ITERATION_LIMIT1) {
System.out.println();
System.out.println("iteration: " + iteration1);
// method #0
methodName = "null";
iteration2 = 0;
startNanos = System.nanoTime();
while (iteration2++ < ITERATION_LIMIT2) {
method0(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #1
methodName = "s1.concat(s2)";
iteration2 = 0;
startNanos = System.nanoTime();
while (iteration2++ < ITERATION_LIMIT2) {
method1(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #2
iteration2 = 0;
startNanos = System.nanoTime();
methodName = "s1 + s2";
while (iteration2++ < ITERATION_LIMIT2) {
method2(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #3
iteration2 = 0;
startNanos = System.nanoTime();
methodName = "new StringBuilder(s1).append(s2).toString()";
while (iteration2++ < ITERATION_LIMIT2) {
method3(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #4
iteration2 = 0;
startNanos = System.nanoTime();
methodName = "new StringBuffer(s1).append(s2).toString()";
while (iteration2++ < ITERATION_LIMIT2) {
method4(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
// method #5
iteration2 = 0;
startNanos = System.nanoTime();
methodName = "String.format(\"%s%s\", s1, s2)";
while (iteration2++ < ITERATION_LIMIT2) {
method5(s1, s2);
}
durationNanos = System.nanoTime() - startNanos;
System.out.println(String.format("%50s: %6.1f nanos", methodName, ((double) durationNanos) / ITERATION_LIMIT2));
}
System.out.println();
System.out.println("Tests complete");
}
public static String method0(String s1, String s2) {
return "";
}
public static String method1(String s1, String s2) {
return s1.concat(s2);
}
public static String method2(String s1, String s2) {
return s1 + s2;
}
public static String method3(String s1, String s2) {
return new StringBuilder(s1).append(s2).toString();
}
public static String method4(String s1, String s2) {
return new StringBuffer(s1).append(s2).toString();
}
public static String method5(String s1, String s2) {
return String.format("%s%s", s1, s2);
}
}
Sie sollten mit einem zur Laufzeit generierten String testen (wie UUID.randomUUID (). ToString ()) nicht zur Kompilierzeit (wie "my string"). Meine Ergebnisse sind
plus: 118 ns
concat: 52 ns
builder1: 102 ns
builder2: 66 ns
buffer1: 119 ns
buffer2: 87 ns
mit dieser Implementierung:
private static long COUNT = 10000000;
public static void main(String[] args) throws Exception {
String s1 = UUID.randomUUID().toString();
String s2 = UUID.randomUUID().toString();
for(String methodName : new String[] {
"none", "plus", "concat", "builder1", "builder2", "buffer1", "buffer2"
}) {
Method method = ConcatPerformanceTest.class.getMethod(methodName, String.class, String.class);
long time = System.nanoTime();
for(int i = 0; i < COUNT; i++) {
method.invoke((Object) null, s1, s2);
}
System.out.println(methodName + ": " + (System.nanoTime() - time)/COUNT + " ns");
}
}
public static String none(String s1, String s2) {
return null;
}
public static String plus(String s1, String s2) {
return s1 + s2;
}
public static String concat(String s1, String s2) {
return s1.concat(s2);
}
public static String builder1(String s1, String s2) {
return new StringBuilder(s1).append(s2).toString();
}
public static String builder2(String s1, String s2) {
return new StringBuilder(s1.length() + s2.length()).append(s1).append(s2).toString();
}
public static String buffer1(String s1, String s2) {
return new StringBuffer(s1).append(s2).toString();
}
public static String buffer2(String s1, String s2) {
return new StringBuffer(s1.length() + s2.length()).append(s1).append(s2).toString();
}
Für die Frage im Titel gilt: String.concat
ist normalerweise die schnellste Möglichkeit, zwei String
s zusammenzustellen (beachten Sie jedoch null
s). Es ist kein [übergroßer] Zwischenspeicher oder ein anderes Objekt beteiligt. Seltsamerweise +
wird in relativ ineffizienten Code mit StringBuilder
kompiliert.
Ihre Frage weist jedoch auf andere Probleme hin. Zeichenkettenverkettung zum Generieren von Schlüsseln für eine Karte ist ein allgemeines "Anti-Idiom". Es ist ein Hack und fehleranfällig. Sind Sie sicher, dass der generierte Schlüssel eindeutig ist? Bleibt es eindeutig, nachdem Ihr Code für eine noch unbekannte Anforderung gepflegt wurde? Am besten erstellen Sie eine unveränderliche Wertklasse für den Schlüssel. Die Verwendung einer List
- und einer generischen Tuple-Klasse ist ein schlampiger Hack.
Für mich ist die concat3-Methode wie folgt der schnellste Weg, nachdem ich Benchmark auf meinem Windows- und Remote-Linux-Rechner durchgeführt habe: - Obwohl ich glaube, dass die Leistung von Concat1 von der JVM-Implementierung und -Optimierung abhängt und in zukünftigen Versionen möglicherweise besser abschneidet
public class StringConcat {
public static void main(String[] args) {
int run = 100 * 100 * 1000;
long startTime, total = 0;
final String a = "a";
final String b = "assdfsaf";
final String c = "aasfasfsaf";
final String d = "afafafdaa";
final String e = "afdassadf";
startTime = System.currentTimeMillis();
concat1(run, a, b, c, d, e);
total = System.currentTimeMillis() - startTime;
System.out.println(total);
startTime = System.currentTimeMillis();
concat2(run, a, b, c, d, e);
total = System.currentTimeMillis() - startTime;
System.out.println(total);
startTime = System.currentTimeMillis();
concat3(run, a, b, c, d, e);
total = System.currentTimeMillis() - startTime;
System.out.println(total);
}
private static void concat3(int run, String a, String b, String c, String d, String e) {
for (int i = 0; i < run; i++) {
String str = new StringBuilder(a.length() + b.length() + c.length() + d.length() + e.length()).append(a)
.append(b).append(c).append(d).append(e).toString();
}
}
private static void concat2(int run, String a, String b, String c, String d, String e) {
for (int i = 0; i < run; i++) {
String str = new StringBuilder(a).append(b).append(c).append(d).append(e).toString();
}
}
private static void concat1(int run, String a, String b, String c, String d, String e) {
for (int i = 0; i < run; i++) {
String str = a + b + c + d + e;
}
}
}
Vielleicht sollten Sie anstelle der Verkettung eine Pair-Klasse erstellen.
public class Pair<T1, T2> {
private T1 first;
private T2 second;
public static <U1,U2> Pair<U1,U2> create(U1 first, U2 second) {
return new Pair<U1,U2>(U1,U2);
}
public Pair( ) {}
public Pair( T1 first, T2 second ) {
this.first = first;
this.second = second;
}
public T1 getFirst( ) {
return first;
}
public void setFirst( T1 first ) {
this.first = first;
}
public T2 getSecond( ) {
return second;
}
public void setSecond( T2 second ) {
this.second = second;
}
@Override
public String toString( ) {
return "Pair [first=" + first + ", second=" + second + "]";
}
@Override
public int hashCode( ) {
final int prime = 31;
int result = 1;
result = prime * result + ((first == null)?0:first.hashCode());
result = prime * result + ((second == null)?0:second.hashCode());
return result;
}
@Override
public boolean equals( Object obj ) {
if ( this == obj )
return true;
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
Pair<?, ?> other = (Pair<?, ?>) obj;
if ( first == null ) {
if ( other.first != null )
return false;
}
else if ( !first.equals(other.first) )
return false;
if ( second == null ) {
if ( other.second != null )
return false;
}
else if ( !second.equals(other.second) )
return false;
return true;
}
}
Und verwenden Sie dies als Schlüssel in Ihrer HashMap
Anstelle von HashMap<String,Whatever>
verwenden Sie HashMap<Pair<String,String>,Whatever>
In Ihrer engen Schleife anstelle von map.get( str1 + str2 )
würden Sie map.get( Pair.create(str1,str2) )
verwenden.
Ich würde empfehlen, den Vorschlag von Thorbjørn Ravn Andersens auszuprobieren.
Wenn Sie die verketteten Strings benötigen, ist es in Abhängigkeit von der Länge der beiden Teile möglicherweise etwas besser, wenn Sie die StringBuilder-Instanz mit der erforderlichen Größe erstellen, um eine Neuzuordnung zu vermeiden. Der standardmäßige StringBuilder-Konstruktor reserviert 16 Zeichen in der aktuellen Implementierung - zumindest auf meinem Computer. Wenn der verkettete String also länger als die anfängliche Puffergröße ist, muss der StringBuilder neu zuweisen.
Probieren Sie es aus und teilen Sie uns mit, was Ihr Profiler dazu zu sagen hat:
StringBuilder ccyPair = new StringBuilder(ccy1.length()+ccy2.length());
ccyPair.append(ccy1);
ccyPair.append(ccy2);
Gemäß der Java-Spezifikation ( und seit der allerersten Version von Java ) wird im Abschnitt "String Concatenation Operator +" Folgendes gesagt:
Um die Leistung der wiederholten String-Verkettung zu erhöhen, wird ein Java Der Compiler kann die StringBuffer-Klasse oder eine ähnliche Technik für .__ verwenden. Reduzieren Sie die Anzahl der zwischengeschalteten String-Objekte, die von .__ erstellt werden. Auswertung eines Ausdrucks
Grundsätzlich ist die Verwendung von + operator
oder StringBuilder.append
für Variablen grundsätzlich gleich.
Andere Sache, ich weiß, dass Sie in Ihrer Frage erwähnt haben, dass Sie nur 2 Strings hinzugefügt haben. Denken Sie jedoch daran, dass das Hinzufügen von 3 oder mehr Strings zu unterschiedlichen Ergebnissen führt:
Ich habe ein leicht modifiziertes @Duncan McGregor-Beispiel verwendet. Ich habe 5 Methoden, die 2 bis 6 Zeichenfolgen mit concat verknüpfen, und 5 Methoden, die 2 bis 6 Zeichenfolgen mit StringBuilder verketten:
// Initialization
private final String s1 = new String("1234567890");
private final String s2 = new String("1234567890");
private final String s3 = new String("1234567890");
private final String s4 = new String("1234567890");
private final String s5 = new String("1234567890");
private final String s6 = new String("1234567890");
// testing the concat
public void testConcatenation2stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2);
}
}
public void testConcatenation3stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2).concat(s3);
}
}
public void testConcatenation4stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2).concat(s3).concat(s4);
}
}
public void testConcatenation5stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2).concat(s3).concat(s4).concat(s5);
}
}
public void testConcatenation6stringsConcat(int count) {
for (int i = 0; i < count; i++) {
String s100 = s1.concat(s2).concat(s3).concat(s4).concat(s5).concat(s6);
}
}
//testing the StringBuilder
public void testConcatenation2stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).toString();
}
}
public void testConcatenation3stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).append(s3).toString();
}
}
public void testConcatenation4stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).append(s3).append(s4).toString();
}
}
public void testConcatenation5stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).append(s3).append(s4).append(s5).toString();
}
}
public void testConcatenation6stringsSB(int count) {
for (int i = 0; i < count; i++) {
String s100 = new StringBuilder(s1).append(s2).append(s3).append(s4).append(s5).append(s6).toString();
}
}
Ich habe diese Ergebnisse (in Sekunden) erhalten:
testConcatenation2stringsConcat: 0.018 ||||||||||||||||| testConcatenation2stringsSB: 0.2 testConcatenation3stringsConcat: 0.35 ||||||||||||||||||| testConcatenation3stringsSB: 0.25 testConcatenation4stringsConcat: 0.5 |||||||||||||||||||||| testConcatenation4stringsSB: 0.3 testConcatenation5stringsConcat: 0.67 ||||||||||||||||||| testConcatenation5stringsSB: 0.38 testConcatenation5stringsConcat: 0.9 ||||||||||||||||||||| testConcatenation5stringsSB: 0.43
Hier handelt es sich um eine vollständige Implementierung einer linearen Sondenkarte mit Doppeltasten und einem einzelnen Wert. Es sollte Java.util.HashMap auch gut übertreffen.
Achtung, es wurde in den frühen Morgenstunden von Grund auf neu geschrieben und könnte daher Fehler enthalten. Sie können es gerne bearbeiten.
Die Lösung muss jeden Wrapper schlagen. Die keine Zuordnung zu get/put macht es auch zu einer schnellen Allzweckkarte.
Hoffe, das löst das Problem. (Der Code kommt mit einigen einfachen Tests, die nicht benötigt werden)
package bestsss.util;
@SuppressWarnings("unchecked")
public class DoubleKeyMap<K1, K2, V> {
private static final int MAX_CAPACITY = 1<<29;
private static final Object TOMBSTONE = new String("TOMBSTONE");
Object[] kvs;
int[] hashes;
int count = 0;
final int rehashOnProbes;
public DoubleKeyMap(){
this(8, 5);
}
public DoubleKeyMap(int capacity, int rehashOnProbes){
capacity = nextCapacity(Math.max(2, capacity-1));
if (rehashOnProbes>capacity){
throw new IllegalArgumentException("rehashOnProbes too high");
}
hashes = new int[capacity];
kvs = new Object[kvsIndex(capacity)];
count = 0;
this.rehashOnProbes = rehashOnProbes;
}
private static int nextCapacity(int c) {
int n = Integer.highestOneBit(c)<<1;
if (n<0 || n>MAX_CAPACITY){
throw new Error("map too large");
}
return n;
}
//alternatively this method can become non-static, protected and overriden, the perfoamnce can drop a little
//but if better spread of the lowest bit is possible, all good and proper
private static<K1, K2> int hash(K1 key1, K2 key2){
//spread more, if need be
int h1 = key1.hashCode();
int h2 = key2.hashCode();
return h1+ (h2<<4) + h2; //h1+h2*17
}
private static int kvsIndex(int baseIdx){
int idx = baseIdx;
idx+=idx<<1;//idx*3
return idx;
}
private int baseIdx(int hash){
return hash & (hashes.length-1);
}
public V get(K1 key1, K2 key2){
final int hash = hash(key1, key2);
final int[] hashes = this.hashes;
final Object[] kvs = this.kvs;
final int mask = hashes.length-1;
for(int base = baseIdx(hash);;base=(base+1)&mask){
int k = kvsIndex(base);
K1 k1 = (K1) kvs[k];
if (k1==null)
return null;//null met; no such value
Object value;
if (hashes[base]!=hash || TOMBSTONE==(value=kvs[k+2]))
continue;//next
K2 k2 = (K2) kvs[k+1];
if ( (key1==k1 || key1.equals(k1)) && (key2==k2 || key2.equals(k2)) ){
return (V) value;
}
}
}
public boolean contains(K1 key1, K2 key2){
return get(key1, key2)!=null;
}
public boolean containsValue(final V value){
final Object[] kvs = this.kvs;
if (value==null)
return false;
for(int i=0;i<kvs.length;i+=3){
Object v = kvs[2];
if (v==null || v==TOMBSTONE)
continue;
if (value==v || value.equals(v))
return true;
}
return false;
}
public V put(K1 key1, K2 key2, V value){
int hash = hash(key1, key2);
return doPut(key1, key2, value, hash);
}
public V remove(K1 key1, K2 key2){
int hash = hash(key1, key2);
return doPut(key1, key2, null, hash);
}
//note, instead of remove a TOMBSTONE is used to mark the deletion
//this may leak keys but deletion doesn't need to shift the array like in Knuth 6.4
protected V doPut(final K1 key1, final K2 key2, Object value, final int hash){
//null value -> remove
int probes = 0;
final int[] hashes = this.hashes;
final Object[] kvs = this.kvs;
final int mask = hashes.length-1;
//conservative resize: when too many probes and the count is greater than the half of the capacity
for(int base = baseIdx(hash);probes<rehashOnProbes || count<(mask>>1);base=(base+1)&mask, probes++){
final int k = kvsIndex(base);
K1 k1 = (K1) kvs[k];
K2 k2;
//find a gap, or resize
Object old = kvs[k+2];
final boolean emptySlot = k1==null || (value!=null && old==TOMBSTONE);
if (emptySlot || (
hashes[base] == hash &&
(k1==key1 || k1.equals(key1)) &&
((k2=(K2) kvs[k+1])==key2 || k2.equals(key2)))
){
if (value==null){//remove()
if (emptySlot)
return null;//not found, and no value ->nothing to do
value = TOMBSTONE;
count-=2;//offset the ++later
}
if (emptySlot){//new entry, update keys
hashes[base] = hash;
kvs[k] = key1;
kvs[k+1] = key2;
}//else -> keys and hash are equal
if (old==TOMBSTONE)
old=null;
kvs[k+2] = value;
count++;
return (V) old;
}
}
resize();
return doPut(key1, key2, value, hash);//hack w/ recursion, after the resize
}
//optimized version during resize, doesn't check equals which is the slowest part
protected void doPutForResize(K1 key1, K2 key2, V value, final int hash){
final int[] hashes = this.hashes;
final Object[] kvs = this.kvs;
final int mask = hashes.length-1;
//find the 1st gap and insert there
for(int base = baseIdx(hash);;base=(base+1)&mask){//it's ensured, no equal keys exist, so skip equals part
final int k = kvsIndex(base);
K1 k1 = (K1) kvs[k];
if (k1!=null)
continue;
hashes[base] = hash;
kvs[k] = key1;
kvs[k+1] = key2;
kvs[k+2] = value;
return;
}
}
//resizes the map by doubling the capacity,
//the method uses altervative varian of put that doesn't check equality, or probes; just inserts at a gap
protected void resize(){
final int[] hashes = this.hashes;
final Object[] kvs = this.kvs;
final int capacity = nextCapacity(hashes.length);
this.hashes = new int[capacity];
this.kvs = new Object[kvsIndex(capacity)];
for (int i=0;i<hashes.length; i++){
int k = kvsIndex(i);
K1 key1 = (K1) kvs[k];
Object value = kvs[k+2];
if (key1!=null && TOMBSTONE!=value){
K2 key2 = (K2) kvs[k+1];
doPutForResize(key1, key2, (V) value, hashes[i]);
}
}
}
public static void main(String[] args) {
DoubleKeyMap<String, String, Integer> map = new DoubleKeyMap<String, String, Integer>(4,2);
map.put("eur/usd", "usd/jpy", 1);
map.put("eur/usd", "usd/jpy", 2);
map.put("eur/jpy", "usd/jpy", 3);
System.out.println(map.get("eur/jpy", "usd/jpy"));
System.out.println(map.get("eur/usd", "usd/jpy"));
System.out.println("======");
map.remove("eur/usd", "usd/jpy");
System.out.println(map.get("eur/jpy", "usd/jpy"));
System.out.println(map.get("eur/usd", "usd/jpy"));
System.out.println("======");
testResize();
}
static void testResize(){
DoubleKeyMap<String, Integer, Integer> map = new DoubleKeyMap<String, Integer, Integer>(18, 17);
long s = 0;
String pref="xxx";
for (int i=0;i<14000;i++){
map.put(pref+i, i, i);
if ((i&1)==1)
map.remove(pref+i, i);
else
s+=i;
}
System.out.println("sum: "+s);
long sum = 0;
for (int i=0;i<14000;i++){
Integer n = map.get(pref+i, i);
if (n!=null && n!=i){
throw new AssertionError();
}
if (n!=null){
System.out.println(n);
sum+=n;
}
}
System.out.println("1st sum: "+s);
System.out.println("2nd sum: "+sum);
}
}
Wenn Sie Millionen von Zeichenfolgen verketten, generiert string.concat höchstwahrscheinlich Millionen neuer String-Objektreferenzen. Dies hat eine erhöhte CPU-Auslastung zur Folge.
StringBuffer ccyPair = new StringBuffer();
ccyPair.append("ccy1").append("ccy2");
Haben Sie versucht, einen String-Puffer zu verwenden, und verwenden Sie dann einen Profiler, um herauszufinden, wo der Engpass liegt. Versuchen Sie, zu sehen, was passiert.
Die Antwort von @Duncan McGregor enthält einige Benchmark-Nummern für ein bestimmtes Beispiel (Größen der Eingabezeichenfolge) und eine JVM-Version. In diesem Fall sieht es so aus, als wäre String.concat()
der Gewinner durch einen signifikanten Faktor. Dieses Ergebnis kann verallgemeinern oder nicht.
Nebenbei: Das überrascht mich! Ich hätte gedacht, dass sich die Compiler-Autoren für die Verwendung von String.concat entschieden hätten, wenn es wahrscheinlich schneller ist. Die Erklärung findet sich in der Auswertung von diesem Fehlerbericht ... und basiert auf der Definition des String-Verkettungsoperators.
(Wenn ein String-typisierter Operand von +
null
ist, gibt die JLS an, dass der String "null"
an seiner Stelle verwendet wird. Das würde nicht funktionieren, wenn der Code s + s2
als s.concat(s2)
und generiert wird s
oder s2
war zufällig null
; Sie würden NPEs erhalten. Und der Fall von s == null
bedeutet, dass eine alternative Version von concat
das NPE-Problem nicht löst.
Die Antwort von @ unwind hat mir jedoch eine Idee für eine alternative Lösung gegeben, die die Notwendigkeit einer String-Verkettung vermeidet.
Wenn die Verkettungen von ccy1
und ccy2
nur aus zwei Schlüsseln bestehen, können Sie möglicherweise eine bessere Leistung erzielen, indem Sie eine spezielle Hash-Tabellenklasse definieren, die zwei statt eines Schlüssels benötigt. Es hätte Operationen wie:
public Object get(String key1, String key2) ...
public void put(String key1, String key2, Object value) ...
Der Effekt wäre wie ein Map<Pair<String, String>, Object>
(siehe @ KitsuneYMGs Antwort), mit der Ausnahme, dass Sie nicht jedes Mal Pair<String, String>
Objekte erstellen müssen, wenn Sie ein get
- oder ein put
-Objekt ausführen möchten. Der Nachteil ist:
Map
-Schnittstelle.Normalerweise würde ich das nicht empfehlen. Wenn die Zeichenfolgenverkettung und die Suche nach Karten wirklich ein kritischer Engpass sind, kann eine benutzerdefinierte Hash-Tabelle mit mehreren Schlüsseln zu einer erheblichen Beschleunigung führen.
Vielleicht können Sie das Problem umgehen, indem Sie die Hashwerte der beiden Zeichenfolgen einzeln berechnen und dann kombinieren, möglicherweise mit einer separaten Hashfunktion, die für ganze Zahlen funktioniert.
So etwas wie:
int h1 = ccy1.hashCode(), h2 = ccy2.hashCode(), h = h1 ^ h2;
Das könnte schneller sein, da das Verketten von Strings, nur um den Hash der Verkettung zu berechnen, verschwenderisch erscheint.
Beachten Sie, dass das oben genannte die beiden Hashes mit dem binären XOR (dem ^
-Operator) kombiniert, was häufig funktioniert, aber Sie möchten dies vielleicht genauer untersuchen.
Ok, was ist Ihre Frage? Nichts zu tun: Wenn Sie Strings verketten müssen, tun Sie es einfach. Es ist in Ordnung, dass Sie Ihren Code profiliert haben. Jetzt können Sie die Tatsache sehen, dass der Operator für Zeichenfolgenverkettung + automatisch die Methode append () von StringBuilder verwendet
StringBuilder ccyPair = new StringBuilder(ccy1)
ccyPair.append(ccy2);
gibt Ihnen keine ernsthaften Vorteile.
Der einzige ernsthafte Weg, Ihren Code zu optimieren, besteht wahrscheinlich darin, Ihr Design so zu ändern, dass die Verkettung überhaupt weggelassen wird. Tun Sie dies jedoch nur, wenn Sie es wirklich brauchen, d. H. Die Verkettung nimmt einen erheblichen Teil der CPU-Zeit in Anspruch.