首先需要说明的是本文所说的是java中的String的format方法性能,可能其他语言有所差异。
下面进入正题。一般新入职一家公司的时候,会去看新公司现有的代码。我记得我来的时候用了一段时间把项目的各个业务结合代码实现整个看了个遍,我清楚的记得,我刚来的时候,我们总监的一句话,业务永远是最重要的。
当然有一些Java的用法也是以前没见到过。比如以前字符串拼接,都会用String类型直接加号拼接字符串,顶多心里知道其实有StringBuilder和StringBuffer效率更好。但是效率差的不多就还是用字符串拼接。在这里发现有人在代码中用String的format方法,感觉很方便,也就跟着用了。
其实这个这个用法到现在也用了好久了,现在想来性能并不一定很好,也还是简单介绍一下。下面先看个简单的例子吧。
1 |
String b = String.format("id:%d, name:%s", 1, "irfen"); |
用法就是这样,第一个参数是个字符串,里面有一些替换的字符,同时有对应类型,后面是个变长数组参数。其中%d对应整型数字,%c为char类型,%f为浮点型,%s为字符串,%b为布尔型,学过c语言的可能会比较熟悉。当然还有很多其他用法,这里就不详细介绍了。
附带说下,其实我们经常输出的log日志,也有类似的用法。
1 |
logger.info("id:{}, name:{}", 1, "irfen"); |
只不过不用指定具体类型,更省事了~
这样用真的好吗,对于一般要求不高的系统,其实这样用比字符串拼接看起来还更加清晰一点,但是性能怎么样呢。这里之前写的一篇文章,《Java使用JMH进行简单的基准测试Benchmark》(怎么用?点去看看),来做个测试,测试代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package me.irfen.jmh; import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 3) @Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS) @Threads(16) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class StringFormatTest { @Benchmark public void testStringAdd() { String a = "a" + 1 + "b" + 2 + "c"; print(a); } @Benchmark public void testStringFormat() { String a = String.format("a%db%dc", 1, 2); print(a); } private void print(String a) { } } |
我们来运行一下,得到的测试结果如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# JMH 1.14.1 (released 46 days ago) # VM version: JDK 1.8.0_71, VM 25.71-b15 # VM invoker: C:\Program Files\Java\jdk1.8.0_71\jre\bin\java.exe # VM options: -Didea.launcher.port=7532 -Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2016.2.4\bin -Dfile.encoding=UTF-8 # Warmup: 3 iterations, 1 s each # Measurement: 10 iterations, 5 s each # Timeout: 10 min per iteration # Threads: 16 threads, will synchronize iterations # Benchmark mode: Throughput, ops/time # Benchmark: me.irfen.jmh.StringFormatTest.testStringAdd # Run progress: 0.00% complete, ETA 00:03:32 # Fork: 1 of 2 # Warmup Iteration 1: 1765160.048 ops/ms # Warmup Iteration 2: 1963889.973 ops/ms # Warmup Iteration 3: 2072082.071 ops/ms Iteration 1: 2073452.461 ops/ms Iteration 2: 2098403.917 ops/ms Iteration 3: 2071669.851 ops/ms Iteration 4: 2057731.528 ops/ms Iteration 5: 2057762.250 ops/ms Iteration 6: 2076944.228 ops/ms Iteration 7: 2100384.457 ops/ms Iteration 8: 2103323.948 ops/ms Iteration 9: 2107015.190 ops/ms Iteration 10: 2092262.668 ops/ms # Run progress: 25.00% complete, ETA 00:02:59 # Fork: 2 of 2 # Warmup Iteration 1: 1989580.834 ops/ms # Warmup Iteration 2: 1949143.086 ops/ms # Warmup Iteration 3: 2011865.640 ops/ms Iteration 1: 2052582.566 ops/ms Iteration 2: 2054651.084 ops/ms Iteration 3: 2049439.875 ops/ms Iteration 4: 2041924.938 ops/ms Iteration 5: 2034063.807 ops/ms Iteration 6: 2037295.266 ops/ms Iteration 7: 2024204.430 ops/ms Iteration 8: 2081162.560 ops/ms Iteration 9: 2090790.434 ops/ms Iteration 10: 2094408.422 ops/ms Result "testStringAdd": 2069973.694 ±(99.9%) 22210.562 ops/ms [Average] (min, avg, max) = (2024204.430, 2069973.694, 2107015.190), stdev = 25577.716 CI (99.9%): [2047763.132, 2092184.256] (assumes normal distribution) # JMH 1.14.1 (released 46 days ago) # VM version: JDK 1.8.0_71, VM 25.71-b15 # VM invoker: C:\Program Files\Java\jdk1.8.0_71\jre\bin\java.exe # VM options: -Didea.launcher.port=7532 -Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2016.2.4\bin -Dfile.encoding=UTF-8 # Warmup: 3 iterations, 1 s each # Measurement: 10 iterations, 5 s each # Timeout: 10 min per iteration # Threads: 16 threads, will synchronize iterations # Benchmark mode: Throughput, ops/time # Benchmark: me.irfen.jmh.StringFormatTest.testStringFormat # Run progress: 50.00% complete, ETA 00:01:59 # Fork: 1 of 2 # Warmup Iteration 1: 2036.947 ops/ms # Warmup Iteration 2: 4070.937 ops/ms # Warmup Iteration 3: 4453.920 ops/ms Iteration 1: 4440.250 ops/ms Iteration 2: 4593.925 ops/ms Iteration 3: 4466.877 ops/ms Iteration 4: 4461.771 ops/ms Iteration 5: 4421.152 ops/ms Iteration 6: 4326.495 ops/ms Iteration 7: 4330.146 ops/ms Iteration 8: 4448.596 ops/ms Iteration 9: 4368.319 ops/ms Iteration 10: 4534.138 ops/ms # Run progress: 75.00% complete, ETA 00:01:01 # Fork: 2 of 2 # Warmup Iteration 1: 1717.527 ops/ms # Warmup Iteration 2: 4504.847 ops/ms # Warmup Iteration 3: 4370.694 ops/ms Iteration 1: 4527.753 ops/ms Iteration 2: 4580.512 ops/ms Iteration 3: 4487.512 ops/ms Iteration 4: 4541.971 ops/ms Iteration 5: 4575.898 ops/ms Iteration 6: 4519.100 ops/ms Iteration 7: 4574.067 ops/ms Iteration 8: 4606.887 ops/ms Iteration 9: 4566.840 ops/ms Iteration 10: 4682.968 ops/ms Result "testStringFormat": 4502.759 ±(99.9%) 82.444 ops/ms [Average] (min, avg, max) = (4326.495, 4502.759, 4682.968), stdev = 94.943 CI (99.9%): [4420.315, 4585.203] (assumes normal distribution) # Run complete. Total time: 00:04:06 Benchmark Mode Cnt Score Error Units StringFormatTest.testStringAdd thrpt 20 2069973.694 ± 22210.562 ops/ms StringFormatTest.testStringFormat thrpt 20 4502.759 ± 82.444 ops/ms |
直接关注最后一行,性能差好多。用结果说话,String的format性能真的没有字符串拼接性能好,而且是差太多,更别说StringBuilder了。
为什么性能这么差呢?虽然我们得到了结果,也来了解下原因吧。首先可以想到的是,String的format需要去逐个替换占位符,另外由于不同的类型,还需要去匹配类型,这肯定比直接字符串相加性能消耗多了很多。
下面来简单了解下关键部位的源码就知道了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public Formatter format(Locale l, String format, Object ... args) { ensureOpen(); // index of last argument referenced int last = -1; // last ordinary index int lasto = -1; FormatString[] fsa = parse(format); for (int i = 0; i < fsa.length; i++) { FormatString fs = fsa[i]; int index = fs.index(); try { switch (index) { case -2: // fixed string, "%n", or "%%" fs.print(null, l); break; case -1: // relative index if (last < 0 || (args != null && last > args.length - 1)) throw new MissingFormatArgumentException(fs.toString()); fs.print((args == null ? null : args[last]), l); break; case 0: // ordinary index lasto++; last = lasto; if (args != null && lasto > args.length - 1) throw new MissingFormatArgumentException(fs.toString()); fs.print((args == null ? null : args[lasto]), l); break; default: // explicit index last = index - 1; if (args != null && last > args.length - 1) throw new MissingFormatArgumentException(fs.toString()); fs.print((args == null ? null : args[last]), l); break; } } catch (IOException x) { lastException = x; } } return this; } |
这里就不粘这里面的parse方法了,也是个循环。
到此我们应该知道他有多慢,而且为什么这么慢了。以后什么时候该用哪个还是要根据具体情况来选择。
©原创文章,转载请注明来源: 赵伊凡's Blog
©本文链接地址: Java中关于String.format的性能问题
666
少考虑到了一个问题, 那就是jdk 编译转换的一个问题, 字符串在运行期间动态生成的效果比较起来才靠谱