Компромисс между производительностью и простотой между String, StringBuffer и StringBuilder - PullRequest
6 голосов
/ 26 марта 2011

Вы когда-нибудь задумывались о последствиях этого изменения в языке программирования Java?

Класс String был задуман как неизменный класс (и это решение было преднамеренно продуманным).Но конкатенация строк очень медленная, я сам ее сравнил.Так родился StringBuffer.Действительно отличный класс, синхронизированный и действительно быстрый.Но некоторые люди были недовольны производительностью некоторых синхронизированных блоков, и был представлен StringBuilder.

Но при использовании String для объединения не слишком большого количества объектов неизменность класса делает его действительно естественным способом достижения безопасности потоков.Я могу понять использование StringBuffer, когда мы хотим управлять несколькими строками.Но вот мой первый вопрос:

  1. Если у вас есть, скажем, 10 или меньше строк, которые вы хотите добавить, например, вы бы обменяли простоту всего на несколько миллисекундво время выполнения?

    Я тоже тестировал StringBuilder.Это более эффективно, чем StringBuffer (улучшение всего на 10%).Но если в вашей однопоточной программе вы используете StringBuilder, что произойдет, если вы захотите иногда изменить дизайн для использования нескольких потоков?Вы должны изменить каждый экземпляр StringBuilder, и если вы забудете его, у вас будет какой-то странный эффект (учитывая возможное состояние гонки), который можно произвести.

  2. В этой ситуации вы бы обменяли производительность на часы отладки?

Хорошо, вот и все.Помимо простого вопроса (StringBuffer более эффективен, чем «+» и поточно-ориентирован, а StringBuilder быстрее, чем StringBuffer, но не поточнобезопасен), я хотел бы знать, когда их использовать.

(Важно: Iзнать различия между ними, это вопрос, связанный с архитектурой платформы и некоторыми проектными решениями.)

Ответы [ 5 ]

8 голосов
/ 26 марта 2011

Просто комментарий о вашем замечании «StringBuilders и потоки»: даже в многопоточных программах очень редко требуется создать строку в нескольких потоках. Как правило, каждый поток имеет некоторый набор данных и создает из них строку, часто путем объединения нескольких строк вместе. Затем они преобразуют эту StringBuilder в строку, и эту строку можно безопасно разделить между потоками.

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

Лично я хотел бы, чтобы StringBuffer не существовало - это было в фазе «давайте все синхронизировать» Java, что привело к Vector и Hashtable, которые были почти устарели из-за несинхронизированных ArrayList и HashMap классы из Java 2. Потребовалось немного времени, чтобы получить несинхронизированный эквивалент StringBuffer.

Так в основном:

  • Используйте строку, если вы не хотите выполнять манипуляции и хотите быть уверенными, что больше ничего не будет
  • Используйте StringBuilder для выполнения манипуляций, обычно в течение короткого периода
  • Избегайте StringBuffer, если вам действительно это действительно не нужно - и, как я уже сказал, я не могу вспомнить когда-либо , увидев ситуацию, в которой я бы использовал StringBuffer вместо StringBuilder, когда оба доступны.
8 голосов
/ 26 марта 2011

StringBuffer был в Java 1.0; это была не какая-либо реакция на медлительность или неизменность. Это также ни в коем случае не быстрее и не лучше, чем конкатенация строк; фактически компилятор Java компилирует

String s1 = s2 + s3;

в нечто вроде

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

Если вы мне не верите, попробуйте сами с дизассемблером (например, javap -c.)

Что касается "StringBuffer быстрее, чем конкатенация", относится к повторной конкатенации. В этом случае явное создание собственного StringBuffer yoir и его неоднократное использование лучше, чем разрешение компилятору создавать многие из них.

StringBuilder был введен в Java 5 по соображениям производительности, как вы говорите. Причина, по которой это имеет смысл, заключается в том, что StringBuffer / Builder практически никогда не используется совместно вне метода, который их создает: 99% их использования - это что-то вроде вышеупомянутого, где они создаются, используются для добавления нескольких строк вместе, а затем отбрасываются.

4 голосов
/ 26 марта 2011

В настоящее время и StringBuffer, и Builder являются бесполезными (с точки зрения производительности).Я объясняю, почему:

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

StringBuffer использовал NOT для копирования char [] при создании String (в необщем варианте);однако это было основным источником проблем, включая утечку огромного символа [] для маленьких строк.В 1.5 они решили, что копия char [] должна появляться каждый раз, и это фактически делает StringBuffer бесполезным (синхронизация была там, чтобы гарантировать, что никакие потоковые игры не смогут обмануть String).Это экономит память, хотя и в конечном итоге помогает GC (помимо явно уменьшенного занимаемого места), обычно char [] - это топ3 объектов, потребляющих память.

String.concat был и остается самым быстрым способом объединения2 строки (и только 2 ... или, возможно, 3).Имейте в виду, что он не выполняет дополнительную копию char [].

Возвращаясь к бесполезной части, теперь любой сторонний код может достичь той же производительности, что и StringBuilder.Даже в java1.1 у меня было имя класса AsycnStringBuffer, которое выполняло в точности то же самое, что и StringBuilder, но все равно выделяет больший char [], чем StringBuilder.Оба StrinBuffer / StringBuilder оптимизированы для небольших строк. По умолчанию вы можете видеть c-tor

  StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
    }

Таким образом, если вторая строка длиннее 16 символов, она получает еще одну копию основного символа [].Довольно не круто.

Это может быть побочным эффектом от попытки подгонки StringBuilder / Buffer и char [] в одну строку кэша (на x86) в 32-битной ОС ... но я не знаю, для чегоконечно.

Что касается примечаний о часах отладки и т. д. Используйте ваше мнение, я лично не припоминаю, чтобы когда-либо возникали какие-либо проблемы с операциями со строками, за исключением импл.структура, аналогичная веревке, для генератора sql JDO impl.


Редактировать: Ниже я проиллюстрирую то, что java-дизайнеры не делали для ускорения операций с String.Обратите внимание, что класс предназначен для пакета java.lang, и его можно поместить туда, только добавив его в путь к начальной загрузке.Однако, даже если этого не сделать (разница состоит из одной строки кода!), Это все равно будет быстрее, чем StringBuilder, шокирует?Класс сделал бы string1 + string2 + ... намного лучше, чем использование StringBuilder, но хорошо ...

package java.lang;

public class FastConcat {

    public static String concat(String s1, String s2){
        s1=String.valueOf(s1);//null checks
        s2=String.valueOf(s2);

        return s1.concat(s2);
    }

    public static String concat(String s1, String s2, String s3){
        s1=String.valueOf(s1);//null checks
        s2=String.valueOf(s2);
        s3=String.valueOf(s3);
        int len = s1.length()+s2.length()+s3.length();
        char[] c = new char[len];
        int idx=0;
        idx = copy(s1, c, idx);
        idx = copy(s2, c, idx);
        idx = copy(s3, c, idx);
        return newString(c);
    }
    public static String concat(String s1, String s2, String s3, String s4){
        s1=String.valueOf(s1);//null checks
        s2=String.valueOf(s2);
        s3=String.valueOf(s3);
        s4=String.valueOf(s4);

        int len = s1.length()+s2.length()+s3.length()+s4.length();
        char[] c = new char[len];
        int idx=0;
        idx = copy(s1, c, idx);
        idx = copy(s2, c, idx);
        idx = copy(s3, c, idx);
        idx = copy(s4, c, idx);
        return newString(c);

    }
    private static int copy(String s, char[] c, int idx){
        s.getChars(c, idx);
        return idx+s.length();

    }
    private static String newString(char[] c){
        return new String(0, c.length, c);
        //return String.copyValueOf(c);//if not in java.lang
    }
}
1 голос
/ 10 февраля 2012

Я попробовал то же самое на машине с XP.StringBuilder работает несколько быстрее, но если вы измените порядок прогона или сделаете несколько прогонов, вы заметите, что «почти фактор два» в результатах будет изменен на 10%:1003 * Для вашего вида теста JVM НЕ поможет.Я должен был ограничить количество прогонов и элементов только для того, чтобы получить ЛЮБОЙ результат теста "Только строка".

0 голосов
/ 19 декабря 2011

Решил проверить параметры с помощью простого упражнения XML. Тестирование проведено на 2,7 ГГц i5 с 16 ГБ оперативной памяти DDR3 для желающих воспроизвести результаты.

Код:

</p> <pre><code> private int testcount = 1000; private int elementCount = 50000; public void testStringBuilder() { long total = 0; int counter = 0; while (counter++ < testcount) { total += doStringBuilder(); } float f = (total/testcount)/1000; System.out.printf("StringBuilder build & output duration= %f µs%n%n", f); } private long doStringBuilder(){ long start = System.nanoTime(); StringBuilder buffer = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); buffer.append("<root>"); for (int i =0; i < elementCount; i++) { buffer.append("<data/>"); } buffer.append("</root>"); //System.out.println(buffer.toString()); output = buffer.toString(); long end = System.nanoTime(); return end - start; } public void testStringBuffer(){ long total = 0; int counter = 0; while (counter++ < testcount) { total += doStringBuffer(); } float f = (total/testcount)/1000; System.out.printf("StringBuffer build & output duration= %f µs%n%n", f); } private long doStringBuffer(){ long start = System.nanoTime(); StringBuffer buffer = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); buffer.append("<root>"); for (int i =0; i < elementCount; i++) { buffer.append("<data/>"); } buffer.append("</root>"); //System.out.println(buffer.toString()); output = buffer.toString(); long end = System.nanoTime(); return end - start; }

Результаты:

On OSX machine:

StringBuilder build & output duration= 1047.000000 µs 

StringBuffer build & output duration= 1844.000000 µs 


On Win7 machine:
StringBuilder build & output duration= 1869.000000 µs 

StringBuffer build & output duration= 2122.000000 µs

Похоже, что повышение производительности может зависеть от платформы, в зависимости от того, как JVM реализует синхронизацию.

Ссылки:

Использование System.nanoTime () было рассмотрено здесь -> Является ли System.nanoTime () полностью бесполезным? и здесь -> Как рассчитать время выполнения метода в Java? .

Источник для StringBuilder и StringBuffer здесь -> http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/lang/java.lang.htm

Хороший обзор синхронизации здесь -> http://www.javaworld.com/javaworld/jw-07-1997/jw-07-hood.html?page=1

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