在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 启动时间等,而不仅仅是两次调用 System.currentTimeMillis()
之间的差异,那就太好了.
原文由 Óscar López 发布,翻译遵循 CC BY-SA 4.0 许可协议
第一次更新:在生产环境中尝试之前(不建议),请先阅读: http ://www.javaspecialists.eu/archive/Issue237.html 从 Java 9 开始,所描述的解决方案将不再有效, 因为现在 Java 默认会将字符串存储为 byte[]。
第二次更新:截至 2016 年 10 月 25 日,在我的 AMDx64 8core 和源代码 1.8 上,使用 ‘charAt’ 和字段访问没有区别。看来 jvm 已充分优化以内联和简化任何 ‘string.charAt(n)’ 调用。
第三次更新:截至 2020 年 9 月 7 日,在我的 Ryzen 1950-X 16 内核和源代码 1.14 上,“charAt1”比字段访问慢 9 倍,“charAt2”比字段访问慢 4 倍。现场访问权重新成为明显的赢家。请注意,该程序需要对 Java 9+ 版本的 jvms 使用 byte[] 访问。
这完全取决于被检查的
String
的长度。如果,正如问题所说,它是针对 长 字符串,检查字符串的最快方法是使用反射来访问字符串的支持char[]
。使用 JDK 8(win32 和 win64)在 64 AMD Phenom II 4 核 955 @ 3.2 GHZ(客户端模式和服务器模式)上使用 9 种不同技术(见下文!)进行的完全随机基准测试表明,使用
String.charAt(n)
对于小字符串来说是最快的,使用reflection
访问字符串后备数组几乎是大字符串的两倍。本实验
尝试了 9 种不同的优化技术。
所有字符串内容都是随机的
测试是针对从 0、1、2、4、8、16 等开始的 2 倍数的字符串大小进行的。
每个字符串大小进行 1,000 次测试
每次测试都会随机排列。换句话说,每次测试都是以随机顺序完成的,超过 1000 次。
整个测试套件向前和向后完成,以显示 JVM 预热对优化和时间的影响。
整个套件执行两次,一次在
-client
模式下,另一个在-server
模式下。结论
-客户端模式(32位)
对于 长度为 1 到 256 个字符的 字符串,调用
string.charAt(i)
平均每秒处理 1340 万到 5.88 亿个字符。此外,它总体上快 5.5%(客户端)和 13.9%(服务器),如下所示:
而不是像这样使用本地最终长度变量:
对于长度为 512 到 256K 个字符 的长字符串,使用反射访问 String 的后备数组是最快的。 这种技术几乎是 String.charAt(i) 的两倍(快 178%)。此范围内的平均速度为每秒 11.11 亿个字符。
该字段必须提前获得,然后可以在库中的不同字符串上重复使用。有趣的是,与上面的代码不同,使用字段访问,使用本地最终长度变量比在循环检查中使用“chars.length”快 9%。以下是最快设置字段访问的方法:
-server 模式特别说明
在我的 AMD 64 机器上的 64 位 Java 机器上,在服务器模式下的 32 个字符长度的字符串后,字段访问开始获胜。直到客户端模式下的 512 个字符长度才出现这种情况。
另外值得注意的是,我认为,当我在服务器模式下运行 JDK 8(32 位构建)时,大字符串和小字符串的整体性能都慢了 7%。这是 JDK 8 早期版本的 build 121 Dec 2013。所以,就目前而言,32 位服务器模式似乎比 32 位客户端模式慢。
话虽如此……似乎唯一值得调用的服务器模式是在 64 位机器上。否则它实际上会影响性能。
对于在 AMD64 上运行
-server mode
的 32 位构建,我可以这样说:还值得一提的是,String.chars()(Stream 和并行版本)是个败笔。比任何其他方式都慢。
Streams
API 是一种执行一般字符串操作的相当慢的方法。愿望清单
Java String 可以具有接受优化方法的谓词,例如 contains(predicate)、forEach(consumer)、forEachWithIndex(consumer)。因此,无需用户知道长度或重复调用 String 方法,这些可以帮助解析库
beep-beep beep
加速。继续做梦:)
快乐的弦乐!
~SH
该测试使用以下 9 种方法来测试字符串是否存在空格:
“charAt1”——以通常的方式检查字符串内容:
“charAt2” – 与上面相同,但使用 String.length() 而不是为 LENGTh 制作最终的本地 int
“stream” – 使用新的 JAVA-8 String 的 IntStream 并传递一个谓词来进行检查
“streamPara”——和上面一样,但是 OH-LA-LA——并行!!!
“reuse” – 用字符串内容重新填充一个可重复使用的 char[]
“new1” – 从字符串中获取 char[] 的新副本
“new2” – 同上,但使用 “FOR-EACH”
“field1”——花式!!获得访问字符串内部 char[] 的字段
“field2” – 同上,但使用 “FOR-EACH”
客户的复合结果
-client
模式(前向和后向测试相结合)注意:在我的 AMD64 机器上,Java 32 位的 -client 模式和 Java 64 位的 -server 模式与下面的相同。
服务器的复合结果
-server
模式(前向和后向测试相结合)注意:这是在 AMD64 上以服务器模式运行的 Java 32 位测试。 Java 64 位的服务器模式与客户端模式中的 Java 32 位相同,只是字段访问在 32 个字符大小后开始获胜。
完整的可运行程序代码
(要在 Java 7 及更早版本上进行测试,请删除两个流测试)