I was looking at the performance of StringCharBuffer for various backing CharSequence types and was surprised to see a significant performance difference between String and StringBuffer. I wrote a small jmh which shows that the String implementation of charAt is significantly slower than StringBuilder. Is this expected?
Benchmark (data) (source) Mode Cnt Score Error Units CharSequenceCharAtBenchmark.test ascii String avgt 3 2537.311 ┬▒ 8952.197 ns/op CharSequenceCharAtBenchmark.test ascii StringBuffer avgt 3 852.004 ┬▒ 2532.958 ns/op CharSequenceCharAtBenchmark.test non-ascii String avgt 3 5115.381 ┬▒ 13822.592 ns/op CharSequenceCharAtBenchmark.test non-ascii StringBuffer avgt 3 836.230 ┬▒ 1154.191 ns/op @Measurement(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) @Warmup(iterations = 2, time = 7, timeUnit = TimeUnit.SECONDS) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Benchmark) @Fork(value = 1, jvmArgsPrepend = {"-Xms512M", "-Xmx512M"}) public class CharSequenceCharAtBenchmark { @Param(value = {"ascii", "non-ascii"}) public String data; @Param(value = {"String", "StringBuffer"}) public String source; private CharSequence sequence; @Setup(Level.Trial) public void setup() throws Exception { StringBuilder sb = new StringBuilder(3152); for (int i=0; i<3152; ++i) { char c = (char) i; if ("ascii".equals(data)) { c = (char) (i & 0x7f); } sb.append(c); } switch(source) { case "String": sequence = sb.toString(); break; case "StringBuffer": sequence = sb; break; default: throw new IllegalArgumentException(source); } } @Benchmark public int test() { int sum = 0; for (int i=0, j=sequence.length(); i<j; ++i) { sum += sequence.charAt(i); } return sum; } }