Какой из них лучше по производительности?
finalWords.stream().forEach(word -> stringBuilder.append(word).append(”,“));
String finalResult = stringBuilder.toString();
ПРОТИВ
String finalResult = finalWords.stream().collect(Collectors.joining(","));
Лучший способ - это, вероятно, использовать String.join. Но вам, вероятно, стоит ориентир.
@JaroslawPawlak Да, я посмотрел код и до сих пор не уверен в производительности, поэтому и поднял вопрос. К тому же я экономлю время многих разработчиков, у которых в будущем может возникнуть такой же вопрос. Надеюсь, это оправдывает вопрос. И я уверен, что вы тоже узнаете что-то новое в ходе обсуждения, которое здесь происходит!
в StringBuilder вы можете указать емкость, в StringJoiner вы не можете, он будет увеличивать *2 каждый раз, массив строк 8 -> 16 -> 32, эти изменения размера используют Array.copyOf. Я бы сказал, что при должной емкости StringBuilder выиграет в тесте скорости.
@Sagar Оба подхода используют StringBuilder, поэтому разница будет незначительной. Вы можете увидеть некоторый шаблонный код в joining - все эти новые созданные объекты (например, StringBuilder) и все операторы if, поэтому он определенно будет немного медленнее. Оба подхода имеют сложность O(n), поэтому все сводится к тому, что @ArnaudDenoyelle уже сказал выше - «предпочитать наиболее читаемый».
Поскольку код не делает то же самое, бессмысленно обсуждать различия в производительности. Первый вариант добавляет завершающий ",", чего не делает соединитель. Кроме того, первый вариант не является потокобезопасным. Помимо этих различий, они делают то же самое под поверхностью. Таким образом, любая разница в производительности возникает из-за того, что не делается одно и то же. По словам Дюклинга, вы также можете использовать String.join(",", finalWords), который попроще (но делает то же самое)…




Я собрал небольшой тест, чтобы проверить это, потому что мне было любопытно. Он инициализирует List со случайно сгенерированными size в нижнем регистре, каждый из которых имеет длину String:
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Fork(3)
public class MyBenchmark {
@Param({"10", "100", "1000", "10000", "100000"})
private int size;
private List<String> finalWords;
@Setup(Level.Invocation)
public void initialize() {
finalWords = IntStream.range(0, size)
.mapToObj(i -> {
return ThreadLocalRandom.current()
.ints(10, 'a', 'z' + 1)
.mapToObj(c -> Character.toString((char) c))
.collect(Collectors.joining());
}).collect(Collectors.toList());
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
@Benchmark
public String stringBuilder() {
StringBuilder sb = new StringBuilder();
finalWords.forEach(word -> sb.append(word).append(","));
return sb.toString();
}
@Benchmark
public String stream() {
return finalWords.stream().collect(Collectors.joining(","));
}
}
Вот результаты:
Benchmark (size) Mode Cnt Score Error Units
MyBenchmark.stream 10 avgt 30 242.330 ± 5.177 ns/op
MyBenchmark.stream 100 avgt 30 1426.333 ± 20.183 ns/op
MyBenchmark.stream 1000 avgt 30 30779.509 ± 1114.992 ns/op
MyBenchmark.stream 10000 avgt 30 720944.424 ± 27845.997 ns/op
MyBenchmark.stream 100000 avgt 30 7701294.456 ± 648084.759 ns/op
MyBenchmark.stringBuilder 10 avgt 30 170.566 ± 1.833 ns/op
MyBenchmark.stringBuilder 100 avgt 30 1166.153 ± 21.162 ns/op
MyBenchmark.stringBuilder 1000 avgt 30 32374.567 ± 979.288 ns/op
MyBenchmark.stringBuilder 10000 avgt 30 473022.229 ± 8982.260 ns/op
MyBenchmark.stringBuilder 100000 avgt 30 4524267.849 ± 242801.008 ns/op
Как видите, метод 10 в этом случае работает быстрее, даже если я не указываю начальную емкость.
это не честный тест, первый ничего не инициирует от Stream API, жрет ваше время
Я также сомневаюсь, что вам здесь нужен Level.Invocation, поскольку все, что вам нужно сделать, это собрать их в строку в конечном итоге
@Eugene Вы рекомендуете использовать stream().forEach(?
Сам вопрос, вероятно, в том, что быстрее StringJoiner или StringBuilder, и это то, о чем должен быть тест, таким образом, ну, вы сравниваете разные вещи. Еще бы добавил тест с начальной емкостью StringBuilder
поскольку OP в конечном итоге хочет String, это может пойти еще дальше, как насчет java-9 concat + java-9 with a different strategy, что-то вроде @Fork(jvmArgsAppend = "-Djava.lang.invoke.stringConcat=BC_SB") или даже guava Joiner, это сделало бы тест, который я хотел бы
Было бы интересно также заменить и протестировать ненужный цикл Stream.forEach() обычным циклом for.
@JacobG. Что вы использовали для этого теста? Являются ли аннотации частью API какой-либо платформы тестирования?
@TMG Скорее всего, это был JMH, Java Microbenchmark Harness.
@JacobG. Вау, это был невероятно быстрый ответ! Спасибо :-)
Честно говоря, я не думаю, что вы когда-нибудь соедините 1 миллион струн. Так что отдайте предпочтение максимально читаемому, вне зависимости от производительности.