Какой самый быстрый способ объединить две строки в Java? - PullRequest
33 голосов
/ 22 февраля 2011

Какой самый быстрый способ объединить две строки в Java?

т.е.

String ccyPair = ccy1 + ccy2;

Я использую cyPair в качестве ключа в HashMap, и он вызывается в очень узком цикле для получения значений.

Когда я в профиле, это узкое место

java.lang.StringBuilder.append(StringBuilder.java:119)  
java.lang.StringBuilder.(StringBuilder.java:93)

Ответы [ 15 ]

41 голосов
/ 04 марта 2011

Много теории - время для практики!

private final String s1 = new String("1234567890");
private final String s2 = new String("1234567890");

Использование обычного для циклов 10 000 000, в разогретой 64-разрядной точке доступа, 1.6.0_22 в Intel Mac OS.

например,

@Test public void testConcatenation() {
    for (int i = 0; i < COUNT; i++) {
        String s3 = s1 + s2;
    }
}

Со следующими утверждениями в циклах

String s3 = s1 + s2; 

1,33

String s3 = new StringBuilder(s1).append(s2).toString();

1,28 с

String s3 = new StringBuffer(s1).append(s2).toString();

1,92 с

String s3 = s1.concat(s2);

0,70 с

String s3 = "1234567890" + "1234567890";

0,0 с

Таким образом, concat является явным победителем, если у вас нет статических строк, в этом случаекомпилятор позаботится о вас уже.

19 голосов
/ 22 февраля 2011

Причина, по которой эти процедуры отображаются в бенчмарке, заключается в том, что именно так компилятор реализует ваше "+" под прикрытием.

Если вам действительно нужна объединенная строка, вы должны позволить компилятору выполнить еемагия с "+".Если вам всем нужен ключ для поиска по карте, класс ключей, содержащий обе строки с подходящими реализациями equals и hashMap, может быть хорошей идеей, поскольку он избегает этапа копирования.

18 голосов
/ 17 февраля 2012

Я полагаю, что ответ, возможно, уже был определен, но я публикую пост, чтобы поделиться кодом.

Краткий ответ, если чистая конкатенация - это все, что вы ищете, это: String.concat (...)

Выход:

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

Пример кода:

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

}
7 голосов
/ 16 сентября 2014

Вы должны тестировать со строкой, сгенерированной во время выполнения (например, UUID.randomUUID (). ToString ()), а не во время компиляции (например, "моя строка"). Мои результаты

plus:     118 ns
concat:    52 ns
builder1: 102 ns
builder2:  66 ns
buffer1:  119 ns
buffer2:   87 ns

с этой реализацией:

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();
}
5 голосов
/ 22 февраля 2011

Для вопроса в заголовке: String.concat обычно будет самым быстрым способом объединения двух String с (но обратите внимание null с).[Увеличенный] промежуточный буфер или другой объект не задействован.Странно, но + компилируется в относительно неэффективный код, включающий StringBuilder.

. Однако основная часть вашего вопроса указывает на другие проблемы.Конкатенация строк для генерации ключей для карты является распространенным «анти-идиомой».Это взломать и подвержен ошибкам.Вы уверены, что сгенерированный ключ уникален?Будет ли он оставаться уникальным после того, как ваш код будет поддерживаться для некоторых пока неизвестных требований?Наилучший подход - создать класс неизменяемых значений для ключа.Использование List и универсального класса кортежей - неаккуратный хак.

3 голосов
/ 27 ноября 2012

Для меня метод concat3, как показано ниже, является самым быстрым способом после выполнения теста на моей Windows и удаленной машине Linux: - Хотя я считаю, что производительность concat1 зависит от реализации и оптимизации JVM и может работать лучше в будущей версии

    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;
        }
    }
}
1 голос
/ 22 февраля 2011

Я бы порекомендовал попробовать предложение Thorbjørn Ravn Andersens.

Если вам нужны сцепленные строки, в зависимости от длины двух частей, может быть немного лучше создать экземпляр StringBuilder с требуемым размеромчтобы избежать перераспределения.Конструктор StringBuilder по умолчанию резервирует 16 символов в текущей реализации - по крайней мере, на моем компьютере.Поэтому, если объединенная строка длиннее исходного размера буфера, StringBuilder должен перераспределить.

Попробуйте и расскажите нам, что ваш профилировщик должен сказать об этом:

StringBuilder ccyPair = new StringBuilder(ccy1.length()+ccy2.length());
ccyPair.append(ccy1); 
ccyPair.append(ccy2); 
1 голос
/ 22 февраля 2011

Возможно, вместо конкатенации, вы должны создать класс Pair?

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

}

И использовать это как ключ в своей HashMap

Вместо HashMap<String,Whatever> используйте HashMap<Pair<String,String>,Whatever>

В вашем узком цикле вместо map.get( str1 + str2 ) вы бы использовали map.get( Pair.create(str1,str2) ).

0 голосов
/ 22 января 2019

Согласно спецификации Java ( и начиная с самой первой версии Java ), в разделе "Оператор конкатенации строк +" говорится, что:

Для повышения производительности многократного объединения строк, Java Компилятор может использовать класс StringBuffer или аналогичный метод для уменьшить количество промежуточных объектов String, которые создаются оценка выражения

Таким образом, использование + operator или StringBuilder.append для переменных в основном одинаково.


Другое дело, я знаю, что в своем вопросе вы упомянули добавление только 2 строк, но имейте в виду, что добавление 3 или более строк приведет к различным результатам:

Я использовал слегка модифицированный пример @Duncan McGregor. У меня есть 5 методов, объединяющих от 2 до 6 строк, используя concat, и 5 методов, объединяющих от 2 до 6 строк, используя StringBuilder:

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

Я получил эти результаты (в секундах):

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

  • Вы можете видеть, что concat быстрее, чем StringBuilder, только когда объединение только 2 строк
  • Обратите внимание, что разница будет более значительной, когда строки очень длинные
0 голосов
/ 07 января 2019

Помните, что если вы объединяете миллионы строк, то string.concat, скорее всего, сгенерирует миллионы новых ссылок на строковые объекты.Это увеличит нагрузку на процессор.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...