String, StringBuffer і StringBuilder


213

Скажіть , будь ласка , реальну ситуацію часу , щоб порівняти String, StringBufferі StringBuilder?

Відповіді:


378

Різниця змінності:

Stringє незмінним , якщо ви намагаєтесь змінити їх значення, створюється інший об'єкт, тоді як StringBufferі StringBuilderє змінним , так що вони можуть змінити їх значення.

Різниця безпеки -

Різниця між StringBufferі StringBuilderполягає в тому, що StringBufferбезпечно для потоків. Тож коли додаток потрібно запускати лише в один потік, тоді краще використовувати StringBuilder. StringBuilderє більш ефективним, ніж StringBuffer.

Ситуації:

  • Якщо ваш рядок не збирається змінювати, використовуйте клас String, оскільки Stringоб'єкт є незмінним.
  • Якщо ваш рядок може змінюватися (наприклад: багато логіки та операцій при побудові рядка) і доступ до нього буде доступний лише з однієї нитки, використовуючи a StringBuilder, досить добре.
  • Якщо ваш рядок може змінюватися і до нього можна отримати доступ з декількох потоків, використовуйте, StringBufferоскільки StringBufferце синхронізовано, щоб у вас була безпека потоку.

16
Також використання String для логічних операцій відбувається досить повільно, і зовсім не рекомендується, оскільки JVM перетворює String в StringBuffer у байт-коді. Багато накладних витрат витрачається на перетворення з String в StringBuffer, а потім знову на String.
Пітер ван Нікерк

2
Отже, Stringsколи ми змінюємо значення, створюється інший об’єкт. Чи дана посилання на старий об'єкт є недійсною, щоб воно могло бути зібране сміттям GCабо навіть зібране сміття?
Суворий Вардан

@PietervanNiekerk Що ви маєте на увазі під логічними операціями?
emlai

Логічні операції я маю на увазі основні рядкові рядки. Тепер я хотів би запитати одне, що, як стверджує @Peter, слід починати використовувати StringBuffer замість String у всіх випадках, або є якісь конкретні випадки?
JavaDragon

@bakkal Можу чи я використовувати всі методи Stringз StringBuilder?
roottraveller

47
  • Ви використовуєте, Stringколи незмінна структура доречна; отримання нової послідовності символів від aString може спричинити неприйнятне покарання за продуктивність, як у процесі процесора, так і в пам’яті (отримання підрядків є ефективним процесором, тому що дані не копіюються, але це означає, що може залишатися виділений значно більший обсяг даних).
  • Ви використовуєте, StringBuilderколи вам потрібно створити змінну послідовність символів, як правило, щоб об'єднати кілька послідовностей символів разом.
  • Ви використовуєте StringBufferв тих самих обставинах, що і ви StringBuilder, але коли зміни в нижній строці повинні бути синхронізовані (оскільки декілька потоків читають / змінюютьбуфер буфера).

Дивіться приклад тут .


2
лаконічна, але неповна, вона пропускає основну причину використання StringBuilder / Buffer, а це зменшення або усунення копії перерозподілу та масиву регулярної поведінки конфанації String.

1
"Ви використовуєте String, коли маєте справу з незмінними рядками" - не має сенсу. Екземпляри String ARE непорушні, тому, можливо, в коментарі слід сказати "Використовувати String, коли використання пам'яті через незмінність не має значення". Прийнята відповідь досить добре висвітлює його бази.
fooMonster

28

Основи:

Stringце незмінний клас, його неможливо змінити. StringBuilder- це клас, що змінюється, до якого можна додати, символи замінено або видалити і в кінцевому підсумку перетворити на а String StringBuffer- це оригінальна синхронізована версіяStringBuilder

Вам слід віддати перевагу StringBuilderв усіх випадках, коли у вас є лише одна нитка, що отримує доступ до вашого об'єкта.

Деталі:

Також зауважте, що StringBuilder/Buffers це не магія, вони просто використовують масив як резервний об'єкт, і що масив повинен бути перерозподілений, коли він заповниться. Будьте впевнені та створюйте свої StringBuilder/Bufferоб'єкти достатньо великими спочатку там, де їх не потрібно постійно змінювати за розміром кожного разу, коли вас .append()викликають.

Повторне розмір може стати дуже виродженим. В основному він переозброює резервний масив у 2 рази більше його поточного розміру кожного разу, коли його потрібно розширювати. Це може призвести до того, що велика кількість оперативної пам’яті буде виділено і не використовується, колиStringBuilder/Buffer заняття починають зростати.

У Java String x = "A" + "B";використовується StringBuilderкулуар. Так що для простих випадків немає користі заявляти про своє. Але якщо ви будуєте Stringоб'єкти з великими розмірами, скажімо, меншими за 4 кк, то декларування StringBuilder sb = StringBuilder(4096);набагато ефективніше, ніж конкатенація або використання конструктора за замовчуванням, який становить лише 16 символів. Якщо у вас Stringбуде менше 10 к, тоді ініціалізуйте його з конструктором до 10 к, щоб бути безпечним. Але якщо це ініціалізація до 10k, то ви пишете на 1 символ більше 10k, він буде перерозподілений та скопійований у масив 20k. Тож ініціалізація високого краще, ніж низького.

У випадку автоматичного зміни розміру на 17-му символі резервний масив перерозподіляється та копіюється на 32 символи, у 33-го символу це повторюється, і ви отримуєте можливість перерозподілити та скопіювати масив на 64 символи. Ви можете бачити, як це вироджується до безлічі перерозподілів та копій, що ви справді намагаєтесь уникати використання StringBuilder/Bufferв першу чергу.

Це з вихідного коду JDK 6 для AbstractStringBuilder

   void expandCapacity(int minimumCapacity) {
    int newCapacity = (value.length + 1) * 2;
        if (newCapacity < 0) {
            newCapacity = Integer.MAX_VALUE;
        } else if (minimumCapacity > newCapacity) {
        newCapacity = minimumCapacity;
    }
        value = Arrays.copyOf(value, newCapacity);
    }

Найкраща практика - ініціалізувати StringBuilder/Bufferтрохи більший розмір, ніж ти думаєш, що тобі знадобиться, якщо ти не знаєш прямо, наскільки великою Stringбуде воля, але ти можеш здогадатися. Один розподіл трохи більше пам’яті, ніж потрібно, буде кращим, ніж безліч перерозподілів та копій.

Також остерігайтеся ініціалізації символу a StringBuilder/Bufferз String, який буде виділяти лише розмір String + 16 символів, що в більшості випадків просто запустить вироджений цикл перерозподілу та копіювання, яких ви намагаєтеся уникнути. Далі йде прямо з вихідного коду Java 6.

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

Якщо ви випадково опинитесь на екземплярі, StringBuilder/Bufferякий ви не створили і не можете керувати конструктором, який викликається, є спосіб уникнути виродженої поведінки перерозподілу та копіювання. Зателефонуйте .ensureCapacity()в той розмір, який ви хочете, щоб переконатися, що ваш результат Stringпідходить.

Альтернативи:

Як зауваження, якщо ви робите по-справжньому важкі String будівництво та маніпуляції, існує набагато більш орієнтована на ефективність альтернатива під назвою Мотузки .

Інша альтернатива - створити StringListімплементацію шляхом підкласифікації ArrayList<String>та додавання лічильників для відстеження кількості символів у всіх .append()та інших операціях з мутації списку, а потім замінити .toString()для створення StringBuilderпотрібного вам потрібного розміру та прокрутити список та скласти Вихід, ви навіть можете зробити цю StringBuilderзмінну екземпляра і "кешувати" результати, .toString()і лише повторно генерувати їх, коли щось зміниться.

Також не забувайте про String.format()створення фіксованого форматованого виводу, який може бути оптимізований компілятором, оскільки вони покращують його.


1
Чи String x = "A" + "B";справді компілюється як StringBuilder? Чому б його не просто компілювати String x = "AB";, він повинен використовувати StringBuilder, лише якщо компоненти не відомі під час компіляції.
Метт Грір

це може оптимізувати константи String, я не можу згадати, коли останній раз декомпілювали байт-код, але я знаю, що якщо там є якісь змінні, він точно використовуватиме StringBuilder. Ви можете завантажити вихідний код JDK та дізнатися самі. "A" + "B" - це надуманий приклад напевно.

Мені було цікаво про String.format (). Я ніколи не бачив, щоб він використовувався в проектах. Зазвичай це StringBuilder. Ну, зазвичай це насправді "A" + "B" + "C", тому що люди ліниві;) Я прагнув завжди використовувати StringBuilder, навіть якщо це було об'єднано лише два рядки, тому що в майбутньому, можливо, буде додано більше рядків . Я ніколи не використовував String.format () головним чином тому, що ніколи не пам’ятав, в який JDK він був введений - я бачу, що це JDK1.5, я би використовував його на користь інших варіантів.
jamiebarrow

9

Ви маєте на увазі конкатенацію?

Приклад із реального світу: Ви хочете створити нову рядок із багатьох інших .

Наприклад, щоб надіслати повідомлення:

Рядок

String s = "Dear " + user.name + "<br>" + 
" I saw your profile and got interested in you.<br>" +
" I'm  " + user.age + "yrs. old too"

StringBuilder

String s = new StringBuilder().append.("Dear ").append( user.name ).append( "<br>" ) 
          .append(" I saw your profile and got interested in you.<br>") 
          .append(" I'm  " ).append( user.age ).append( "yrs. old too")
          .toString()

Або

String s = new StringBuilder(100).appe..... etc. ...
// The difference is a size of 100 will be allocated upfront as  fuzzy lollipop points out.

StringBuffer (синтаксис точно такий, як у StringBuilder, ефекти відрізняються)

Про

StringBuffer vs. StringBuilder

Перший синхронізований, а пізніше - ні.

Отже, якщо ви будете викликати його кілька разів в одному потоці (що становить 90% випадків), StringBuilderвін запуститься набагато швидше, оскільки він не зупиниться, щоб побачити, чи є у нього блокування потоку.

Отже, його рекомендується використовувати StringBuilder(якщо, звичайно, у вас є одночасно більше ніж один потік), що рідко)

Stringконкатенація ( за допомогою оператора + ) може бути оптимізована компілятором для використання StringBuilderвнизу, тож, це вже не про що турбуватися, в старіші дні Java це було те, про що всі кажуть, що слід уникати будь-якої ціни, тому що кожного конкатенації створили новий об'єкт String. Сучасні компілятори більше не роблять цього, але все-таки є хорошою практикою використовувати StringBuilderнатомість на випадок, якщо ви використовуєте "старий" компілятор.

редагувати

Тільки для тих, хто цікавий, ось що робить компілятор для цього класу:

class StringConcatenation {
    int x;
    String literal = "Value is" + x;
    String builder = new StringBuilder().append("Value is").append(x).toString();
}

javap -c StringConcatenation

Compiled from "StringConcatenation.java"
class StringConcatenation extends java.lang.Object{
int x;

java.lang.String literal;

java.lang.String builder;

StringConcatenation();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   new #2; //class java/lang/StringBuilder
   8:   dup
   9:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   12:  ldc #4; //String Value is
   14:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   17:  aload_0
   18:  getfield    #6; //Field x:I
   21:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   24:  invokevirtual   #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   27:  putfield    #9; //Field literal:Ljava/lang/String;
   30:  aload_0
   31:  new #2; //class java/lang/StringBuilder
   34:  dup
   35:  invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   38:  ldc #4; //String Value is
   40:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   43:  aload_0
   44:  getfield    #6; //Field x:I
   47:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   50:  invokevirtual   #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   53:  putfield    #10; //Field builder:Ljava/lang/String;
   56:  return

}

Рядки з цифрами 5 - 27 призначені для рядка під назвою "буквальний"

Рядки з номером 31-53 призначені для рядка під назвою "builder"

Ther не має різниці, точно однаковий код виконується для обох рядків.


2
це справді поганий приклад. Ініціалізація StringBuilder з "Dear" означає, що перший .append () призведе до перерозподілу та копіювання. Повністю заперечуючи будь-яку ефективність, яку ви намагаєтеся отримати над "нормальною" конкатенацією. Кращим прикладом може бути створення його з початковим розміром, який міститиме весь вміст остаточного рядка.

1
Взагалі НЕ є доброю практикою використовувати StringBuilderоб'єднання рядків String в правій частині завдання. Будь-яка хороша реалізація використовуватиме StringBuilder за кадром, як ви кажете. Крім того, ваш приклад "a" + "b"буде складений в один буквальний, "ab"але якщо ви використовуєте, StringBuilderце призведе до двох зайвих дзвінків на append().
Марк Петерс

@Mark Я не хотів використовувати, "a"+"b"але щоб сказати, що було об'єднанням String, я змінив це на явне. Чого ти не кажеш, чому це не є хорошою практикою цього робити. Саме так робить (сучасний) компілятор. @fuzzy, погоджуюся, спеціально, коли ви знаєте, який би розмір остаточного рядка (aprox).
OscarRyz

1
Я не думаю, що це особливо погана практика, але я, звичайно, не хотів би жодної рекомендації робити це взагалі. Це незграбно і важко читати, і надмірно багатослівний. Крім того, це заохочує помилки на зразок вашої, коли ви розбиваєте два літерали, які в іншому випадку можна скласти як один. Тільки якби профілінг сказав мені, що це змінило значення, я зробив би це так, як ти.
Марк Петерс

@ Марк Зрозумів. Я думав більше про "великий шматок коду, як шаблон", а не насправді у кожному регулярному рядковому рядку. Але так, я погоджуюсь, оскільки вони роблять те саме, що й зараз, це не має сенсу (10 років тому було приводом відхилити зміну коду версії) :)
OscarRyz

8
-------------------------------------------------- --------------------------------
                  String StringBuffer StringBuilder
-------------------------------------------------- --------------------------------                 
Зберігання | Постійний струнний басейн Heap Heap
Модифікується | Ні (незмінний) Так (змінний) Так (змінний)
Нитка безпечна | Так Так Ні
 Продуктивність | Швидкий Дуже повільний Швидкий
-------------------------------------------------- --------------------------------

Чому продуктивність String є швидкою, а продуктивність StringBuffer дуже повільною?
Гаурав

1
@gaurav ви можете прочитати його вихідний код, усі методи в StringBuffer є, synchronisedі ось чому .
Херен

8

Струнна родина

Рядок

String classЯвляє собою рядок символів. Всі рядкові літерали в програмі Java, такі як "abc"реалізовані як екземпляри цього класу.

Об'єкти струни незмінні після їх створення ми не можемо змінити. ( Струни - константи )

  • Якщо рядок створюється з допомогою конструктора або методу , то ці рядки будуть зберігатися в динамічної пам'яті , а також SringConstantPool. Але перед збереженням у пулі він запускає intern()метод перевірити наявність об'єкта з однаковим вмістом у пулі, використовуючи метод рівняння. Якщо String-copy доступний у пулі, то повертає посилання. В іншому випадку об'єкт String додається до пулу і повертає посилання.

    • Мова Java забезпечує спеціальну підтримку оператора конкатенації рядків ( +) та перетворення інших об'єктів у рядки. Зв'язування рядків реалізується через клас StringBuilder (або StringBuffer) та його метод додавання.

    String heapSCP = new String("Yash");
    heapSCP.concat(".");
    heapSCP = heapSCP + "M";
    heapSCP = heapSCP + 777;
    
    // For Example: String Source Code 
    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
  • Літеральні рядки зберігаються в StringConstantPool.

    String onlyPool = "Yash";

StringBuilder і StringBuffer - це змінна послідовність символів. Це означає, що можна змінити значення цих об'єктів. StringBuffer має ті самі методи, що і StringBuilder, але кожен метод у StringBuffer синхронізований, таким чином, він є безпечним для потоків.

  • Дані StringBuffer і StringBuilder можна створити лише за допомогою нового оператора. Отже, вони зберігаються в пам'яті Heap.

  • Примірники StringBuilder не безпечні для використання кількома потоками. Якщо потрібна така синхронізація, рекомендується використовувати StringBuffer.

    StringBuffer threadSafe = new StringBuffer("Yash");
    threadSafe.append(".M");
    threadSafe.toString();
    
    StringBuilder nonSync = new StringBuilder("Yash");
    nonSync.append(".M");
    nonSync.toString();
  • StringBuffer і StringBuilder надають спеціальні методи , такі як., replace(int start, int end, String str)І reverse().

    ПРИМІТКА : StringBuffer і SringBuilder є зміненими, оскільки вони забезпечують реалізацію Appendable Interface.


Коли використовувати який.

  • Якщо ви не збираєтесь змінювати значення кожен раз, тоді його краще використовувати String Class. Як частина Generics, якщо ви хочете сортувати Comparable<T>або порівнювати значення, тоді переходьте String Class.

    //ClassCastException: java.lang.StringBuffer cannot be cast to java.lang.Comparable
    Set<StringBuffer> set = new TreeSet<StringBuffer>();
    set.add( threadSafe );
    System.out.println("Set : "+ set);
  • Якщо ви збираєтесь змінювати значення кожного разу, перейдіть на StringBuilder, який швидше, ніж StringBuffer. Якщо кілька потоків змінюють значення, перейдіть для StringBuffer.


4

Крім того, StringBufferє безпечним для ниток, чого StringBuilderнемає.

Тож у ситуації в реальному часі, коли до нього звертаються різні потоки, це StringBuilderможе мати неефективні результати.


3

Зауважте, що якщо ви використовуєте Java 5 або новішу версію, слід використовувати її StringBuilderзамість StringBuffer. З документації API:

Починаючи з версії JDK 5, цей клас був доповнений еквівалентним класом , призначеного для використання в одному потоці, StringBuilder. StringBuilderКлас , як правило , повинні бути використані в перевазі до цього, так як вона підтримує все ті ж операції , але це швидше, так як він не виконує синхронізацію.

На практиці ви майже ніколи не будете використовувати це з декількох потоків одночасно, тому синхронізація, яка StringBufferце робиться, майже завжди зайва накладні витрати.


3

Особисто я не думаю, що для цього немає жодного реального використання StringBuffer. Коли мені хотілося б спілкуватися між декількома потоками, маніпулюючи послідовністю символів? Це зовсім не здається корисним, але, можливо, мені ще належить побачити світло :)


3

Різниця між String та іншими двома класами полягає в тому, що String є незмінним, а інші два - класами, що змінюються.

Але чому ми маємо два класи з однаковою метою?

Причина в тому, що StringBufferнитка безпечна і StringBuilderні. StringBuilderце новий клас, StringBuffer Apiі він був введений в JDK5і завжди рекомендується, якщо ви працюєте в єдиному потоковому середовищі, оскільки це багатоFaster

Для отримання повної інформації ви можете прочитати http://www.codingeek.com/java/stringbuilder-and-stringbuffer-a-way-to-create-mutable-strings-in-java/


2

У java String незмінна. Будучи непорушним, ми маємо на увазі, що після створення String ми не можемо змінити її значення. StringBuffer можна змінювати. Після створення об’єкта StringBuffer ми просто додаємо вміст до значення об'єкта замість створення нового об'єкта. StringBuilder схожий на StringBuffer, але він не є безпечним для потоків. Методи StingBuilder не синхронізовані, але порівняно з іншими струнами Stringbuilder працює найшвидше. Ви можете дізнатися різницю між String, StringBuilder і StringBuffer , реалізуючи їх.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.