今天看到网上一个关于代码效率优化的视频,对下面这段字符串拼接的代码进行优化。因为上面的方式会多在栈中多创建一个引用。
修改前:
for (int i = 0; i < 1000_0000; i++) {
String v1 = map.get("k1");
String v2 = map.get("k2");
String s = v1 + " " + v2;
}
修改后:
for (int i = 0; i < 1000_0000; i++) {
String s = map.get("k1") + "-" + map.get("k2");
}
如果是为了节省空间这样优化没问题 (但是感觉也收效甚微),我想起以前也遇到过类似这种问题,从速度上讲,执行速度上是上面这种方式更快。
于是我就又写了个demo测试一下( jdk1.8 )。如下:
public static void main(String[] args) {
long be = System.currentTimeMillis();
Map<String, String> map = new HashMap<>(2, 1);
map.put("k1", "v1");
map.put("k2", "v2");
for (int i = 0; i < 1000_0000; i++) {
String v1 = map.get("k1");
String v2 = map.get("k2");
String s = v1 + "-" + v2;
}
System.out.println(System.currentTimeMillis() - be);
}
在我的电脑上,结果大概是 186 毫秒左右
public static void main(String[] args) {
long be = System.currentTimeMillis();
Map<String, String> map = new HashMap<>(2, 1);
map.put("k1", "v1");
map.put("k2", "v2");
for (int i = 0; i < 1000_0000; i++) {
String s = map.get("k1") + "-" + map.get("k2");
}
System.out.println(System.currentTimeMillis() - be);
}
在我的电脑上,结果大概是 453 毫秒左右
结果是第一种方式比第二种快了很多。为什么?
这也是面试八股文的核心,每个 java 仔可能都背得滚瓜烂熟,但是实际使用可能一不小心就忽略了。这其实算是一个经验或者说玄学,因为鲜有人知道 jvm 里的具体实现,所以也都是硬背概念。
简言之就是:直接用字符串或者字符串变量拼接创建String对象,会去检查字符串常量池,池中没有就在池中创建一个,有则直接使用,不会在堆中再重复创建。如下:s的创建就有常量池优化
String a = "a";
String b = "b";
String s = a + b + "c";
由于我测试的demo写死的 map 的 key (k1 和 k2),所以 s = v1 + “-” + v2 也是固定值,在第一次创建后就一直在常量池里。所以方式1会比方式2快非常多。
我改一下demo,用不重复的 map 值来拼接,如下: 每次 get 到的值都是不同的,所以 s 每次都不相同
public static void main(String[] args) {
long be = System.currentTimeMillis();
Map<String, String> map = new HashMap<>(2, 1);
for (int i = 0; i < 1000_0000; i++) {
map.put(Integer.toString(i),Integer.toString(i));
}
for (int i = 0; i < 1000_0000; i++) {
String v1 = map.get(Integer.toString(i));
String v2 = map.get(Integer.toString(i));
String s = v1 + "-" + v2;
}
System.out.println(System.currentTimeMillis() - be);
}
在我的电脑上,结果大概是 13427 毫秒左右
public static void main(String[] args) {
long be = System.currentTimeMillis();
Map<String, String> map = new HashMap<>(2, 1);
for (int i = 0; i < 1000_0000; i++) {
map.put(Integer.toString(i), Integer.toString(i));
}
for (int i = 0; i < 1000_0000; i++) {
String s = map.get(Integer.toString(i)) + "-" + map.get(Integer.toString(i));
}
System.out.println(System.currentTimeMillis() - be);
}
在我的电脑上,结果大概是 13778 毫秒左右
很明显,这一次每次拼接的字符串都不相同,所以常量池优化没起作用,最后运行结果相差无几。
上面测试了拼接字符串 全部相同 和 全部不同 的情况,
最后再修改下demo为,部分相同,如下。(提前预测,第一种方式还是会快很多)
public static void main(String[] args) {
long be = System.currentTimeMillis();
Map<String, String> map = new HashMap<>(2, 1);
for (int i = 0; i < 1000; i++) {
map.put(Integer.toString(i), Integer.toString(i));
}
for (int i = 0; i < 1000_0000; i++) {
String v1 = map.get(Integer.toString(i % 1000));
String v2 = map.get(Integer.toString(i % 1000));
String s = v1 + "-" + v2;
}
System.out.println(System.currentTimeMillis() - be);
}
在我的电脑上,结果大概是 747 毫秒左右
public static void main(String[] args) {
long be = System.currentTimeMillis();
Map<String, String> map = new HashMap<>(2, 1);
for (int i = 0; i < 1000; i++) {
map.put(Integer.toString(i), Integer.toString(i));
}
for (int i = 0; i < 1000_0000; i++) {
String s = map.get(Integer.toString(i % 1000)) + "-" + map.get(Integer.toString(i % 1000));
}
System.out.println(System.currentTimeMillis() - be);
}
在我的电脑上,结果大概是 1003 毫秒左右
预测正确。
所以结论:如果拼接的字符串有很多重复的,那么一定要采用方式1,有常量池的优化速度会快很多,在栈里多创建一个引用的影响也微乎其微。
如果拼接的字符串没有或者极少重复,那使用方式1或2都可以。方式2会更省空间一点点,看自己的取舍。