String
用Java剥离所有不可打印字符的最快方法是什么?
到目前为止,我已经尝试并测量了138字节,131个字符的字符串:
- 字符串的
replaceAll()
-最慢的方法- 517009个结果/秒
- 预编译模式,然后使用Matcher的
replaceAll()
- 637836个结果/秒
- 使用StringBuffer,使用
codepointAt()
一对一获取代码点并追加到StringBuffer- 711946结果/秒
- 使用StringBuffer,使用
charAt()
一对一获取字符并追加到StringBuffer- 1052964结果/秒
- 预分配
char[]
缓冲区,使用charAt()
一对一获取字符并填充该缓冲区,然后转换回String- 2022653个结果/秒
- 预分配2个
char[]
缓冲区-旧的和新的,使用一次获取现有String的所有字符,一次又一次getChars()
遍历旧缓冲区并填充新缓冲区,然后将新缓冲区转换为String-我自己最快的版本- 2502502结果/秒
- 具有2个缓冲区的相同内容-仅使用
byte[]
,getBytes()
并将编码指定为“ utf-8”- 857485结果/秒
- 具有2个
byte[]
缓冲区的相同内容,但将编码指定为常量Charset.forName("utf-8")
- 791076个结果/秒
- 具有2个
byte[]
缓冲区的相同内容,但是将编码指定为1字节本地编码(几乎没有理智的事情)- 370164结果/秒
我的最佳尝试是:
char[] oldChars = new char[s.length()];
s.getChars(0, s.length(), oldChars, 0);
char[] newChars = new char[s.length()];
int newLen = 0;
for (int j = 0; j < s.length(); j++) {
char ch = oldChars[j];
if (ch >= ' ') {
newChars[newLen] = ch;
newLen++;
}
}
s = new String(newChars, 0, newLen);
关于如何使其更快的想法?
回答一个非常奇怪的问题的好处是:为什么直接使用“ utf-8”字符集名称会比使用预分配的静态const产生更好的性能Charset.forName("utf-8")
?
更新资料
- 棘轮怪胎的建议可产生令人印象深刻的3105590结果/秒性能,提高了+ 24%!
- Ed Staub的建议带来了另一个改进-3471017个结果/秒,比以前的最佳增长了+ 12%。
更新2
我已尽力收集了所有建议的解决方案及其交叉变异,并将其作为小型基准测试框架发布在github上。目前,它采用17种算法。其中之一是“特殊的” -Voo1算法(由SO用户Voo提供)使用复杂的反射技巧,从而达到了惊人的速度,但是却弄乱了JVM字符串的状态,因此进行了单独的基准测试。
欢迎您将其签出并运行以确定您的包装盒上的结果。这是我获得的结果摘要。它的规格:
- Debian SID
- Linux 2.6.39-2-amd64(x86_64)
- Java是从软件包安装的
sun-java6-jdk-6.24-1
,JVM将自身标识为- Java(TM)SE运行时环境(内部版本1.6.0_24-b07)
- Java HotSpot(TM)64位服务器VM(内部版本19.1-b02,混合模式)
给定不同的输入数据集,不同的算法最终显示出不同的结果。我已经在3种模式下运行了基准测试:
相同的单个字符串
此模式适用于StringSource
类提供的作为常量的同一单个字符串。摊牌是:
操作数│算法 ──────────┼──────────────────────── 6535947│Voo1 ──────────┼──────────────────────── 5350454│RatchetFreak2EdStaub1GreyCat1 5249343│EdStaub1 5002501│EdStaub1GreyCat1 4859 086│ArrayOfCharFromStringCharAt 4295532│RatchetFreak1 4045307│ArrayOfCharFromArrayOfChar 2 790 178│RatchetFreak2EdStaub1GreyCat2 2583311│RatchetFreak2 1274859│StringBuilderChar 1138174│StringBuilderCodePoint 994727│ArrayOfByteUTF8String 918611│ArrayOfByteUTF8Const 756 086│MatcherReplace 598945│StringReplaceAll 460045│ArrayOfByteWindows1251
以图表形式:(
来源:greycat.ru)
多个字符串,其中100%的字符串包含控制字符
源字符串提供程序使用(0..127)字符集预先生成了大量随机字符串-因此几乎所有字符串都包含至少一个控制字符。算法以循环方式从此预生成的数组中接收字符串。
操作数│算法 ──────────┼──────────────────────── 2123142│Voo1 ──────────┼──────────────────────── 1782214│EdStaub1 1776199│EdStaub1GreyCat1 1694628│ArrayOfCharFromStringCharAt 1481481│ArrayOfCharFromArrayOfChar 1460067│RatchetFreak2EdStaub1GreyCat1 1438435│RatchetFreak2EdStaub1GreyCat2 1366494│棘轮怪2 1349710│棘轮怪胎1 893176│ArrayOfByteUTF8String 817127│ArrayOfByteUTF8Const 778089│StringBuilderChar 734754│StringBuilderCodePoint 377829│ArrayOfByteWindows1251 224140│MatcherReplace 211 104│StringReplaceAll
以图表形式:(
来源:greycat.ru)
多个字符串,其中1%的字符串包含控制字符
与以前相同,但是只有1%的字符串是使用控制字符生成的-其他99%的字符串是使用[32..127]字符集生成的,因此它们根本不能包含控制字符。在我这个地方,这种合成负载最接近该算法的实际应用。
操作数│算法 ──────────┼──────────────────────── 3711952│Voo1 ──────────┼──────────────────────── 2851440│EdStaub1GreyCat1 2455796│EdStaub1 2426007│ArrayOfCharFromStringCharAt 2347969│RatchetFreak2EdStaub1GreyCat2 2242152│RatchetFreak1 2171553│ArrayOfCharFromArrayOfChar 1922707│RatchetFreak2EdStaub1GreyCat1 1857010│棘轮怪2 1 023 751│ArrayOfByteUTF8String 939 055│StringBuilderChar 907194│ArrayOfByteUTF8Const 841963│StringBuilderCodePoint 606465│MatcherReplace 501555│StringReplaceAll 381185│ArrayOfByteWindows1251
以图表形式:(
来源:greycat.ru)
我很难决定谁提供了最佳答案,但是鉴于实际应用中最佳解决方案是由Ed Staub提供/启发的,我想标记他的答案是很公平的。感谢所有参与此活动的人,您的意见非常有帮助且非常宝贵。随时在您的设备上运行测试套件,并提出更好的解决方案(正在使用JNI解决方案,有人吗?)。
参考文献
- 带有基准测试套件的GitHub存储库