为什么Java11中的空字符串的String.strip()比String.trim()快5倍
我遇到了一个有趣的场景。由于某些原因,Java 11中的为什么Java11中的空字符串的String.strip()比String.trim()快5倍,java,string,performance,microbenchmark,java-11,Java,String,Performance,Microbenchmark,Java 11,我遇到了一个有趣的场景。由于某些原因,Java 11中的strip()对空字符串(仅包含空格)的速度明显快于trim() 基准 public class Test { public static final String TEST_STRING = " "; // 3 whitespaces @Benchmark @Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS) @Measureme
strip()
对空字符串(仅包含空格)的速度明显快于trim()
基准
public class Test {
public static final String TEST_STRING = " "; // 3 whitespaces
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testTrim() {
TEST_STRING.trim();
}
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testStrip() {
TEST_STRING.strip();
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
@Warmup(iterations = 5, time = 1, timeUnit = SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = SECONDS)
@Fork(value = 3)
@BenchmarkMode(Mode.Throughput)
public class Test {
private static final String BLANK = ""; // Blank
private static final String EMPTY = " "; // 3 spaces
private static final String ASCII = " abc "; // ASCII characters only
private static final String UNICODE = " абв "; // Russian Characters
private static final String BIG = EMPTY.concat("Test".repeat(100)).concat(EMPTY);
@Benchmark
public void blankTrim() {
BLANK.trim();
}
@Benchmark
public void blankStrip() {
BLANK.strip();
}
@Benchmark
public void emptyTrim() {
EMPTY.trim();
}
@Benchmark
public void emptyStrip() {
EMPTY.strip();
}
@Benchmark
public void asciiTrim() {
ASCII.trim();
}
@Benchmark
public void asciiStrip() {
ASCII.strip();
}
@Benchmark
public void unicodeTrim() {
UNICODE.trim();
}
@Benchmark
public void unicodeStrip() {
UNICODE.strip();
}
@Benchmark
public void bigTrim() {
BIG.trim();
}
@Benchmark
public void bigStrip() {
BIG.strip();
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
结果
# Run complete. Total time: 00:04:16
Benchmark Mode Cnt Score Error Units
Test.testStrip thrpt 200 2067457963.295 ± 12353310.918 ops/s
Test.testTrim thrpt 200 402307182.894 ± 4559641.554 ops/s
# Run complete. Total time: 00:05:23
Benchmark Mode Cnt Score Error Units
Test.asciiStrip thrpt 15 356846913.133 ± 4096617.178 ops/s
Test.asciiTrim thrpt 15 371319467.629 ± 4396583.099 ops/s
Test.bigStrip thrpt 15 29058105.304 ± 1909323.104 ops/s
Test.bigTrim thrpt 15 28529199.298 ± 1794655.012 ops/s
Test.blankStrip thrpt 15 1556405453.206 ± 67230630.036 ops/s
Test.blankTrim thrpt 15 1587932109.069 ± 19457780.528 ops/s
Test.emptyStrip thrpt 15 2126290275.733 ± 23402906.719 ops/s
Test.emptyTrim thrpt 15 406354680.805 ± 14359067.902 ops/s
Test.unicodeStrip thrpt 15 37320438.099 ± 399421.799 ops/s
Test.unicodeTrim thrpt 15 88226653.577 ± 1628179.578 ops/s
显然,strip()
比trim()
好5倍
尽管对于非空字符串,结果几乎相同:
public class Test {
public static final String TEST_STRING = " Test String ";
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testTrim() {
TEST_STRING.trim();
}
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testStrip() {
TEST_STRING.strip();
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
# Run complete. Total time: 00:04:16
Benchmark Mode Cnt Score Error Units
Test.testStrip thrpt 200 126939018.461 ± 1462665.695 ops/s
Test.testTrim thrpt 200 141868439.680 ± 1243136.707 ops/s
为什么?这是一个错误还是我做错了
测试环境
- CPU-英特尔至强E3-1585L v5@3.00 GHz
- 操作系统-Windows 7 SP 1 64位
- JVM-Oracle JDK 11.0.1
- Benchamrk-JMH v 1.19
更新 为不同的字符串(空、空等)添加了更多性能测试 基准
public class Test {
public static final String TEST_STRING = " "; // 3 whitespaces
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testTrim() {
TEST_STRING.trim();
}
@Benchmark
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public void testStrip() {
TEST_STRING.strip();
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
@Warmup(iterations = 5, time = 1, timeUnit = SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = SECONDS)
@Fork(value = 3)
@BenchmarkMode(Mode.Throughput)
public class Test {
private static final String BLANK = ""; // Blank
private static final String EMPTY = " "; // 3 spaces
private static final String ASCII = " abc "; // ASCII characters only
private static final String UNICODE = " абв "; // Russian Characters
private static final String BIG = EMPTY.concat("Test".repeat(100)).concat(EMPTY);
@Benchmark
public void blankTrim() {
BLANK.trim();
}
@Benchmark
public void blankStrip() {
BLANK.strip();
}
@Benchmark
public void emptyTrim() {
EMPTY.trim();
}
@Benchmark
public void emptyStrip() {
EMPTY.strip();
}
@Benchmark
public void asciiTrim() {
ASCII.trim();
}
@Benchmark
public void asciiStrip() {
ASCII.strip();
}
@Benchmark
public void unicodeTrim() {
UNICODE.trim();
}
@Benchmark
public void unicodeStrip() {
UNICODE.strip();
}
@Benchmark
public void bigTrim() {
BIG.trim();
}
@Benchmark
public void bigStrip() {
BIG.strip();
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
结果
# Run complete. Total time: 00:04:16
Benchmark Mode Cnt Score Error Units
Test.testStrip thrpt 200 2067457963.295 ± 12353310.918 ops/s
Test.testTrim thrpt 200 402307182.894 ± 4559641.554 ops/s
# Run complete. Total time: 00:05:23
Benchmark Mode Cnt Score Error Units
Test.asciiStrip thrpt 15 356846913.133 ± 4096617.178 ops/s
Test.asciiTrim thrpt 15 371319467.629 ± 4396583.099 ops/s
Test.bigStrip thrpt 15 29058105.304 ± 1909323.104 ops/s
Test.bigTrim thrpt 15 28529199.298 ± 1794655.012 ops/s
Test.blankStrip thrpt 15 1556405453.206 ± 67230630.036 ops/s
Test.blankTrim thrpt 15 1587932109.069 ± 19457780.528 ops/s
Test.emptyStrip thrpt 15 2126290275.733 ± 23402906.719 ops/s
Test.emptyTrim thrpt 15 406354680.805 ± 14359067.902 ops/s
Test.unicodeStrip thrpt 15 37320438.099 ± 399421.799 ops/s
Test.unicodeTrim thrpt 15 88226653.577 ± 1628179.578 ops/s
测试环境是一样的
只有一个有趣的发现。包含Unicode字符的字符串比strip()的速度快。在查看OpenJDK的源代码后,假设Oracle版本的实现是类似的,我认为差异可以通过以下事实来解释:
将尝试查找第一个非空白字符,如果找不到,只返回strip
“”
将始终返回trim
新字符串(…子字符串…)
strip
比trim
优化了一点点,至少在OpenJDK中是这样,因为除非必要,否则它会避开新对象的创建
(注意:我没有费心检查这些方法的unicode版本。)在OpenJDK 11.0.1上
String.strip()
(实际上stringlati1.strip()
)通过返回一个插入的字符串常量,将剥离优化为空字符串
public static String strip(byte[] value) {
int left = indexOfNonWhitespace(value);
if (left == value.length) {
return "";
}
而String.trim()
(实际上StringLatin1.trim()
)总是分配一个新的String
对象。在您的示例中,st=3
和len=3
so
return ((st > 0) || (len < value.length)) ?
newString(value, st, len - st) : null;
根据上述假设,我们可以更新基准以与非空的字符串进行比较,该字符串不应受到所述String.strip()优化的影响:
@Warmup(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public class MyBenchmark {
public static final String EMPTY_STRING = " "; // 3 whitespaces
public static final String NOT_EMPTY_STRING = " a "; // 3 whitespaces with a in the middle
@Benchmark
public void testEmptyTrim() {
EMPTY_STRING.trim();
}
@Benchmark
public void testEmptyStrip() {
EMPTY_STRING.strip();
}
@Benchmark
public void testNotEmptyTrim() {
NOT_EMPTY_STRING.trim();
}
@Benchmark
public void testNotEmptyStrip() {
NOT_EMPTY_STRING.strip();
}
}
对于非空的字符串
,运行它不会显示strip()
和trim()
之间的显著差异。奇怪的是,修剪成空的字符串仍然是最慢的:
Benchmark Mode Cnt Score Error Units
MyBenchmark.testEmptyStrip thrpt 100 1887848947.416 ± 257906287.634 ops/s
MyBenchmark.testEmptyTrim thrpt 100 206638996.217 ± 57952310.906 ops/s
MyBenchmark.testNotEmptyStrip thrpt 100 399701777.916 ± 2429785.818 ops/s
MyBenchmark.testNotEmptyTrim thrpt 100 385144724.856 ± 3928016.232 ops/s
是的。在Java11或更早版本中,.trim()似乎总是创建一个新字符串(),但strip()返回一个缓存字符串。您可以测试这个简单的代码,并自己验证它
public class JavaClass{
public static void main(String[] args){
//prints false
System.out.println(" ".trim()=="");//CREATING A NEW STRING()
}
}
vs
strip()
较新…(不使用getChar
{unicode},只检查尾随字符中是否有空字符串,返回“
{literal}而不是新字符串(字节)
),感谢您的解释!我有点想知道为什么JDK开发人员没有像strip()
那样优化trim()
。5x是一个巨大的性能差异。