Почему Alibaba не рекомендует использовать «+» для конкатенации строк в циклах for

Java

Строка — один из наиболее часто используемых типов данных в Java. Что касается знаний о строках, автор опубликовал несколько статей и многое представил, например:

Серия изучения исходного кода Java 7 (1) — строка

Как создать строку, используя " " или конструктор?

Наконец-то я понял эту мелочь о String

Три картинки, чтобы полностью понять неизменность строк в Java

Почему Java проектирует строки неизменяемыми

Три изображения, чтобы полностью понять принцип и различие подстроки в JDK 6 и JDK 7.

Конкретные детали реализации Switch в Java для целочисленных, символьных и строковых типов

Эта статья также является дополнением к знаниям, связанным со строками в Java, в основном для ознакомления со знаниями, связанными со сращиванием строк. Эта статья основана на jdk1.8.0_181.

Объединение строк {#toc_0}

Конкатенация строк — это то, что мы часто делаем в коде Java, а именно объединение нескольких строк вместе.

мы все знаем,String — это неизменяемый класс в Java., поэтому после создания экземпляра его нельзя изменить.

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

Но поскольку строки неизменяемы, как насчет конкатенации строк?

Неизменяемость строк и конкатенация строк

По сути, все так называемое сращивание строк заключается в том, чтобы регенерировать новую строку. Следующий фрагмент кода конкатенации строк:

String s = "abcd";
s = s.concat("ef");

На самом деле конечная s, которую мы получаем, уже является новой строкой. Как показано ниже

В s сохраняется ссылка на воссозданный объект String.

Итак, как в Java выполнить конкатенацию строк? Существует много способов объединения строк, вот некоторые из наиболее часто используемых.

использовать+объединить строки

В Java самый простой способ конкатенации строк — использовать символы напрямую.+к сращиванию. как:

String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
String hollis = wechat + "," + introduce;

Вот особый момент, некоторые люди используют Java+Функция конкатенации строк понимается какперегрузка оператора. Не совсем,Java не поддерживает перегрузку операторов. На самом деле это только один из предоставленных Javaсинтаксический сахар. Подробности позже.

Перегрузка операторов. В компьютерном программировании перегрузка операторов является типом полиморфизма. Перегрузка оператора заключается в переопределении существующего оператора и наделении его другой функцией для адаптации к различным типам данных.

Синтаксический сахар: Синтаксический сахар, также переводимый как «покрытая сахаром грамматика», — это термин, изобретенный британским ученым-компьютерщиком Питером Лендингом для обозначения определенной грамматики, добавленной к компьютерному языку, которая не влияет на функцию языка. удобно для программистов. Синтаксический сахар делает программы более краткими и читабельными.

concat
Помимо использования+В дополнение к объединению строк вы также можете использовать метод concat в классе String для объединения строк. как:

String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
String hollis = wechat.concat(",").concat(introduce);

StringBuffer

Что касается строк, в дополнение к определению строки, которую можно использовать для определениястроковая константаизStringВ дополнение к классам также предусмотрены, которые можно использовать для определениястроковая переменнаяизStringBufferКласс, объекты которого можно расширять и изменять.

использоватьStringBufferСтроки могут быть легко объединены. как:

StringBuffer wechat = new StringBuffer("Hollis");
String introduce = "每日更新Java相关技术文章";
StringBuffer hollis = wechat.append(",").append(introduce);

StringBuilder
КромеStringBufferКроме того, есть классStringBuilderтакже может использоваться, его использование иStringBufferпохожий. как:

StringBuilder wechat = new StringBuilder("Hollis");
String introduce = "每日更新Java相关技术文章";
StringBuilder hollis = wechat.append(",").append(introduce);

StringUtils.join
В дополнение к встроенному методу конкатенации строк в JDK вы также можете использовать имена методов конкатенации строк, предоставленные в некоторых библиотеках классов с открытым исходным кодом, таких какapache.commons中который предоставилStringUtilsкласса, из которыхjoinметод объединения строк.

String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
System.out.println(StringUtils.join(wechat, ",", introduce));

Вот краткое введение.Основная функция метода соединения, представленного в StringUtils, заключается в объединении массива или коллекции с помощью сплайсера для формирования новой строки, например:

String []list  ={"Hollis","每日更新Java相关技术文章"};
String result= StringUtils.join(list,",");
System.out.println(result);
//结果:Hollis,每日更新Java相关技术文章

Кроме того, класс String в Java8 также предоставляет метод статического соединения, который аналогичен по использованию StringUtils.join.

Выше приведены пять наиболее часто используемых способов объединения строк в Java, так какой из них лучше использовать? Почему не рекомендуется использовать его в теле цикла в Руководстве по разработке Java для Alibaba?+Как насчет конкатенации строк?

(Протокол о сращивании строк в Руководстве по разработке Java для Alibaba)

использовать+Принцип реализации соединения строк {#toc_1}

Как упоминалось ранее, использование+Конкатенация строк на самом деле является просто синтаксическим сахаром, предоставляемым Java, поэтому давайте объясним этот синтаксический сахар и посмотрим, как реализуются его внутренние принципы.

Еще этот кусок кода. Давайте декомпилируем сгенерированный им байт-код и посмотрим на результат.

String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
String hollis = wechat + "," + introduce;

Декомпилированное содержимое выглядит следующим образом, а инструмент декомпиляции — jad.

String wechat = "Hollis";
String introduce = "\u6BCF\u65E5\u66F4\u65B0Java\u76F8\u5173\u6280\u672F\u6587\u7AE0";//每日更新Java相关技术文章
String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();

Глядя на код после декомпиляции, мы можем обнаружить, что исходная строковая константа обрабатывается ее методом добавления после преобразования String в StringBuilder в процессе объединения.

То есть в Java+Сращивание струн осуществляется с помощьюStringBuilder.append.

Как реализовано объединение {#toc_2}

Давайте взглянем на исходный код метода concat и посмотрим, как этот метод реализован.

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

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

Из исходного кода мы также можем видеть, что после метода concat фактически создается новая строка, что также перекликается с проблемой инвариантности строки, о которой мы упоминали ранее.

StringBuffer и StringBuilder

Далее посмотримStringBufferиStringBuilderпринцип реализации.

иStringкласс похожий,StringBuilderКласс также инкапсулирует массив символов, определенный следующим образом:

char[] value;

иStringОтличие в том, что это неfinal, поэтому его можно изменить. Кроме того, сStringРазличные, не обязательно все позиции в массиве символов были использованы, у него есть переменная экземпляра, которая представляет количество символов, которые были использованы в массиве, определяемом следующим образом:

int count;

Исходный код добавления выглядит следующим образом:

public StringBuilder append(String str) {
    super.append(str);
    return this;
}

Этот класс наследуетAbstractStringBuilderкласс, посмотри на негоappendметод:

public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

append будет напрямую копировать символы во внутренний массив символов, если длины массива символов недостаточно, он будет расширен.

StringBufferиStringBuilderАналогично, самая большая разницаStringBufferявляется потокобезопасным, взглянитеStringBufferизappendметод.

public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

Этот метод используетsynchronizedОбъявите, что это потокобезопасный метод. иStringBuilderЭто не потокобезопасно.

Как реализован StringUtils.join {#toc_4}

просмотревStringUtils.joinВ исходном коде мы можем обнаружить, что, собственно, он тоже прошелStringBuilderбыть реализованным.

public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {
    if (array == null) {
        return null;
    }
    if (separator == null) {
        separator = EMPTY;
    }

    // endIndex - startIndex > 0:   Len = NofStrings *(len(firstString) + len(separator))
    //           (Assuming that all Strings are roughly equally long)
    final int noOfItems = endIndex - startIndex;
    if (noOfItems <= 0) {
        return EMPTY;
    }

    final StringBuilder buf = new StringBuilder(noOfItems * 16);

    for (int i = startIndex; i < endIndex; i++) {
        if (i > startIndex) {
            buf.append(separator);
        }
        if (array[i] != null) {
            buf.append(array[i]);
        }
    }
    return buf.toString();
}

Сравнение эффективности {#toc_5}

Поскольку существует так много способов конкатенации строк, какой из них наиболее эффективен? Проведем простое сравнение.

long t1 = System.currentTimeMillis();
//这里是初始字符串定义
for (int i = 0; i < 50000; i++) {
    //这里是字符串拼接代码
}
long t2 = System.currentTimeMillis();
System.out.println("cost:" + (t2 - t1));

Мы используем код приведенной выше формы для проверки времени работы каждого из пяти кодов конкатенации строк. Результат выглядит следующим образом:

+ cost:5119
StringBuilder cost:3
StringBuffer cost:4
concat cost:3623
StringUtils.join cost:25726

Как видно из результатов, сравнение времени от короткого к длинному составляет:

StringBuilder<StringBuffer<concat<+<StringUtils.join

StringBufferсуществуетStringBuilderНа основе синхронной обработки это займет относительно больше времени.

StringUtils.join тоже использует StringBuilder, и в нем много других операций, так что это занимает много времени, и это легко понять. На самом деле StringUtils.join лучше справляется с конкатенацией строковых массивов или списков.

Затем возникает проблема, которую мы проанализировали ранее, фактически используя+Также используется принцип реализации сращивания строкStringBuilder, тогда почему результаты такие разные, до более чем 1000 раз?

Давайте декомпилируем следующий код:

long t1 = System.currentTimeMillis();
String str = "hollis";
for (int i = 0; i < 50000; i++) {
    String s = String.valueOf(i);
    str += s;
}
long t2 = System.currentTimeMillis();
System.out.println("+ cost:" + (t2 - t1));

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

long t1 = System.currentTimeMillis();
String str = "hollis";
for(int i = 0; i < 50000; i++)
{
    String s = String.valueOf(i);
    str = (new StringBuilder()).append(str).append(s).toString();
}

long t2 = System.currentTimeMillis();
System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());

Мы видим, что декомпилированный код вforВ цикле каждый разnewвзял одинStringBuilder, а затем положитьStringПревратиться вStringBuilder, а затем продолжитьappend.

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

Поэтому в Руководстве по разработке Java для Alibaba рекомендуются: тело цикла, метод подключения строк, использованиеStringBuilderизappendметод расширения. Вместо того, чтобы использовать+.

Резюме {#toc_6}

В этой статье рассказывается, что такое сращивание строк.Хотя строки неизменяемы, их все же можно сплайсировать путем создания новых строк.

Существует пять широко используемых методов сращивания строк, которые следует использовать.+,использоватьconcat,использоватьStringBuilder,использоватьStringBufferи использоватьStringUtils.join.

Поскольку новые объекты создаются во время конкатенации строк, необходимо учитывать проблемы с памятью и эффективностью, если конкатенация строк должна выполняться в теле цикла.

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

Однако следует также подчеркнуть, что:

1. Если строка не конкатенирована в теле цикла, используйте ее напрямую+Достаточно.

2. Если конкатенация строк выполняется в параллельных сценариях, используйтеStringBufferзаменитьStringBuilder.