it-swarm.com.de

Was ist der schnellste Weg, zwei Strings in Java zu verketten?

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)
31
Dan

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.

38
Duncan McGregor

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

}
17
sfosdal

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

Für die Frage im Titel gilt: String.concat ist normalerweise die schnellste Möglichkeit, zwei Strings zusammenzustellen (beachten Sie jedoch nulls). 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;
        }
    }
}
3
leoismyname

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.

1
KitsuneYMG

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); 
1
Axel

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

  • Sie können sehen, dass concat nur dann schneller als StringBuilder ist, wenn Nur 2 Strings verkettet
  • Beachten Sie, dass beim Hinzufügen von mehr und mehr Zeichenfolgen die resultierende StringBuilder-Zeit langsamer wird als bei Verwendung von concat
  • Beachten Sie, dass der Unterschied signifikanter ist, wenn die Zeichenfolgen sehr groß sind
0
ihebiheb

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


    }
}
0
bestsss

Wenn Sie Millionen von Zeichenfolgen verketten, generiert string.concat höchstwahrscheinlich Millionen neuer String-Objektreferenzen. Dies hat eine erhöhte CPU-Auslastung zur Folge.

0
ThatDataGuy
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.

0
Deepak

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:

  • sie müssen eine neue Hashtabellenklasse von Grund auf implementieren, und
  • die neue Klasse entspricht nicht der 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.

0
Stephen C

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.

0
unwind

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.

0
AlexR