网站首页 > 技术文章 正文
今天再写一个读取Java class File并进行分析的Demo时,偶然发现了下面这个场景(基于oracle jdk 1.8.0_144):
package test;
public class Test8 {
String s1 = "111", s2 = "222", s3 = "333", s4 = "444";
public String test() {
return s1 + s2 + s3 + s4 + "5555" + "66666666666666666666666666" + "777" + new String("测试测试") + String.valueOf("test test") + "长字符串长字符串长字符串长字符串长字符串长字符串长字符串长字符串长字符串长字符串长字符串长字符串";
}
}
这是一个很简单的类,只完成了字符串的 + 操作,我们查看对应生成的class文件的outline:
// class version 52.0 (52)
// access flags 0x21
public class test/Test8 {
// compiled from: Test8.java
// access flags 0x0
Ljava/lang/String; s1
// access flags 0x0
Ljava/lang/String; s2
// access flags 0x0
Ljava/lang/String; s3
// access flags 0x0
Ljava/lang/String; s4
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 5 L1
ALOAD 0
LDC "111"
PUTFIELD test/Test8.s1 : Ljava/lang/String;
ALOAD 0
LDC "222"
PUTFIELD test/Test8.s2 : Ljava/lang/String;
ALOAD 0
LDC "333"
PUTFIELD test/Test8.s3 : Ljava/lang/String;
ALOAD 0
LDC "444"
PUTFIELD test/Test8.s4 : Ljava/lang/String;
RETURN
L2
LOCALVARIABLE this Ltest/Test8; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x1
public test()Ljava/lang/String;
L0
LINENUMBER 8 L0
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 0
GETFIELD test/Test8.s1 : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
GETFIELD test/Test8.s2 : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
GETFIELD test/Test8.s3 : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
GETFIELD test/Test8.s4 : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "555566666666666666666666666666777"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
NEW java/lang/String
DUP
LDC "\u6d4b\u8bd5\u6d4b\u8bd5"
INVOKESPECIAL java/lang/String.<init> (Ljava/lang/String;)V
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "test test"
INVOKESTATIC java/lang/String.valueOf (Ljava/lang/Object;)Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Ltest/Test8; L0 L1 0
MAXSTACK = 4
MAXLOCALS = 1
}
请注意这段:
// access flags 0x1
public test()Ljava/lang/String;
L0
LINENUMBER 8 L0
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 0
GETFIELD test/Test8.s1 : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
我们可以看到,即便我们没有显式的使用StringBuilder,实际上编译器也会隐式的将我们的 + 运算符优化为StringBuilder的append()操作;另外,其中字符串常量的相加这里,也就是 "5555" + "66666666666666666666666666" + "777" 这里对应的操作是:
GETFIELD test/Test8.s4 : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "555566666666666666666666666666777"
直接被合并在了一次(具体这是什么操作我不是很明白)
这时候我就想起来,原来一直被教导的“字符串相加一定要用StringBuilder而不要用 + ”真的正确吗?这个值得深思。
2017-01-22更新:
GETFIELD test/Test8.s4 : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "555566666666666666666666666666777"
直接被合并在了一次(具体这是什么操作我不是很明白)
这里可能是编译器做了“公共的表达式消除”这个优化操作 (这里是错误的,请看后边的勘误)
2017-01-25更新:
在循环+=的情况下,编译器也会做优化工作的,但是IDE仍然会给出警告,不知道编译器的优化是否在所有情况下均会触发(有待继续学习)
public static void main(String[] args) {
String s = "";
for (int i = 0; i < 10000; i++) {
int int_ = new Random().nextInt();
s += int_;
}
System.out.println(s);
}
更新与勘误
2019-08-13 更新
当在循环中对字符串进行 += 操作时,会在每一次迭代中都创建一个StringBuilder,这种在循环内进行字符串 += 操作会被idea提示用 StringBuilder 替代的
package io.since1986.demo;
public class Test10 {
public static void main(String[] args) {
String s = "1111DDDDFFFGGGGG";
for (int i = 0; i < 99; i++) {
s += "3fghjl";
}
System.out.println(s);
}
}
// class version 52.0 (52)
// access flags 0x21
public class io/since1986/demo/Test10 {
// compiled from: Test10.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lio/since1986/demo/Test10; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
LDC "1111DDDDFFFGGGGG"
ASTORE 1
L1
LINENUMBER 7 L1
ICONST_0
ISTORE 2
L2
FRAME APPEND [java/lang/String I]
ILOAD 2
BIPUSH 99
IF_ICMPGE L3
L4
LINENUMBER 8 L4
NEW java/lang/StringBuilder // 注意这里是在循环内创建 StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "3fghjl"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 1
L5
LINENUMBER 7 L5
IINC 2 1
GOTO L2
L3
LINENUMBER 10 L3
FRAME CHOP 1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L6
LINENUMBER 11 L6
RETURN
L7
LOCALVARIABLE i I L2 L3 2
LOCALVARIABLE args [Ljava/lang/String; L0 L7 0
LOCALVARIABLE s Ljava/lang/String; L1 L7 1
MAXSTACK = 2
MAXLOCALS = 3
}
2019-08-15 更新
GETFIELD test/Test8.s4 : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "555566666666666666666666666666777"
“5555” + “66666666666666666666666666” + “777” 直接被合并在了一块,这个操作叫 常量折叠
Java中,关于String类型的变量和常量做“+”运算时发生了什么?
猜你喜欢
- 2024-10-27 从bitmap到布隆过滤器,再到高并发缓存设计策略
- 2024-10-27 强大 WebView2 + 不用写 JavaScript 的 htmx.js 「小轻快」开发桌面程序
- 2024-10-27 《JSP》第13节:JSP中的四大作用域介绍
- 2024-10-27 Java,FreeMarker,模板引擎,通过案例代码,学懂模板引擎
- 2024-10-27 面向对象的三大特性(c++面向对象的三大特性)
- 2024-10-27 教你分析9种 OOM 常见原因及解决方案
- 2024-10-27 可动态调节参数的线程池实现(动态调试工具有哪些)
- 2024-10-27 Java,基本类型和引用类型,强引用、软引用、弱引用、虚引用
- 2024-10-27 深入理解Java:类加载机制及反射(java常见类加载器)
- 2024-10-27 JVM系列-6.javap指令介绍(jvm调优)
- 1517℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 594℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 521℃MySQL service启动脚本浅析(r12笔记第59天)
- 489℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 489℃启用MySQL查询缓存(mysql8.0查询缓存)
- 477℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 456℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 454℃MySQL server PID file could not be found!失败
- 最近发表
-
- PS所有滤镜的说明(六)(ps滤镜详解)
- 5款小白也能用的在线图片编辑器!电商效率飙升就靠它!
- Java变量(java变量有什么作用)
- Java面试常见问题:Java注解(java中的面试题)
- Java编程入门第一课:HelloWorld(java编程从入门到实践)
- Java基础教程:Java继承概述(java里继承的概述)
- java基础之——访问修饰符(private/default/protected/public)
- 如何规划一个合理的JAVA项目工程结构
- 将机器指令翻译成 JavaScript -- 终极目标
- Web 服务器基准测试:Go vs. Node.js vs. Nim vs. Bun
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)