#java #string-formatting #string-concatenation #mud
#java #форматирование строки #строка-конкатенация #грязь
Вопрос:
В данный момент я пишу MUD (текстовую игру), используя Java. Одним из основных аспектов MUD является форматирование строк и отправка их обратно пользователю. Как это лучше всего выполнить?
Допустим, я хотел отправить следующую строку:
Вы говорите кому-то «Привет!» — где «Кто-то», «сказать» и «Привет!» — это все переменные. Что было бы наилучшим с точки зрения производительности?
"You " verb " to " user " "" text """
или
String.format("You %1$s to %2$s "%3$s"", verb, user, text)
или какой-либо другой вариант?
Я не уверен, что в конечном итоге будет проще в использовании (что важно, потому что это будет везде), но я думаю об этом на данный момент, потому что объединение с ‘s становится немного запутанным с некоторыми из больших строк. Я чувствую, что использование StringBuilder в этом случае просто сделает его еще менее читаемым.
Есть какие-либо предложения здесь?
Комментарии:
1. Не беспокойтесь о производительности, если вы не обнаружите, что это проблема и что это узкое место. Делайте все, что, по вашему мнению, приведет к наиболее читаемому и поддерживаемому коду.
Ответ №1:
Если строки построены с использованием одного выражения конкатенации; например
String s = "You " verb " to " user " "" text """;
тогда это более или менее эквивалентно более длинному:
StringBuilder sb = new StringBuilder();
sb.append("You");
sb.append(verb);
sb.append(" to ");
sb.append(user);
sb.append(" "");
sb.append(text );
sb.append('"');
String s = sb.toString();
Фактически, классический компилятор Java скомпилирует первое во второе … почти. В Java 9 они реализовали JEP 280, который заменяет последовательность вызовов конструктора и метода в байт-кодах одним invokedynamic
байт-кодом. Затем система выполнения оптимизирует это1.
Проблемы с эффективностью возникают, когда вы начинаете создавать промежуточные строки или создавать строки с использованием =
и так далее. На этом этапе StringBuilder
становится более эффективным, потому что вы уменьшаете количество промежуточных строк, которые создаются, а затем выбрасываются.
Теперь, когда вы используете String.format()
, он должен использовать StringBuilder
под капотом. Однако format
также необходимо анализировать строку формата каждый раз, когда вы выполняете вызов, и это накладные расходы, которых у вас нет, если вы выполняете построение строки оптимально.
Сказав это, я бы посоветовал написать код наиболее читаемым способом. Только беспокоиться о наиболее эффективном способе построения строк, если профилирование говорит вам, что это реальная проблема с производительностью. (Прямо сейчас вы тратите время на размышления о способах решения проблемы производительности, которая может оказаться незначительной или неактуальной.)
В другом ответе упоминается, что использование строки формата может упростить поддержку нескольких языков. Это верно, хотя существуют ограничения относительно того, что вы можете делать в отношении таких вещей, как множественное число, пол и так далее.
1 — Как следствие, ручная оптимизация в соответствии с приведенным выше примером может иметь негативные последствия для Java 9 или более поздней версии. Но это риск, на который вы идете всякий раз, когда выполняете микрооптимизацию.
Комментарии:
1. Спасибо за подробное объяснение, это помогло мне все продумать. Я собираюсь использовать String.format, поскольку это а) встроенное решение и б) уже упрощает мою жизнь, делая строки намного более удобочитаемыми.
Ответ №2:
Я думаю, что конкатенация с
более удобочитаема, чем использование String.format
.
String.format
хорошо, когда вам нужно отформатировать число и даты.
Комментарии:
1. Кажется, это самый аккуратный и удобный выбор, и он реализован через
java.util.Formatter
.
Ответ №3:
Конкатенация с plus позволяет компилятору преобразовывать код перформативным способом. С форматом строки я не знаю.
Я предпочитаю объединять с плюсом, я думаю, что это легче понять.
Ответ №4:
Ключ к простоте — никогда не смотреть на это. Вот что я имею в виду:
Joiner join = Joiner.on(" ");
public void constructMessage(StringBuilder sb, Iterable<String> words) {
join.appendTo(sb, words);
}
Я использую класс Guava Joiner, чтобы сделать читаемость не проблемой. Что может быть понятнее, чем «присоединиться»? Все неприятные моменты, касающиеся конкатенации, хорошо скрыты. Используя Iterable, я могу использовать этот метод со всеми видами структур данных, списки являются наиболее очевидными.
Вот пример вызова с использованием Guava ImmutableList (который более эффективен, чем обычный список, поскольку любые методы, которые изменяют список, просто генерируют исключения и правильно представляют тот факт, что constructMessage() не может изменить список слов, просто использует его):
StringBuilder outputMessage = new StringBuilder();
constructMessage(outputMessage,
new ImmutableList.Builder<String>()
.add("You", verb, "to", user, """, text, """)
.build());
Ответ №5:
Я буду честен и предлагаю вам выбрать первый, если вы хотите меньше печатать, или последний, если вы ищете способ сделать это в стиле C.
Я сидел здесь минуту или две, размышляя о том, что может быть проблемой, но я думаю, что все сводится к тому, сколько вы хотите ввести.
У кого-нибудь еще есть идея?
Комментарии:
1. Мои рассуждения об этом больше касаются удобочитаемости. Я довольно привык набирать «материал» «другой материал», и, вероятно, мне придется отказаться от этой привычки, если я выберу другой вариант. Изначально я думал о чем-то вроде «Вы [глагол] к [пользователю] [текст]» и выполняете replaceall (управляемый классом, поэтому я бы добавил переменные типа SetElement («глагол», verb), а затем он вернул бы форматированную строку), но наткнулся на .format и решил, что моя первоначальная идея будет ужасной с производительностью (но очень легко читается).
Ответ №6:
Предполагая, что вы собираетесь повторно использовать базовые строки, часто храните свои шаблоны как
String mystring = «Вам от $ 1 до $ 2 «$ 3 «»
Затем просто получите копию и замените $ X тем, что вы хотите.
Это было бы очень хорошо для файла ресурсов тоже.
Комментарии:
1. Также вы могли бы поддерживать несколько языков: download.oracle.com/javase/tutorial/i18n/resbundle/concept.html
Ответ №7:
Я думаю, String.format выглядит чище. Однако вы можете использовать StringBuilder и использовать функцию append для создания нужной строки
Ответ №8:
Лучшим с точки зрения производительности, вероятно, было бы использовать StringBuffer .
Комментарии:
1. Не согласен. Из
StringBuilder
javadoc: Там, где это возможно, рекомендуется использовать этот класс предпочтительнее, чемStringBuffer
поскольку это будет быстрее в большинстве реализаций. — Кстати., я сомневаюсь, что конкатенация строк является узким местом в таком приложении.2. Та же разница, один синхронизирован, другой нет. При правильной реализации синхронизации разница незначительна, если нет разногласий. (И, очевидно, не хотелось бы использовать StringBuilder, если есть конфликт.)