网站首页 > 技术文章 正文
今天再写一个读取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调优)
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)