迭代string中所有字符的最快方法

在Java中,迭代string中所有字符的最快方法是:

String str = "a really, really long string"; for (int i = 0, n = str.length(); i < n; i++) { char c = str.charAt(i); } 

或这个:

 char[] chars = str.toCharArray(); for (int i = 0, n = chars.length; i < n; i++) { char c = chars[i]; } 

编辑:

我想知道的是,如果在长时间迭代期间重复调用charAt方法的代价最终小于或大于在开始时对toCharArray执行单个调用的成本,然后直接访问数组迭代。

如果有人能够为不同的string长度提供一个稳健的基准,那么记住JIT预热时间,JVM启动时间等等,而不是两次调用System.currentTimeMillis()之间的区别。

第一次更新:在你尝试在生产环境中(不build议)之前,先阅读这个: http : //www.javaspecialists.eu/archive/Issue237.html从Java 9开始,所述的解决scheme将不再工作了,因为现在Java默认将string存储为byte []。

第二次更新:截至2016-10-25,在我的AMDx64 8核心和源1.8,使用'charAt'和现场访问没有区别。 看来,jvm已经过充分的优化,可以内联和简化任何“string.charAt(n)”调用。

这一切都取决于正在检查的String的长度。 如果像问题所说的那样,对于string,检查string的最快方法是使用reflection来访问string的后备char[]

在64 AMD Phenom II 4核心955 @ 3.2 GHZ(在客户端模式和服务器模式下)与JDK 8(win32和win64)完全随机基准与9种不同的技术(见下文!)显示,使用String.charAt(n)对于小string来说是最快的,使用reflection来访问String backingarrays几乎是大string的两倍。

本实验

  • 尝试了9种不同的优化技术。

  • 所有的string内容都是随机的

  • testing是从0,1,2,4,8,16开始,以两个倍数的string大小完成的。

  • testing每个string大小进行1000次

  • 每次testing都随机排列。 换句话说,testing每次完成时都是按随机顺序完成的,超过1000次。

  • 整个testing套件向前和向后完成,以显示JVM预热对优化和时间的影响。

  • 整个套件都进行了两次,一次处于客户端模式,另一次处于服务器模式。

结论

客户端模式(32位)

对于长度1到256个字符的string,调用string.charAt(i)以每秒1340万到588万字符的平均处理获胜。

而且,总体来说5.5%(客户端)和13.9%(服务器)是这样的:

  for (int i = 0; i < data.length(); i++) { if (data.charAt(i) <= ' ') { doThrow(); } } 

比这样的局部最终长度variables:

  final int len = data.length(); for (int i = 0; i < len; i++) { if (data.charAt(i) <= ' ') { doThrow(); } } 

对于长度为512到256K的长string,使用reflection来访问string的后备数组是最快的。 这种技术几乎是String.charAt(i)的两倍(快了 178%)。 这个范围内的平均速度是每秒11.1亿个字符。

必须提前获取字段,然后在不同string的库中重新使用该字段。 有趣的是,与上面的代码不同,使用Field访问,在循环检查中,使用本地最终长度variables比使用“chars.length”要快9%。 以下是如何设置最快的字段访问:

  final Field field = String.class.getDeclaredField("value"); field.setAccessible(true); try { final char[] chars = (char[]) field.get(data); final int len = chars.length; for (int i = 0; i < len; i++) { if (chars[i] <= ' ') { doThrow(); } } return len; } catch (Exception ex) { throw new RuntimeException(ex); } 

服务器模式的特别评论

在我的AMD 64计算机上的64位Java机器上,服务器模式下的32个字符长度的string开始获得字段访问权限。 在客户端模式下,只有512个字符长度才能看到。

另外值得注意的是,我认为,当我在服务器模式下运行JDK 8(32位版本)时,对于大小string,整体性能降低了7%。 这是2013年12月发布的JDK 8的早期版本。 所以,现在看来,32位服务器模式比32位客户模式慢。

这就是说…似乎唯一的服务器模式是值得调用的是在64位机器上。 否则,这实际上妨碍了性能。

对于在AMD64上以服务器-server mode运行的32位版本,我可以这样说:

  1. String.charAt(i)总体上是明显的赢家。 虽然大小在8到512之间,但在“新”“重复使用”和“场地”之间却有赢家。
  2. 在客户端模式下,String.charAt(i)的速度提高了45%
  3. 对于客户端模式下的大string,字段访问速度是两倍。

另外值得一提的是,String.chars()(Stream和并行版本)是一个半身像。 比任何其他方式更慢。 Streams API是执行一般string操作的一种相当慢的方式。

愿望清单

Java String可能有谓词接受优化的方法,如contains(predicate),forEach(consumer),forEachWithIndex(consumer)。 因此,不需要用户知道长度或重复调用string方法,这些可以帮助parsing库beep-beep beep

继续做梦:)

快乐的弦乐!

〜SH

该testing使用以下9种方法testingstring是否存在空格:

“charAt1” – 以常用方式检查string内容:

 int charAtMethod1(final String data) { final int len = data.length(); for (int i = 0; i < len; i++) { if (data.charAt(i) <= ' ') { doThrow(); } } return len; } 

“charAt2” – 与上述相同,但使用String.length()INSTEAD为长度做一个最终的本地int

 int charAtMethod2(final String data) { for (int i = 0; i < data.length(); i++) { if (data.charAt(i) <= ' ') { doThrow(); } } return data.length(); } 

“stream” – 使用新的JAVA-8string的IntStream并通过它的检查预测

 int streamMethod(final String data, final IntPredicate predicate) { if (data.chars().anyMatch(predicate)) { doThrow(); } return data.length(); } 

“streamPara” – 相同,但OH – LA – LA – 并行!

 // avoid this at all costs int streamParallelMethod(final String data, IntPredicate predicate) { if (data.chars().parallel().anyMatch(predicate)) { doThrow(); } return data.length(); } 

“重用” – 用string内容填充可重复使用的char []

 int reuseBuffMethod(final char[] reusable, final String data) { final int len = data.length(); data.getChars(0, len, reusable, 0); for (int i = 0; i < len; i++) { if (reusable[i] <= ' ') { doThrow(); } } return len; } 

“new1” – 从string中获取char []的新副本

 int newMethod1(final String data) { final int len = data.length(); final char[] copy = data.toCharArray(); for (int i = 0; i < len; i++) { if (copy[i] <= ' ') { doThrow(); } } return len; } 

“new2” – 与以上相同,但使用“FOR-EACH”

 int newMethod2(final String data) { for (final char c : data.toCharArray()) { if (c <= ' ') { doThrow(); } } return data.length(); } 

“field1” – FANCY !! 获取字段访问string的内部char []

 int fieldMethod1(final Field field, final String data) { try { final char[] chars = (char[]) field.get(data); final int len = chars.length; for (int i = 0; i < len; i++) { if (chars[i] <= ' ') { doThrow(); } } return len; } catch (Exception ex) { throw new RuntimeException(ex); } } 

“field2” – 如上所述,但使用“FOR-EACH”

 int fieldMethod2(final Field field, final String data) { final char[] chars; try { chars = (char[]) field.get(data); } catch (Exception ex) { throw new RuntimeException(ex); } for (final char c : chars) { if (c <= ' ') { doThrow(); } } return chars.length; } 

客户端的组合结果 – 客户端模式(组合向前和向后testing)

注意:在我的AMD64机器上,带有Java 64位的Java 32位和-server模式的-client模式与下面的模式相同。

 Size WINNER charAt1 charAt2 stream streamPar reuse new1 new2 field1 field2 1 charAt 77.0 72.0 462.0 584.0 127.5 89.5 86.0 159.5 165.0 2 charAt 38.0 36.5 284.0 32712.5 57.5 48.3 50.3 89.0 91.5 4 charAt 19.5 18.5 458.6 3169.0 33.0 26.8 27.5 54.1 52.6 8 charAt 9.8 9.9 100.5 1370.9 17.3 14.4 15.0 26.9 26.4 16 charAt 6.1 6.5 73.4 857.0 8.4 8.2 8.3 13.6 13.5 32 charAt 3.9 3.7 54.8 428.9 5.0 4.9 4.7 7.0 7.2 64 charAt 2.7 2.6 48.2 232.9 3.0 3.2 3.3 3.9 4.0 128 charAt 2.1 1.9 43.7 138.8 2.1 2.6 2.6 2.4 2.6 256 charAt 1.9 1.6 42.4 90.6 1.7 2.1 2.1 1.7 1.8 512 field1 1.7 1.4 40.6 60.5 1.4 1.9 1.9 1.3 1.4 1,024 field1 1.6 1.4 40.0 45.6 1.2 1.9 2.1 1.0 1.2 2,048 field1 1.6 1.3 40.0 36.2 1.2 1.8 1.7 0.9 1.1 4,096 field1 1.6 1.3 39.7 32.6 1.2 1.8 1.7 0.9 1.0 8,192 field1 1.6 1.3 39.6 30.5 1.2 1.8 1.7 0.9 1.0 16,384 field1 1.6 1.3 39.8 28.4 1.2 1.8 1.7 0.8 1.0 32,768 field1 1.6 1.3 40.0 26.7 1.3 1.8 1.7 0.8 1.0 65,536 field1 1.6 1.3 39.8 26.3 1.3 1.8 1.7 0.8 1.0 131,072 field1 1.6 1.3 40.1 25.4 1.4 1.9 1.8 0.8 1.0 262,144 field1 1.6 1.3 39.6 25.2 1.5 1.9 1.9 0.8 1.0 

服务器 – 服务器模式的综合结果(组合向前和向后testing)

注意:这是在AMD64服务器模式下运行Java 32位的testing。 Java 64位的服务器模式与客户机模式下的Java 32位相同,只是字段访问在32个字符大小之后开始获胜。

 Size WINNER charAt1 charAt2 stream streamPar reuse new1 new2 field1 field2 1 charAt 74.5 95.5 524.5 783.0 90.5 102.5 90.5 135.0 151.5 2 charAt 48.5 53.0 305.0 30851.3 59.3 57.5 52.0 88.5 91.8 4 charAt 28.8 32.1 132.8 2465.1 37.6 33.9 32.3 49.0 47.0 8 new2 18.0 18.6 63.4 1541.3 18.5 17.9 17.6 25.4 25.8 16 new2 14.0 14.7 129.4 1034.7 12.5 16.2 12.0 16.0 16.6 32 new2 7.8 9.1 19.3 431.5 8.1 7.0 6.7 7.9 8.7 64 reuse 6.1 7.5 11.7 204.7 3.5 3.9 4.3 4.2 4.1 128 reuse 6.8 6.8 9.0 101.0 2.6 3.0 3.0 2.6 2.7 256 field2 6.2 6.5 6.9 57.2 2.4 2.7 2.9 2.3 2.3 512 reuse 4.3 4.9 5.8 28.2 2.0 2.6 2.6 2.1 2.1 1,024 charAt 2.0 1.8 5.3 17.6 2.1 2.5 3.5 2.0 2.0 2,048 charAt 1.9 1.7 5.2 11.9 2.2 3.0 2.6 2.0 2.0 4,096 charAt 1.9 1.7 5.1 8.7 2.1 2.6 2.6 1.9 1.9 8,192 charAt 1.9 1.7 5.1 7.6 2.2 2.5 2.6 1.9 1.9 16,384 charAt 1.9 1.7 5.1 6.9 2.2 2.5 2.5 1.9 1.9 32,768 charAt 1.9 1.7 5.1 6.1 2.2 2.5 2.5 1.9 1.9 65,536 charAt 1.9 1.7 5.1 5.5 2.2 2.4 2.4 1.9 1.9 131,072 charAt 1.9 1.7 5.1 5.4 2.3 2.5 2.5 1.9 1.9 262,144 charAt 1.9 1.7 5.1 5.1 2.3 2.5 2.5 1.9 1.9 

全运行程序代码

(要testingJava 7及更早版本,请移除两个streamtesting)

 import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import java.util.function.IntPredicate; /** * @author Saint Hill <http://stackoverflow.com/users/1584255/saint-hill> */ public final class TestStrings { // we will not test strings longer than 512KM final int MAX_STRING_SIZE = 1024 * 256; // for each string size, we will do all the tests // this many times final int TRIES_PER_STRING_SIZE = 1000; public static void main(String[] args) throws Exception { new TestStrings().run(); } void run() throws Exception { // double the length of the data until it reaches MAX chars long // 0,1,2,4,8,16,32,64,128,256 ... final List<Integer> sizes = new ArrayList<>(); for (int n = 0; n <= MAX_STRING_SIZE; n = (n == 0 ? 1 : n * 2)) { sizes.add(n); } // CREATE RANDOM (FOR SHUFFLING ORDER OF TESTS) final Random random = new Random(); System.out.println("Rate in nanoseconds per character inspected."); System.out.printf("==== FORWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE); printHeadings(TRIES_PER_STRING_SIZE, random); for (int size : sizes) { reportResults(size, test(size, TRIES_PER_STRING_SIZE, random)); } // reverse order or string sizes Collections.reverse(sizes); System.out.println(""); System.out.println("Rate in nanoseconds per character inspected."); System.out.printf("==== BACKWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE); printHeadings(TRIES_PER_STRING_SIZE, random); for (int size : sizes) { reportResults(size, test(size, TRIES_PER_STRING_SIZE, random)); } } /// /// /// METHODS OF CHECKING THE CONTENTS /// OF A STRING. ALWAYS CHECKING FOR /// WHITESPACE (CHAR <=' ') /// /// // CHECK THE STRING CONTENTS int charAtMethod1(final String data) { final int len = data.length(); for (int i = 0; i < len; i++) { if (data.charAt(i) <= ' ') { doThrow(); } } return len; } // SAME AS ABOVE BUT USE String.length() // instead of making a new final local int int charAtMethod2(final String data) { for (int i = 0; i < data.length(); i++) { if (data.charAt(i) <= ' ') { doThrow(); } } return data.length(); } // USE new Java-8 String's IntStream // pass it a PREDICATE to do the checking int streamMethod(final String data, final IntPredicate predicate) { if (data.chars().anyMatch(predicate)) { doThrow(); } return data.length(); } // OH LA LA - GO PARALLEL!!! int streamParallelMethod(final String data, IntPredicate predicate) { if (data.chars().parallel().anyMatch(predicate)) { doThrow(); } return data.length(); } // Re-fill a resuable char[] with the contents // of the String's char[] int reuseBuffMethod(final char[] reusable, final String data) { final int len = data.length(); data.getChars(0, len, reusable, 0); for (int i = 0; i < len; i++) { if (reusable[i] <= ' ') { doThrow(); } } return len; } // Obtain a new copy of char[] from String int newMethod1(final String data) { final int len = data.length(); final char[] copy = data.toCharArray(); for (int i = 0; i < len; i++) { if (copy[i] <= ' ') { doThrow(); } } return len; } // Obtain a new copy of char[] from String // but use FOR-EACH int newMethod2(final String data) { for (final char c : data.toCharArray()) { if (c <= ' ') { doThrow(); } } return data.length(); } // FANCY! // OBTAIN FIELD FOR ACCESS TO THE STRING'S // INTERNAL CHAR[] int fieldMethod1(final Field field, final String data) { try { final char[] chars = (char[]) field.get(data); final int len = chars.length; for (int i = 0; i < len; i++) { if (chars[i] <= ' ') { doThrow(); } } return len; } catch (Exception ex) { throw new RuntimeException(ex); } } // same as above but use FOR-EACH int fieldMethod2(final Field field, final String data) { final char[] chars; try { chars = (char[]) field.get(data); } catch (Exception ex) { throw new RuntimeException(ex); } for (final char c : chars) { if (c <= ' ') { doThrow(); } } return chars.length; } /** * * Make a list of tests. We will shuffle a copy of this list repeatedly * while we repeat this test. * * @param data * @return */ List<Jobber> makeTests(String data) throws Exception { // make a list of tests final List<Jobber> tests = new ArrayList<Jobber>(); tests.add(new Jobber("charAt1") { int check() { return charAtMethod1(data); } }); tests.add(new Jobber("charAt2") { int check() { return charAtMethod2(data); } }); tests.add(new Jobber("stream") { final IntPredicate predicate = new IntPredicate() { public boolean test(int value) { return value <= ' '; } }; int check() { return streamMethod(data, predicate); } }); tests.add(new Jobber("streamPar") { final IntPredicate predicate = new IntPredicate() { public boolean test(int value) { return value <= ' '; } }; int check() { return streamParallelMethod(data, predicate); } }); // Reusable char[] method tests.add(new Jobber("reuse") { final char[] cbuff = new char[MAX_STRING_SIZE]; int check() { return reuseBuffMethod(cbuff, data); } }); // New char[] from String tests.add(new Jobber("new1") { int check() { return newMethod1(data); } }); // New char[] from String tests.add(new Jobber("new2") { int check() { return newMethod2(data); } }); // Use reflection for field access tests.add(new Jobber("field1") { final Field field; { field = String.class.getDeclaredField("value"); field.setAccessible(true); } int check() { return fieldMethod1(field, data); } }); // Use reflection for field access tests.add(new Jobber("field2") { final Field field; { field = String.class.getDeclaredField("value"); field.setAccessible(true); } int check() { return fieldMethod2(field, data); } }); return tests; } /** * We use this class to keep track of test results */ abstract class Jobber { final String name; long nanos; long chars; long runs; Jobber(String name) { this.name = name; } abstract int check(); final double nanosPerChar() { double charsPerRun = chars / runs; long nanosPerRun = nanos / runs; return charsPerRun == 0 ? nanosPerRun : nanosPerRun / charsPerRun; } final void run() { runs++; long time = System.nanoTime(); chars += check(); nanos += System.nanoTime() - time; } } // MAKE A TEST STRING OF RANDOM CHARACTERS AZ private String makeTestString(int testSize, char start, char end) { Random r = new Random(); char[] data = new char[testSize]; for (int i = 0; i < data.length; i++) { data[i] = (char) (start + r.nextInt(end)); } return new String(data); } // WE DO THIS IF WE FIND AN ILLEGAL CHARACTER IN THE STRING public void doThrow() { throw new RuntimeException("Bzzzt -- Illegal Character!!"); } /** * 1. get random string of correct length 2. get tests (List<Jobber>) 3. * perform tests repeatedly, shuffling each time */ List<Jobber> test(int size, int tries, Random random) throws Exception { String data = makeTestString(size, 'A', 'Z'); List<Jobber> tests = makeTests(data); List<Jobber> copy = new ArrayList<>(tests); while (tries-- > 0) { Collections.shuffle(copy, random); for (Jobber ti : copy) { ti.run(); } } // check to make sure all char counts the same long runs = tests.get(0).runs; long count = tests.get(0).chars; for (Jobber ti : tests) { if (ti.runs != runs && ti.chars != count) { throw new Exception("Char counts should match if all correct algorithms"); } } return tests; } private void printHeadings(final int TRIES_PER_STRING_SIZE, final Random random) throws Exception { System.out.print(" Size"); for (Jobber ti : test(0, TRIES_PER_STRING_SIZE, random)) { System.out.printf("%9s", ti.name); } System.out.println(""); } private void reportResults(int size, List<Jobber> tests) { System.out.printf("%6d", size); for (Jobber ti : tests) { System.out.printf("%,9.2f", ti.nanosPerChar()); } System.out.println(""); } } 

这只是微型优化,你不应该担心。

 char[] chars = str.toCharArray(); 

返回str字符数组的副本(在JDK中,它通过调用System.arrayCopy返回字符副本)。

除此之外, str.charAt()只检查索引是否确实在边界内,并返回数组索引内的一个字符。

第一个不会在JVM中创build额外的内存。

只是为了好奇,并与圣山的答案进行比较。

如果您需要处理大量数据,则不应在客户端模式下使用JVM。 客户端模式不用于优化。

我们使用客户端模式和服务器模式下的JVM比较@Saint Hill基准testing的结果。

 Core2Quad Q6600 G0 @ 2.4GHz JavaSE 1.7.0_40 

另请参见: “java -server”和“java -client”之间的真正区别?


客户模式:

 len = 2: 111k charAt(i), 105k cbuff[i], 62k new[i], 17k field access. (chars/ms) len = 4: 285k charAt(i), 166k cbuff[i], 114k new[i], 43k field access. (chars/ms) len = 6: 315k charAt(i), 230k cbuff[i], 162k new[i], 69k field access. (chars/ms) len = 8: 333k charAt(i), 275k cbuff[i], 181k new[i], 85k field access. (chars/ms) len = 12: 342k charAt(i), 342k cbuff[i], 222k new[i], 117k field access. (chars/ms) len = 16: 363k charAt(i), 347k cbuff[i], 275k new[i], 152k field access. (chars/ms) len = 20: 363k charAt(i), 392k cbuff[i], 289k new[i], 180k field access. (chars/ms) len = 24: 375k charAt(i), 428k cbuff[i], 311k new[i], 205k field access. (chars/ms) len = 28: 378k charAt(i), 474k cbuff[i], 341k new[i], 233k field access. (chars/ms) len = 32: 376k charAt(i), 492k cbuff[i], 340k new[i], 251k field access. (chars/ms) len = 64: 374k charAt(i), 551k cbuff[i], 374k new[i], 367k field access. (chars/ms) len = 128: 385k charAt(i), 624k cbuff[i], 415k new[i], 509k field access. (chars/ms) len = 256: 390k charAt(i), 675k cbuff[i], 436k new[i], 619k field access. (chars/ms) len = 512: 394k charAt(i), 703k cbuff[i], 439k new[i], 695k field access. (chars/ms) len = 1024: 395k charAt(i), 718k cbuff[i], 462k new[i], 742k field access. (chars/ms) len = 2048: 396k charAt(i), 725k cbuff[i], 471k new[i], 767k field access. (chars/ms) len = 4096: 396k charAt(i), 727k cbuff[i], 459k new[i], 780k field access. (chars/ms) len = 8192: 397k charAt(i), 712k cbuff[i], 446k new[i], 772k field access. (chars/ms) 

服务器模式:

 len = 2: 86k charAt(i), 41k cbuff[i], 46k new[i], 80k field access. (chars/ms) len = 4: 571k charAt(i), 250k cbuff[i], 97k new[i], 222k field access. (chars/ms) len = 6: 666k charAt(i), 333k cbuff[i], 125k new[i], 315k field access. (chars/ms) len = 8: 800k charAt(i), 400k cbuff[i], 181k new[i], 380k field access. (chars/ms) len = 12: 800k charAt(i), 521k cbuff[i], 260k new[i], 545k field access. (chars/ms) len = 16: 800k charAt(i), 592k cbuff[i], 296k new[i], 640k field access. (chars/ms) len = 20: 800k charAt(i), 666k cbuff[i], 408k new[i], 800k field access. (chars/ms) len = 24: 800k charAt(i), 705k cbuff[i], 452k new[i], 800k field access. (chars/ms) len = 28: 777k charAt(i), 736k cbuff[i], 368k new[i], 933k field access. (chars/ms) len = 32: 800k charAt(i), 780k cbuff[i], 571k new[i], 969k field access. (chars/ms) len = 64: 800k charAt(i), 901k cbuff[i], 800k new[i], 1306k field access. (chars/ms) len = 128: 1084k charAt(i), 888k cbuff[i], 633k new[i], 1620k field access. (chars/ms) len = 256: 1122k charAt(i), 966k cbuff[i], 729k new[i], 1790k field access. (chars/ms) len = 512: 1163k charAt(i), 1007k cbuff[i], 676k new[i], 1910k field access. (chars/ms) len = 1024: 1179k charAt(i), 1027k cbuff[i], 698k new[i], 1954k field access. (chars/ms) len = 2048: 1184k charAt(i), 1043k cbuff[i], 732k new[i], 2007k field access. (chars/ms) len = 4096: 1188k charAt(i), 1049k cbuff[i], 742k new[i], 2031k field access. (chars/ms) len = 8192: 1157k charAt(i), 1032k cbuff[i], 723k new[i], 2048k field access. (chars/ms) 

结论:

正如你所看到的,服务器模式要快得多。

第一个使用str.charAt应该会更快。

如果你深入挖掘String类的源代码,我们可以看到charAt的实现如下:

 public char charAt(int index) { if ((index < 0) || (index >= count)) { throw new StringIndexOutOfBoundsException(index); } return value[index + offset]; } 

在这里,它所做的就是索引一个数组并返回值。

现在,如果我们看到了toCharArray的实现,我们会发现如下:

 public char[] toCharArray() { char result[] = new char[count]; getChars(0, count, result, 0); return result; } public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > count) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, offset + srcBegin, dst, dstBegin, srcEnd - srcBegin); } 

正如你所看到的,它正在做一个System.arraycopy ,肯定会比不这样做慢一点。

看起来好像是更快或更慢

  public static void main(String arguments[]) { //Build a long string StringBuilder sb = new StringBuilder(); for(int j = 0; j < 10000; j++) { sb.append("a really, really long string"); } String str = sb.toString(); for (int testscount = 0; testscount < 10; testscount ++) { //Test 1 long start = System.currentTimeMillis(); for(int c = 0; c < 10000000; c++) { for (int i = 0, n = str.length(); i < n; i++) { char chr = str.charAt(i); doSomethingWithChar(chr);//To trick JIT optimistaion } } System.out.println("1: " + (System.currentTimeMillis() - start)); //Test 2 start = System.currentTimeMillis(); char[] chars = str.toCharArray(); for(int c = 0; c < 10000000; c++) { for (int i = 0, n = chars.length; i < n; i++) { char chr = chars[i]; doSomethingWithChar(chr);//To trick JIT optimistaion } } System.out.println("2: " + (System.currentTimeMillis() - start)); System.out.println(); } } public static void doSomethingWithChar(char chr) { int newInt = chr << 2; } 

对于长的string,我会select第一个。 为什么复制长string? 文件说:

public char [] toCharArray()将此string转换为新的字符数组。

返回:新分配的字符数组,其长度是此string的长度,其内容被初始化为包含由此string表示的字符序列。

//编辑1

我已经改变了testing来欺骗JIT优化。

//编辑2

重复testing10次,让JVM预热。

//编辑3

结论:

首先str.toCharArray(); 在内存中复制整个string。 对于长string,它可能会耗费内存。 方法String.charAt( )在String类检查索引之前查找char数组中char。 它看起来像足够短的string第一个方法(即chatAt方法)由于这个索引检查有点慢。 但是,如果string足够长,复制整个字符数组变得更慢,第一种方法更快。 string越长, toCharArray执行速度toCharArray慢。 尝试改变for(int j = 0; j < 10000; j++)循环中的限制来查看它。 如果让JVM让代码运行更快,但是比例是相同的。

毕竟这只是微型优化。

尽pipe@Saint Hill的回答是,如果考虑str.toCharArray()的时间复杂度,

即使是非常大的string,第一个也是更快的。 你可以运行下面的代码来自己看看。

  char [] ch = new char[1_000_000_00]; String str = new String(ch); // to create a large string // ---> from here long currentTime = System.nanoTime(); for (int i = 0, n = str.length(); i < n; i++) { char c = str.charAt(i); } // ---> to here System.out.println("str.charAt(i):"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)"); /** * ch = str.toCharArray() itself takes lots of time */ // ---> from here currentTime = System.nanoTime(); ch = str.toCharArray(); for (int i = 0, n = str.length(); i < n; i++) { char c = ch[i]; } // ---> to here System.out.println("ch = str.toCharArray() + c = ch[i] :"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)"); 

输出:

 str.charAt(i):5.492102 (ms) ch = str.toCharArray() + c = ch[i] :79.400064 (ms) 

第二个导致一个新的字符数组被创build,所有的字符从string被复制到这个新的字符数组,所以我猜想第一个更快(和更less的内存饥饿)。

我很确定第一个会更快。

在第二个中,您添加了另一个操作将string转换为字符…您在这里失去了几个时间…

String.toCharArray()创build新的字符数组,意味着分配string长度的内存,然后使用System.arraycopy()复制原始字符数组的string,然后将此副本返回给调用者。 String.charAt()从原始位置返回位置i字符,这就是String.charAt()String.toCharArray()更快的原因。 虽然, String.toCharArray()返回的是原始String数组的复制,而不是char,其中String.charAt()从原始char数组返回字符。 下面的代码返回此string的指定索引处的值。

 public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; } 

下面的代码返回一个新分配的字符数组,其长度是该string的长度

 public char[] toCharArray() { // Cannot use Arrays.copyOf because of class initialization order issues char result[] = new char[value.length]; System.arraycopy(value, 0, result, 0, value.length); return result; }