为什么 String.toUpperCase() 这么慢?
Why is String.toUpperCase() so slow?
此代码比标准 String.toUpperCase() 函数快约 3 倍:
public static String toUpperString(String pString) {
if (pString != null) {
char[] retChar = pString.toCharArray();
for (int idx = 0; idx < pString.length(); idx++) {
char c = retChar[idx];
if (c >= 'a' && c <= 'z') {
retChar[idx] = (char) (c & -33);
}
}
return new String(retChar);
} else {
return null;
}
}
为什么这么快? String.toUpperCase() 还做了哪些其他工作?
换句话说,是否存在此代码不起作用的情况?
执行 2,000,000 次随机长字符串(纯文本)的基准测试结果:
toUpperString(String):3514.339 毫秒 - 大约 3.5 秒
String.toUpperCase():9705.397 毫秒 - 将近 10 秒
** 更新
我已经添加了 "latin" 检查并将其用作基准(对于那些不相信我的人):
public class BenchmarkUpperCase {
public static String[] randomStrings;
public static String nextRandomString() {
SecureRandom random = new SecureRandom();
return new BigInteger(500, random).toString(32);
}
public static String customToUpperString(String pString) {
if (pString != null) {
char[] retChar = pString.toCharArray();
for (int idx = 0; idx < pString.length(); idx++) {
char c = retChar[idx];
if (c >= 'a' && c <= 'z') {
retChar[idx] = (char) (c & -33);
} else if (c >= 192) { // now catering for other than latin...
retChar[idx] = Character.toUpperCase(c);
}
}
return new String(retChar);
} else {
return null;
}
}
public static void main(String... args) {
long timerStart, timePeriod = 0;
randomStrings = new String[1000];
for (int idx = 0; idx < 1000; idx++) {
randomStrings[idx] = nextRandomString();
}
String dummy = null;
for (int count = 1; count <= 5; count++) {
timerStart = System.nanoTime();
for (int idx = 0; idx < 20000000; idx++) {
dummy = randomStrings[idx % 1000].toUpperCase();
}
timePeriod = System.nanoTime() - timerStart;
System.out.println(count + " String.toUpper() : " + (timePeriod / 1000000));
}
for (int count = 1; count <= 5; count++) {
timerStart = System.nanoTime();
for (int idx = 0; idx < 20000000; idx++) {
dummy = customToUpperString(randomStrings[idx % 1000]);
}
timePeriod = System.nanoTime() - timerStart;
System.out.println(count + " customToUpperString() : " + (timePeriod / 1000000));
}
}
}
我得到这些结果:
1 String.toUpper() : 10724
2 String.toUpper() : 10551
3 String.toUpper() : 10551
4 String.toUpper() : 10660
5 String.toUpper() : 10575
1 customToUpperString() : 6687
2 customToUpperString() : 6684
3 customToUpperString() : 6686
4 customToUpperString() : 6693
5 customToUpperString() : 6710
仍然快了大约 60%。
我 运行 简单的 jmh 基准测试来比较两种方法 #toUpperString
和默认 j8 #toUpperCase
结果如下:
Benchmark Mode Cnt Score Error Units
MyBenchmark.customToString avgt 20 3307.137 ± 81.192 ns/op
MyBenchmark.defaultToString avgt 20 3384.921 ± 75.357 ns/op
测试实现是:
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1, warmups = 1)
@Threads(1)
public class MyBenchmark {
public static String toUpperString(String pString) {
if (pString != null) {
char[] retChar = pString.toCharArray();
for (int idx = 0; idx < pString.length(); idx++) {
char c = retChar[idx];
if (c >= 'a' && c <= 'z') {
retChar[idx] = (char) (c & -33);
}
}
return new String(retChar);
} else {
return null;
}
}
private SecureRandom random = new SecureRandom();
public String nextSessionId() {
return new BigInteger(130, random).toString(32);
}
@Setup
public void init() {
}
@Benchmark
public Object customToString() {
return toUpperString(nextSessionId());
}
@Benchmark
public String defaultToString() {
return nextSessionId().toUpperCase();
}
}
根据本次测试的score
,此方法不比默认快3倍。
In other words, are there cases in which this code will not work?
是的。即使您更新后的代码也无法在德语中正常工作,因为它没有涵盖“ß”的特殊情况。
此字母仅以小写形式存在,并转换为大写形式的 double s:
String bla = "blöße";
System.out.println(customToUpperString(bla)); // BLÖßE <- wrong
System.out.println(bla.toUpperCase(Locale.GERMANY)); // BLÖSSE <- right
我敢肯定在其他语言中还有很多这样的特殊情况。
针对 java.lang.String
检查 source code 很有启发性:
标准版本使用了相当长的篇幅来避免在不必要时创建新字符串。这需要对字符串进行两次传递。
标准版使用语言环境对象对所有字符进行大小写转换。您只对大于 192 的字符执行此操作。虽然这可能适用于常见的语言环境,但某些语言环境(当前或未来......或自定义)可能会有 "interesting" 大写规则适用于小于 192 的字符192也是。
标准版本通过 Unicode 代码点而不是代码单元正确地转换为大写。 (如果字符串包含代理字符,则按代码单元转换可能会中断或给出错误答案。)
"doing it correctly" 的惩罚是 toUppercase
的标准版本比您的 1 版本慢。但在您的版本不会给出正确答案的情况下,它会给出正确答案。
请注意,由于您正在测试 ASCII 字符串,因此您不会遇到 toUppercase
版本给出错误答案的情况。
1 - 根据您的基准...但请参阅其他答案!
此代码比标准 String.toUpperCase() 函数快约 3 倍:
public static String toUpperString(String pString) {
if (pString != null) {
char[] retChar = pString.toCharArray();
for (int idx = 0; idx < pString.length(); idx++) {
char c = retChar[idx];
if (c >= 'a' && c <= 'z') {
retChar[idx] = (char) (c & -33);
}
}
return new String(retChar);
} else {
return null;
}
}
为什么这么快? String.toUpperCase() 还做了哪些其他工作? 换句话说,是否存在此代码不起作用的情况?
执行 2,000,000 次随机长字符串(纯文本)的基准测试结果:
toUpperString(String):3514.339 毫秒 - 大约 3.5 秒
String.toUpperCase():9705.397 毫秒 - 将近 10 秒
** 更新
我已经添加了 "latin" 检查并将其用作基准(对于那些不相信我的人):
public class BenchmarkUpperCase {
public static String[] randomStrings;
public static String nextRandomString() {
SecureRandom random = new SecureRandom();
return new BigInteger(500, random).toString(32);
}
public static String customToUpperString(String pString) {
if (pString != null) {
char[] retChar = pString.toCharArray();
for (int idx = 0; idx < pString.length(); idx++) {
char c = retChar[idx];
if (c >= 'a' && c <= 'z') {
retChar[idx] = (char) (c & -33);
} else if (c >= 192) { // now catering for other than latin...
retChar[idx] = Character.toUpperCase(c);
}
}
return new String(retChar);
} else {
return null;
}
}
public static void main(String... args) {
long timerStart, timePeriod = 0;
randomStrings = new String[1000];
for (int idx = 0; idx < 1000; idx++) {
randomStrings[idx] = nextRandomString();
}
String dummy = null;
for (int count = 1; count <= 5; count++) {
timerStart = System.nanoTime();
for (int idx = 0; idx < 20000000; idx++) {
dummy = randomStrings[idx % 1000].toUpperCase();
}
timePeriod = System.nanoTime() - timerStart;
System.out.println(count + " String.toUpper() : " + (timePeriod / 1000000));
}
for (int count = 1; count <= 5; count++) {
timerStart = System.nanoTime();
for (int idx = 0; idx < 20000000; idx++) {
dummy = customToUpperString(randomStrings[idx % 1000]);
}
timePeriod = System.nanoTime() - timerStart;
System.out.println(count + " customToUpperString() : " + (timePeriod / 1000000));
}
}
}
我得到这些结果:
1 String.toUpper() : 10724
2 String.toUpper() : 10551
3 String.toUpper() : 10551
4 String.toUpper() : 10660
5 String.toUpper() : 10575
1 customToUpperString() : 6687
2 customToUpperString() : 6684
3 customToUpperString() : 6686
4 customToUpperString() : 6693
5 customToUpperString() : 6710
仍然快了大约 60%。
我 运行 简单的 jmh 基准测试来比较两种方法 #toUpperString
和默认 j8 #toUpperCase
结果如下:
Benchmark Mode Cnt Score Error Units
MyBenchmark.customToString avgt 20 3307.137 ± 81.192 ns/op
MyBenchmark.defaultToString avgt 20 3384.921 ± 75.357 ns/op
测试实现是:
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1, warmups = 1)
@Threads(1)
public class MyBenchmark {
public static String toUpperString(String pString) {
if (pString != null) {
char[] retChar = pString.toCharArray();
for (int idx = 0; idx < pString.length(); idx++) {
char c = retChar[idx];
if (c >= 'a' && c <= 'z') {
retChar[idx] = (char) (c & -33);
}
}
return new String(retChar);
} else {
return null;
}
}
private SecureRandom random = new SecureRandom();
public String nextSessionId() {
return new BigInteger(130, random).toString(32);
}
@Setup
public void init() {
}
@Benchmark
public Object customToString() {
return toUpperString(nextSessionId());
}
@Benchmark
public String defaultToString() {
return nextSessionId().toUpperCase();
}
}
根据本次测试的score
,此方法不比默认快3倍。
In other words, are there cases in which this code will not work?
是的。即使您更新后的代码也无法在德语中正常工作,因为它没有涵盖“ß”的特殊情况。 此字母仅以小写形式存在,并转换为大写形式的 double s:
String bla = "blöße";
System.out.println(customToUpperString(bla)); // BLÖßE <- wrong
System.out.println(bla.toUpperCase(Locale.GERMANY)); // BLÖSSE <- right
我敢肯定在其他语言中还有很多这样的特殊情况。
针对 java.lang.String
检查 source code 很有启发性:
标准版本使用了相当长的篇幅来避免在不必要时创建新字符串。这需要对字符串进行两次传递。
标准版使用语言环境对象对所有字符进行大小写转换。您只对大于 192 的字符执行此操作。虽然这可能适用于常见的语言环境,但某些语言环境(当前或未来......或自定义)可能会有 "interesting" 大写规则适用于小于 192 的字符192也是。
标准版本通过 Unicode 代码点而不是代码单元正确地转换为大写。 (如果字符串包含代理字符,则按代码单元转换可能会中断或给出错误答案。)
"doing it correctly" 的惩罚是 toUppercase
的标准版本比您的 1 版本慢。但在您的版本不会给出正确答案的情况下,它会给出正确答案。
请注意,由于您正在测试 ASCII 字符串,因此您不会遇到 toUppercase
版本给出错误答案的情况。
1 - 根据您的基准...但请参阅其他答案!