什么是从String
Java中去除所有不可打印字符的最快方法?
到目前为止,我已经尝试过使用138字节,131字符的字符串进行测量:
replaceAll()
- 最慢的方法 replaceAll()
codepointAt()
逐个获取代码点并追加到StringBuffer charAt()
逐个获取字符并追加到StringBuffer char[]
缓冲区,charAt()
逐个获取字符并填充此缓冲区,然后转换回字符串 char[]
缓冲区 - 旧的和新的,一次性使用现有字符串的所有字符,getChars()
逐个迭代旧缓冲区并填充新缓冲区,然后将新缓冲区转换为字符串 - 我自己的最快版本 byte[]
,getBytes()
并指定编码为“utf-8” byte[]
缓冲区相同的东西,但将编码指定为常量Charset.forName("utf-8")
byte[]
缓冲区相同的东西,但指定编码为1字节本地编码(几乎没有一个理智的事情) 我最好的尝试是以下几点:
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”字符集名称直接产生比使用预先分配的静态常量更好的性能Charset.forName("utf-8")
?
从棘轮怪胎的建议产生令人印象深刻的3105590结果/秒的表现,+ 24%的改善!
Ed Staub的建议产生了另一项改进 - 每秒3471017个结果,比之前的最佳结果增加12%。
我尽我所能收集了所有提出的解决方案及其交叉变异,并将它作为github上的小型基准测试框架发布。目前它运行17种算法。其中之一是“特殊的” --Voo1算法(由SO用户Voo提供)采用复杂的反射技巧,从而实现了极佳的速度,但是它扰乱了JVM字符串的状态,因此它被单独进行基准测试。
欢迎您查看并运行它以确定您的盒子上的结果。以下是我对我的结果的总结。它的规格:
sun-java6-jdk-6.24-1
,JVM将自己标识为 给定一组不同的输入数据,不同的算法会显示最终的不同结果。我在3种模式下运行了一个基准测试:
这种模式在StringSource
类提供的同一个单一字符串上作为常量使用。摊牌是:
Ops / s│算法
──────────┼──────────────────────────────
6 535 947│For1
──────────┼──────────────────────────────
5 350 454│RatchetFreak2EdStaub1GreyCat1
5 249 343│EdStaub1
5 002 501│EdStaub1GreyCat1
4 859 086│ArrayOfCharFromStringCharAt
4 295 532│RatchetFreak1
4 045 307│ArrayOfCharFromArrayOfChar
2 790 178│RatchetFreak2EdStaub1GreyCat2
2 583 311│RatchetFreak2
1 274 859│StringBuilderChar
1 138 174│StringBuilderCodePoint
994 727│ArrayOfByteUTF8String
918 611│ArrayOfByteUTF8Const
756 086│MatcherReplace
598 945│StringReplaceAll
460 045│ArrayOfByteWindows1251
以图表形式: http://www.greycat.ru/img/os-chart-single.png
源字符串提供程序使用(0..127)字符集预先生成大量随机字符串 - 因此几乎所有字符串都至少包含一个控制字符。算法以循环方式从此预生成阵列接收字符串。
Ops / s│算法
──────────┼──────────────────────────────
2 123 142│对于.1
──────────┼──────────────────────────────
1 782 214│EdStaub1
1 776 199│EdStaub1GreyCat1
1 694 628│ArrayOfCharFromStringCharAt
1 481 481│ArrayOfCharFromArrayOfChar
1 460 067│RatchetFreak2EdStaub1GreyCat1
1 438 435│RatchetFreak2EdStaub1GreyCat2
1 366 494│RatchetFreak2
1 349 710│RatchetFreak1
893 176│ArrayOfByteUTF8String
817 127│ArrayOfByteUTF8Const
778 089│StringBuilderChar
734 754│StringBuilderCodePoint
377 829│ArrayOfByteWindows1251
224 140│MatcherReplace
211 104│StringReplaceAll
以图表形式: http://www.greycat.ru/img/os-chart-multi100.png
与之前相同,但只有1%的字符串是由控制字符生成的 - 其他99%是在使用[32..127]字符集时生成的,因此它们根本不能包含控制字符。这个综合负载在我的地方最接近这个算法的实际应用。
Ops / s│算法
──────────┼──────────────────────────────
3 711 952│For1
──────────┼──────────────────────────────
2 851 440│EdStaub1GreyCat1
2 455 796│EdStaub1
2 426 007│ArrayOfCharFromStringCharAt
2 347 969│RatchetFreak2EdStaub1GreyCat2
2 242 152│RatchetFreak1
2 171 553│ArrayOfCharFromArrayOfChar
1 922 707│RatchetFreak2EdStaub1GreyCat1
1 857 010│RatchetFreak2
1 023 751│ArrayOfByteUTF8String
939 055│StringBuilderChar
907 194│ArrayOfByteUTF8Const
841 963│StringBuilderCodePoint
606 465│MatcherReplace
501 555│StringReplaceAll
381 185│ArrayOfByteWindows1251
以图表形式: http://www.greycat.ru/img/os-chart-multi1.png
我很难决定谁提供了最好的答案,但考虑到现实世界应用程序的最佳解决方案是由Ed Staub给出/启发的,我想这是公平的标记他的答案。感谢参与此事的所有人,您的意见非常有帮助和宝贵。随意在你的机器上运行测试套件,并提出更好的解决方案(任何人都可以使用JNI解决方案)。
发布于 2018-03-02 14:08:00
如果将此方法嵌入到不是跨线程共享的类中是合理的,则可以重新使用该缓冲区:
char [] oldChars = new char[5];
String stripControlChars(String s)
{
final int inputLen = s.length();
if ( oldChars.length < inputLen )
{
oldChars = new char[inputLen];
}
s.getChars(0, inputLen, oldChars, 0);
等等...
这是一个巨大的胜利 - 20%左右,因为我了解目前的最佳案例。
如果这是用于潜在的大字符串,并且内存“泄漏”是一个问题,则可以使用弱引用。
发布于 2018-03-02 15:49:02
使用1个字符数组可能会更好一些
int length = s.length();
char[] oldChars = new char[length];
s.getChars(0, length, oldChars, 0);
int newLen = 0;
for (int j = 0; j < length; j++) {
char ch = oldChars[j];
if (ch >= ' ') {
oldChars[newLen] = ch;
newLen++;
}
}
s = new String(oldChars, 0, newLen);
我避免了重复的呼叫 s.length();
另一个可能起作用的微型优化是
int length = s.length();
char[] oldChars = new char[length+1];
s.getChars(0, length, oldChars, 0);
oldChars[length]='\0';//avoiding explicit bound check in while
int newLen=-1;
while(oldChars[++newLen]>=' ');//find first non-printable,
// if there are none it ends on the null char I appended
for (int j = newLen; j < length; j++) {
char ch = oldChars[j];
if (ch >= ' ') {
oldChars[newLen] = ch;//the while avoids repeated overwriting here when newLen==j
newLen++;
}
}
s = new String(oldChars, 0, newLen);
https://stackoverflow.com/questions/-100007495
复制相似问题