String.substring() 的性能与手动查找复制的 char[] 数组相比如何?
How does the performance of String.substring() compare with manual lookups into a copied char[] array?
我正在研究一个连续扫描大量相对较短的字符串并从中生成大量子字符串的函数。该函数将用于大容量字符串处理系统。我更喜欢先优化内存,CPU 其次,因为我在系统上遇到的内存压力大于 CPU 压力。在这种情况下,性能如何比较:
//Java-ish PseudoCode
functionParent(List<String> strings) {
StringBuilder result;
for(String s : strings) {
result.appendAll(functionA(s));
}
}
functionA(String arg1) {
results.add(arg1.substring(i, i + length)); //hotspot here
}
和:
//Java-ish PseudoCode
functionParent(List<String> strings) {
StringBuilder result;
for(String s : strings) {
result.appendAll(functionB(s.toCharArray()));
}
}
functionB(char[] arg1) {
results.add(new String(arg1,i, length)); //hotspot here
}
据我目前的测试所知,在内存方面这是一次清洗(char[] 分配可以忽略不计并且不影响 GC,两者都创建相同数量的新 strings/the substring() 内存泄漏已在不久前修复),并且 CPU 明智的是,由于对子字符串的不断查找,char[] 版本获胜。这听起来正确吗?我在分析中遗漏了什么吗?
解法注意事项
根据以下答案,functionB
似乎是最快的。还要注意 functionParent 中有一个 StringBuilder - StringBuilder 有一个 1append(char[], index, length)` 函数
String.substring
- 除了绑定检查,通过调用 new String(value, beginIndex, subLen)
结束 - 除非结果是整个原始字符串,在这种情况下它只是 returns 原始字符串.
所以我会用你的第二个/“functionB
”例子。
如果您对 String
操作合并大量 substring
和 append
类似操作有内存压力,您应该了解 CharSequence
抽象。许多 API 已经准备好使用这些代替 String
.
由于 String
本身实现了 CharSequence
,您可以更改您的方法以接受 CharSequence
输入而不是 String
,而无需改变它们的用途。 String
的 subSequence
实现与 substring
相同,因此复制内容,但您可以使用 CharBuffer.wrap(string, start, end)
.
创建子序列而不复制
如前所述,大多数操作都支持 CharSequence
输入,尤其是您的用例
StringBuilder.append(CharSequence s)
StringBuilder.append(CharSequence s, int start, int end)
前面已经提到了CharBuffer.wrap(CharSequence csq, int start, int end)
还有 CharSequence.subSequence(int start, int end)
,但如前所述,如果基础序列是 String
,您将获得一个副本,而 CharBuffer.wrap
保证非复制行为
CharBuffer.wrap(char[] array, int offset, int length)
作为获得 char[]
数组的 CharSequence
表示而不复制 的规范方式
String.contentEquals(CharSequence)
- 整个正则表达式包,如果需要,请参见,例如
Pattern.matcher(CharSequence)
- etc.
请注意,StringBuilder
也实现了 CharSequence
,因此允许您的操作往返,而无需创建中间 String
副本。
我正在研究一个连续扫描大量相对较短的字符串并从中生成大量子字符串的函数。该函数将用于大容量字符串处理系统。我更喜欢先优化内存,CPU 其次,因为我在系统上遇到的内存压力大于 CPU 压力。在这种情况下,性能如何比较:
//Java-ish PseudoCode
functionParent(List<String> strings) {
StringBuilder result;
for(String s : strings) {
result.appendAll(functionA(s));
}
}
functionA(String arg1) {
results.add(arg1.substring(i, i + length)); //hotspot here
}
和:
//Java-ish PseudoCode
functionParent(List<String> strings) {
StringBuilder result;
for(String s : strings) {
result.appendAll(functionB(s.toCharArray()));
}
}
functionB(char[] arg1) {
results.add(new String(arg1,i, length)); //hotspot here
}
据我目前的测试所知,在内存方面这是一次清洗(char[] 分配可以忽略不计并且不影响 GC,两者都创建相同数量的新 strings/the substring() 内存泄漏已在不久前修复),并且 CPU 明智的是,由于对子字符串的不断查找,char[] 版本获胜。这听起来正确吗?我在分析中遗漏了什么吗?
解法注意事项
根据以下答案,functionB
似乎是最快的。还要注意 functionParent 中有一个 StringBuilder - StringBuilder 有一个 1append(char[], index, length)` 函数
String.substring
- 除了绑定检查,通过调用 new String(value, beginIndex, subLen)
结束 - 除非结果是整个原始字符串,在这种情况下它只是 returns 原始字符串.
所以我会用你的第二个/“functionB
”例子。
如果您对 String
操作合并大量 substring
和 append
类似操作有内存压力,您应该了解 CharSequence
抽象。许多 API 已经准备好使用这些代替 String
.
由于 String
本身实现了 CharSequence
,您可以更改您的方法以接受 CharSequence
输入而不是 String
,而无需改变它们的用途。 String
的 subSequence
实现与 substring
相同,因此复制内容,但您可以使用 CharBuffer.wrap(string, start, end)
.
如前所述,大多数操作都支持 CharSequence
输入,尤其是您的用例
StringBuilder.append(CharSequence s)
StringBuilder.append(CharSequence s, int start, int end)
前面已经提到了
CharBuffer.wrap(CharSequence csq, int start, int end)
还有
CharSequence.subSequence(int start, int end)
,但如前所述,如果基础序列是String
,您将获得一个副本,而CharBuffer.wrap
保证非复制行为CharBuffer.wrap(char[] array, int offset, int length)
作为获得char[]
数组的CharSequence
表示而不复制 的规范方式
String.contentEquals(CharSequence)
- 整个正则表达式包,如果需要,请参见,例如
Pattern.matcher(CharSequence)
- etc.
请注意,StringBuilder
也实现了 CharSequence
,因此允许您的操作往返,而无需创建中间 String
副本。