在Java中,迭代字符串中所有字符的最快方法是什么:
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
的单个调用的成本在开始时,然后在迭代期间直接访问数组。
如果有人能够为不同的字符串长度提供强大的基准测试,考虑到JIT预热时间,JVM启动时间等等,而不仅仅是两次调用{{1}之间的差异,那就太棒了。 }。
答案 0 :(得分:336)
第一次更新:在生产环境中尝试此操作之前(不建议),请先阅读此内容:http://www.javaspecialists.eu/archive/Issue237.html 从Java 9开始,所描述的解决方案不再适用,因为现在Java默认将字符串存储为byte []。
第二次更新:截至2016-10-25,在我的AMDx64 8core和源1.8上,使用&#39; charAt&#39;和现场访问。似乎jvm已经过充分优化,可以内联和简化任何字符串.charAt(n)&#39;调用
这一切都取决于被检查String
的长度。如果问题是长字符串,那么检查字符串的最快方法是使用反射来访问字符串的后备char[]
。
使用JDK 8(win32和win64)在64位AMD Phenom II 4核心955 @ 3.2 GHZ(在客户端模式和服务器模式下)使用9种不同技术(见下文!)的完全随机基准显示使用{{ 1}}对于小字符串来说是最快的,并且使用String.charAt(n)
来访问String后备数组的速度几乎是大字符串的两倍。
尝试了9种不同的优化技术。
所有字符串内容都是随机的
对于以0,1,2,4,8,16等开头的两个倍数的字符串大小进行测试。
每个字符串大小测试完成1000次
每次都会将测试随机排列。换句话说,测试每次完成后都会以随机顺序进行,超过1000次。
整个测试套件向前和向后完成,以显示JVM预热对优化和时间的影响。
整个套件已完成两次,一次处于reflection
模式,另一种处于-client
模式。
对于字符串长度为1到256个字符,调用-server
获胜的平均处理速度为每秒1340万到5.88亿个字符。
此外,整体速度提高了5.5%(客户端)和13.9%(服务器):
string.charAt(i)
与本地最终长度变量相比:
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
对于长字符串, 512到256K字符长度,使用反射来访问String的后备阵列是最快的。 这种技术的速度几乎是String.charAt(i)的两倍(快178%)。该范围内的平均速度为每秒11.11亿个字符。
必须提前获取字段,然后可以在库中的不同字符串上重复使用。有趣的是,与上面的代码不同,使用Field访问,拥有本地最终长度变量比使用&#39; chars.length&#39;快9%。在循环检查中。以下是Field访问可以设置为最快的方式:
final int len = data.length();
for (int i = 0; i < len; i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
在AMD 64计算机上的64位Java计算机上,在服务器模式下,字段访问开始在32个字符长度字符串后获胜。在客户端模式下,直到512个字符长度才能看到。
另外值得注意的是,当我在服务器模式下运行JDK 8(32位版本)时,大字符串和小字符串的整体性能降低了7%。这是2013年12月121日JDK 8早期版本的构建。因此,目前看来,32位服务器模式似乎比32位客户端模式慢。
话虽如此......似乎唯一值得调用的服务器模式是在64位机器上。否则它实际上会妨碍表现。
对于在AMD64上 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);
}
运行的32位版本,我可以这样说:
另外值得一提的是,String.chars()(Stream和并行版)是一个半身像。比任何其他方式慢。 -server mode
API是执行常规字符串操作的一种相当慢的方式。
Java String可以让谓词接受优化方法,例如contains(谓词),forEach(使用者),forEachWithIndex(使用者)。因此,无需用户知道长度或重复调用String方法,这些可以帮助解析库Streams
加速。
继续做梦:)
Happy Strings!
〜SH
&#34; charAt1&#34; - 以常规方式检查字符串内容:
beep-beep beep
&#34; charAt2&#34; - 与上面相同但是使用String.length()为LENGTW制作最终的本地文件
int charAtMethod1(final String data) {
final int len = data.length();
for (int i = 0; i < len; i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return len;
}
&#34;流&#34; - 使用新的JAVA-8字符串的IntStream并通过预测进行检查
int charAtMethod2(final String data) {
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return data.length();
}
&#34; streamPara&#34; - 与上面相同,但是OH-LA-LA - GO PARALLEL !!!
int streamMethod(final String data, final IntPredicate predicate) {
if (data.chars().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
&#34;再利用&#34; - 用字符串内容重新填充可重复使用的字符[]
// avoid this at all costs
int streamParallelMethod(final String data, IntPredicate predicate) {
if (data.chars().parallel().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
&#34;名new1&#34; - 从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;
}
&#34; NEW2&#34; - 与上述相同,但仅限于使用&#34;每个&#34;
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;
}
&#34; field1的&#34; - 花哨!!获取字段以访问字符串内部字符[]
int newMethod2(final String data) {
for (final char c : data.toCharArray()) {
if (c <= ' ') {
doThrow();
}
}
return data.length();
}
&#34; FIELD2&#34; - 与上述相同,但仅限于使用&#34;每个&#34;
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);
}
}
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;
}
模式的综合结果(前向和后向测试相结合)注意:带有Java 32位的-client模式和带有Java 64位的-server模式与我的AMD64机器上的相同。
-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
模式的综合结果(组合前后测试)注意:这是在AMD64上以服务器模式运行的Java 32位的测试。 Java 64位的服务器模式与客户端模式下的Java 32位相同,只是Field访问在32个字符大小后开始获胜。
-server
(在Java 7及更早版本上进行测试,删除两个流测试)
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
答案 1 :(得分:13)
这只是微观优化,您不必担心。
char[] chars = str.toCharArray();
返回str
个字符数组的副本(在JDK中,它通过调用System.arrayCopy
返回字符副本)。
除此之外,str.charAt()
仅检查索引是否确实在边界内并返回数组索引中的字符。
第一个不会在JVM中创建额外的内存。
答案 2 :(得分:9)
只是为了好奇并与圣希尔的答案进行比较。
如果需要处理大量数据,则不应在客户端模式下使用JVM。客户端模式不适用于优化。
让我们在客户端模式和服务器模式下使用JVM比较@Saint Hill基准测试的结果。
Core2Quad Q6600 G0 @ 2.4GHz
JavaSE 1.7.0_40
另请参阅:Real differences between "java -server" and "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)
<强>结论:强>
如您所见,服务器模式要快得多。
答案 3 :(得分:6)
使用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
,这肯定会比不这样做慢一点。
答案 4 :(得分:2)
看起来niether更快或更慢
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;
}
对于长琴弦,我会选择第一个琴弦。为什么要复制长串呢? 文件说:
public char [] toCharArray() 将此字符串转换为新的字符数组。
返回: 新分配的字符数组,其长度为此字符串的长度,其内容初始化为包含此字符串表示的字符序列。
//编辑1
我已经改变了测试以欺骗JIT优化。
//编辑2
重复测试10次让JVM热身。
//编辑3
结论:
首先,str.toCharArray();
将整个字符串复制到内存中。对于长字符串,它可能是内存消耗。方法String.charAt( )
在String类检查索引之前的char数组中查找char。
看起来足够短的Strings第一种方法(即chatAt
方法)由于这种索引检查而有点慢。但是如果String足够长,复制整个char数组会变慢,第一种方法会更快。字符串越长,toCharArray
执行速度越慢。尝试更改for(int j = 0; j < 10000; j++)
循环中的限制以查看它。
如果我们让JVM预热代码运行得更快,但比例是相同的。
毕竟它只是微优化。
答案 5 :(得分:2)
如果您考虑 str.toCharArray()的时间复杂度,@ Saint Hill的回答,
即使对于非常大的字符串,第一个也更快。您可以运行以下代码自己查看。
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)
答案 6 :(得分:2)
String.toCharArray()
创建新的char数组,意味着分配字符串长度的内存,然后使用System.arraycopy()
复制字符串的原始char数组,然后将此副本返回给调用者。
String.charAt()从原始副本返回位置i
处的字符,这就是为什么String.charAt()
将比String.toCharArray()
更快的原因。
虽然,String.toCharArray()
从原始String数组返回copy而不是char,其中String.charAt()
返回原始char数组中的字符。
下面的代码返回此字符串的指定索引处的值。
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
下面的代码返回一个新分配的字符数组,其长度是该字符串的长度
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;
}
答案 7 :(得分:1)
第二个导致创建一个新的char数组,并将String中的所有字符复制到这个新的char数组,所以我猜第一个字符更快(并且内存不足)。