String против StringBuilder против StringBuffer в Java

Введение Одним из наиболее часто используемых классов в Java является класс String. Он представляет собой строку (массив) символов и, следовательно, содержит текстовые данные, такие как «Hello World!». Помимо класса String, для аналогичных целей используются еще два, хотя и не так часто, - StringBuilder и StringBuffer. Каждый существует по своей причине, и, не зная о преимуществах других классов, многие начинающие программисты используют только строки, что приводит к снижению производительности и плохой масштабируемости.

Вступление

Один из наиболее часто используемых классов в Java - это класс String Он представляет собой строку (массив) символов и, следовательно, содержит текстовые данные, такие как «Hello World!». Помимо String , есть еще два других класса, которые используются для аналогичных целей, хотя и не так часто, - StringBuilder и StringBuffer .

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

Нить

Инициализировать строку так же просто, как:

 String string = "Hello World!"; 

Это нетипично, как и во всех других случаях, мы бы создали экземпляр объекта, используя new , тогда как здесь у нас есть «сокращенная» версия.

Есть несколько способов создать экземпляр String:

 // Most common, short way 
 String str1 = "Hello World"; 
 
 // Using the `new` keyword and passing text to the constructor 
 String str2 = new String("Hello World"); 
 
 // Initializing an array of characters and assigning them to a String 
 char[] charArray = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'}; 
 String str3 = new String(charArray); 

Давайте посмотрим на исходный код класса и сделаем несколько наблюдений:

 public final class String 
 implements java.io.Serializable, Comparable<String>, CharSequence { 
 /** The value is used for character storage. */ 
 private final char value[]; 
 
 /** 
 * Initializes a newly created {@code String} object so that it represents 
 * an empty character sequence. Note that use of this constructor is 
 * unnecessary since Strings are immutable. 
 */ 
 public String() { 
 this.value = new char[0]; 
 } 
 
 /** 
 * Allocates a new {@code String} so that it represents the sequence of 
 * characters currently contained in the character array argument. The 
 * contents of the character array are copied; subsequent modification of 
 * the character array does not affect the newly created string. 
 * 
 * @param value 
 * The initial value of the string 
 */ 
 public String(char value[]) { 
 this.value = Arrays.copyOf(value, value.length); 
 } 
 
 ... 
 } 

Сначала мы можем наблюдать, как сохраняется сам текст - в массиве char При этом для нас логично иметь возможность формировать String из массива символов.

Здесь действительно важно отметить тот факт, что String определен как final . Это означает, что String неизменяем .

Что это значит?

 String str1 = "Hello World!"; 
 str1.substring(1,4).concat("abc").toLowerCase().trim().replace('a', 'b'); 
 System.out.println(str1); 

Выход:

 Hello World! 

Поскольку String является final, ни один из этих методов не изменил его. Они просто вернули измененное состояние, которое мы нигде не использовали и не назначали. Каждый раз, когда вызывается метод String, создается новая String, состояние изменяется и возвращается.

Снова посмотрим на исходный код:

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

Исходная str никогда не изменяется. Его значение копируется, и к нему добавляется текст, который мы объединяем, после чего возвращается String

Если бы мы сделали что-то вроде этого:

 String str1 = "Hello World!"; 
 String str2 = str1.substring(1,4).concat("abc").toLowerCase().trim().replace('a', 'b'); 
 System.out.println(str2); 

Затем нас встретит вывод:

 ellbbc 

Теперь давайте посмотрим на эти две строки:

 String str1 = "qwerty"; 
 String str2 = "qwerty"; 

Когда мы создаем экземпляр String подобный этой, значение, в данном случае qwerty , сохраняется в Java Heap Memory, которая используется для распределения динамической памяти для всех Java-объектов.

Хотя в этом примере у нас есть две разные ссылочные переменные, обе они относятся только к одной области памяти в Java Heap Memory. Хотя может показаться, что существует два разных объекта String, на самом деле есть только один - str2 никогда не создается как объект, а скорее назначается объекту в памяти, который соответствует str1 .

Это происходит из-за того, как Java была оптимизирована для работы со строками. Каждый раз, когда вы хотите создать экземпляр объекта String, подобного этому, значение, которое вы хотите добавить в память кучи, сравнивается с ранее добавленными значениями. Если равное значение уже существует, объект не инициализируется, и значение присваивается ссылочной переменной.

Эти значения сохраняются в так называемом пуле строк , который содержит все буквальные значения String. Однако есть способ обойти это

  • используя ключевое слово new

Давайте посмотрим на другой пример:

 String str1 = "qwerty"; 
 String str2 = "qwerty"; 
 String str3 = new String("qwerty"); 
 
 System.out.println(str1 == str2); 
 System.out.println(str1 == str3); 
 System.out.println(str1.equals(str2)); 
 System.out.println(str1.equals(str3)); 

Выход:

 true 
 false 
 true 
 true 

Это логично, поскольку str1 и str2 указывают на один и тот же объект в памяти. str3 явно как new поэтому для него создается новый объект, даже если строковый литерал уже существует в пуле. Метод equals() сравнивает их значения, а не объекты, на которые они указывают, поэтому он возвращает true для всех этих строк.

Важно отметить, что substring() и concat() возвращают новый String и сохраняют его в пуле String.

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

StringBuffer и StringBuilder

Изменчивость

StringBuffer и StringBuilder основном содержат то же значение, что и String - последовательность символов. И StringBuffer и StringBuilder также являются изменяемыми, что означает, что после присвоения им значения это значение обрабатывается как атрибут объекта StringBuffer или StringBuilder .

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

StringBuilder против StringBuffer

Эти два класса почти идентичны друг другу - они используют методы с одинаковыми именами, которые возвращают одинаковые результаты. Хотя между ними есть два основных различия:

  • Безопасность потоков : методы StringBuffer синхронизируются , что означает, что только один поток может вызывать методы StringBuffer одновременно. С другой стороны, StringBuilder не синхронизируются, поэтому несколько потоков могут вызывать методы в StringBuilder без блокировки.

    Итак, мы пришли к выводу, что StringBuffer является потокобезопасным классом, а StringBuffer - нет.

    Это то, о чем вам следует беспокоиться? Может быть. Если вы работаете над приложением, которое использует несколько потоков, работа с StringBuilder может быть потенциально опасной.

  • Скорость : StringBuffer на самом деле в два-три раза медленнее, чем StringBuilder . Причиной этого является StringBuffer - разрешение на выполнение только одного потока на объекте за раз приводит к гораздо более медленному выполнению кода.

Методы

И StringBuffer и StringBuilder имеют одинаковые методы (помимо synchronized метода в StringBuilder ). Давайте рассмотрим некоторые из наиболее распространенных:

  • append()
  • insert()
  • replace()
  • delete()
  • reverse()

Как видите, название каждого метода в значительной степени описывает то, что он делает. Вот простая демонстрация:

 StringBuffer sb1 = new StringBuffer("Buffer no 1"); 
 System.out.println(sb1); 
 
 sb1.append(" - and this is appended!"); 
 System.out.println(sb1); 
 sb1.insert(11, ", this is inserted"); 
 System.out.println(sb1); 
 sb1.replace(7, 9, "Number"); 
 System.out.println(sb1); 
 sb1.delete(7, 14); 
 System.out.println(sb1); 
 sb1.reverse(); 
 System.out.println(sb1); 

Выход:

 Buffer no 1 
 Buffer no 1 - and this is appended! 
 Buffer no 1, this is inserted - and this is appended! 
 Buffer Number 1, this is inserted - and this is appended! 
 Buffer 1, this is inserted - and this is appended! 
 !dedneppa si siht dna - detresni si siht ,1 reffuB 

Строка против StringBuilder против StringBuffer


                      Нить   StringBuffer   StringBuilder

Мутабельный Нет да да Поточно-безопасный да да Нет Эффективное время Нет Нет да Эффективная память Нет да да


Примечание . Как видно из таблицы выше, String менее эффективен по времени и памяти, но это не значит, что мы никогда не должны использовать его снова.

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

Пример кода

Чтобы показать, насколько эффективны String , StringBuffer и StringBuilder , мы собираемся провести тест производительности:

 String concatString = "concatString"; 
 StringBuffer appendBuffer = new StringBuffer("appendBuffer"); 
 StringBuilder appendBuilder = new StringBuilder("appendBuilder"); 
 long timerStarted; 
 
 timerStarted = System.currentTimeMillis(); 
 for (int i = 0; i < 50000; i++) { 
 concatString += " another string"; 
 } 
 System.out.println("Time needed for 50000 String concatenations: " + (System.currentTimeMillis() - timerStarted) + "ms"); 
 
 timerStarted = System.currentTimeMillis(); 
 for (int i = 0; i < 50000; i++) { 
 appendBuffer.append(" another string"); 
 } 
 System.out.println("Time needed for 50000 StringBuffer appends: " + (System.currentTimeMillis() - timerStarted) + "ms"); 
 
 timerStarted = System.currentTimeMillis(); 
 for (int i = 0; i < 50000; i++) { 
 appendBuilder.append(" another string"); 
 } 
 System.out.println("Time needed for 50000 StringBuilder appends: " + (System.currentTimeMillis() - timerStarted) + "ms"); 

Выход:

 Time needed for 50000 String concatenations: 18108ms 
 Time needed for 50000 StringBuffer appends: 7ms 
 Time needed for 50000 StringBuilder appends: 3ms 

Этот вывод может отличаться в зависимости от вашей виртуальной машины Java. Итак, из этого тестового теста мы видим, что StringBuilder является самым быстрым в обработке строк. Далее идет StringBuffer , который в два-три раза медленнее, чем StringBuilder . И, наконец, у нас есть String который на сегодняшний день является самым медленным в обработке строк.

Использование StringBuilder привело к времени в ~ 6000 раз быстрее, чем у обычного String . То, что потребуется StringBuilder для объединения за 1 секунду, займет у String 1,6 часа (если бы мы могли объединить столько).

Заключение

Мы видели производительность String , StringBuffer и StringBuilder а также их плюсы и минусы. Теперь возникает последний вопрос:

Кто из них победитель?

Что ж, идеальный ответ на этот вопрос - «Это зависит от обстоятельств». Мы знаем, что String легко набирать, легко использовать и потокобезопасно. С другой стороны, они неизменяемы (что означает большее потребление памяти) и очень медленны при манипуляциях со строками.

StringBuffer являются изменяемыми, эффективными с точки зрения памяти и потокобезопасными. Их недостаток - скорость по сравнению с гораздо более быстрыми StringBuilder s.

Что касается StringBuilder s, они также изменяемы и эффективны с точки зрения памяти, они являются самыми быстрыми в обработке строк, но, к сожалению, они не являются потокобезопасными.

Если вы примете во внимание эти факты, вы всегда сделаете правильный выбор!

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus