网站首页 > 技术文章 正文
今天再写一个读取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-11-15 超全干货 | 统计学中常用的数据分析方法汇总
- 2024-11-15 BenchmarkSQL如何添加新的数据库?
- 2024-11-15 经典SQL语句大全(sql常用语句大全简书)
- 2024-11-15 卓越分享丨SQL语句大全,所有的SQL都在这里
- 2024-11-15 2020年SQL的全网最全面语句(建议收藏)必看篇
- 2024-11-15 全网最全面的 SQL语句(建议收藏)(sqlui)
- 2024-11-15 SQL语句大全,所有的SQL都在这里 需要的收藏
- 2024-11-15 SQL语句大全,所有的SQL都在这里(sql语句大全,所有的sql都在这里吗)
- 2024-11-15 mysql sql语句大全(mysql的语句)
- 2024-11-15 收藏!所有SQL语句都在这里了(收藏!所有sql语句都在这里了吗)
你 发表评论:
欢迎- 612℃几个Oracle空值处理函数 oracle处理null值的函数
- 603℃Oracle分析函数之Lag和Lead()使用
- 592℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 589℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 583℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 576℃【数据统计分析】详解Oracle分组函数之CUBE
- 566℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 558℃Oracle有哪些常见的函数? oracle中常用的函数
- 最近发表
-
- PageHelper - 最方便的 MyBatis 分页插件
- 面试二:pagehelper是怎么实现分页的,
- MyBatis如何实现分页查询?(mybatis-plus分页查询)
- SpringBoot 各种分页查询方式详解(全网最全)
- 如何在Linux上运行exe文件,怎么用linux运行windows软件
- 快速了解hive(快速了解美国50个州)
- Python 中的 pyodbc 库(pydbclib)
- Linux搭建Weblogic集群(linux weblogic部署项目步骤)
- 「DM专栏」DMDSC共享集群之部署(一)——共享存储配置
- 故障分析 | MySQL 派生表优化(mysql pipe)
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- 前端获取当前时间 (50)
- Oracle RAC (76)
- oracle恢复 (77)
- oracle 删除表 (52)
- oracle 用户名 (80)
- oracle 工具 (55)
- oracle 内存 (55)
- oracle 导出表 (62)
- oracle约束 (54)
- oracle 中文 (51)
- oracle链接 (54)
- oracle的函数 (58)
- 前端调试 (52)
本文暂时没有评论,来添加一个吧(●'◡'●)