Действительно ли строка concatenaion настолько медленная?

В настоящее время я просматриваю параметры String concat и штраф за общую производительность. И мой тестовый пример создает результаты, которые взорвут мой разум, я не уверен, что я что-то пропускаю.

Вот сделка: Doing "something"+"somethingElse" в java будет (во время компиляции) создавать новый StringBuilder каждый раз, когда это делается.

Для моего тестового сценария я загружаю файл с моего жесткого диска, который имеет 1661 строк примерных данных (классический "Lorem Ipsum" ). Этот вопрос не о производительности ввода/вывода, а о производительности различных методов строки concat.

public class InefficientStringConcat {

    public static void main(String[] agrs) throws Exception{
        // Get a file with example data:

        System.out.println("Starting benchmark");
        // Read an measure:
        for (int i = 0; i < 10; i++){
            BufferedReader in = new BufferedReader(
                    new InputStreamReader(new FileInputStream(new File("data.txt")))
            );

            long start = System.currentTimeMillis();
            // Un-comment method to test:
            //inefficientRead(in);
            //betterRead(in);
            long end = System.currentTimeMillis();
            System.out.println("Took "+(end-start)+"ms");

            in.close();
        }



    }

    public static String betterRead(BufferedReader in) throws IOException{
        StringBuilder b = new StringBuilder();
        String line;
        while ((line = in.readLine()) != null){
            b.append(line);
        }
        return b.toString();
    }

    public static String inefficientRead(BufferedReader in) throws IOException {
        String everything = "", line;
        while ((line = in.readLine()) != null){
            everything += line;
        }
        return everything;
    }
}

Как вы можете видеть, настройка для обоих тестов одинакова. Вот результаты:

Использование inefficientRead() -метод:

Starting benchmark
#1 Took 658ms
#2 Took 590ms
#3 Took 569ms
#4 Took 567ms
#5 Took 562ms
#6 Took 570ms
#7 Took 563ms
#8 Took 568ms
#9 Took 560ms
#10 Took 568ms

Использование betterRead() -метод

Starting benchmark
#1 Took 42ms
#2 Took 10ms
#3 Took 5ms
#4 Took 7ms
#5 Took 16ms
#6 Took 3ms
#7 Took 4ms
#8 Took 5ms
#9 Took 5ms
#10 Took 13ms

Я запускаю тесты с без дополнительных параметров с помощью команды java. Я запускаю MacMini3,1 с начала 2009 года и Sun JDK 7:

[[email protected] ~]$ java -version
java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) Client VM (build 23.5-b02, mixed mode)

Это поражает меня как очень тяжелую разницу. Я делаю что-то неправильно, измеряя это, или это должно произойти?

Ответ 1

Я делаю что-то неправильно, измеряя это, или это должно произойти?

Это должно произойти. Построение длинной строки с использованием повторной конкатенации строк - это известный анти-шаблон производительности: каждая конкатенация должна создать новую строку с копией исходной строки, а также копию дополнительной строки. Вы получаете производительность O (N 2). Когда вы используете StringBuilder, большую часть времени вы просто копируете дополнительную строку в буфер. Иногда буфер должен выходить за пределы пространства и его необходимо развернуть (путем копирования существующих данных в новый буфер), но это происходит не часто (из-за стратегии расширения буфера).

Подробнее см. статью о конкатенации строк - это очень старая статья, поэтому предшествует StringBuilder, но основные принципы не изменились, (В принципе StringBuilder похож на StringBuffer, но без синхронизации.)

Ответ 2

Это именно то, что должно произойти. betterRead принимает линейное время; inefficientRead принимает квадратичное время.