i++和++i是编码中比较常用的代码,并且也是初学者容易混淆的。我们知道i++是先赋值再+自己,而++i是先+自己再赋值。为什么是这样呢?他们之间效率对比又是怎么样呢?本文我们从字节码层面来分析。
前面的文章介绍过了jvm字节码的基本知识还有如何分析字节码,我们现在还是写个小demo
public class TestSelfAdd {
public static void f1() {
int i=0;
int j = i++;
System.out.println(j);
}
public static void f2() {
int i=0;
int j = ++i;
System.out.println(j);
}
}
编译成字节码之后:(如何编译成字节码看上一篇文章)
Classfile /G:/WorkingProjects/learn/project/test/out/production/test/TestSelfAdd.class
Last modified 2018-11-19; size 580 bytes
MD5 checksum fc262482f3054fd5f4c2bb18edf2355a
Compiled from "TestSelfAdd.java"
public class TestSelfAdd
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #23.#24 // java/io/PrintStream.println:(I)V
#4 = Class #25 // TestSelfAdd
#5 = Class #26 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 LTestSelfAdd;
#13 = Utf8 f1
#14 = Utf8 i
#15 = Utf8 I
#16 = Utf8 j
#17 = Utf8 f2
#18 = Utf8 SourceFile
#19 = Utf8 TestSelfAdd.java
#20 = NameAndType #6:#7 // "<init>":()V
#21 = Class #27 // java/lang/System
#22 = NameAndType #28:#29 // out:Ljava/io/PrintStream;
#23 = Class #30 // java/io/PrintStream
#24 = NameAndType #31:#32 // println:(I)V
#25 = Utf8 TestSelfAdd
#26 = Utf8 java/lang/Object
#27 = Utf8 java/lang/System
#28 = Utf8 out
#29 = Utf8 Ljava/io/PrintStream;
#30 = Utf8 java/io/PrintStream
#31 = Utf8 println
#32 = Utf8 (I)V
{
public TestSelfAdd();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LTestSelfAdd;
public static void f1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: iconst_0
1: istore_0
2: iload_0
3: iinc 0, 1
6: istore_1
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
14: return
LineNumberTable:
line 3: 0
line 4: 2
line 5: 7
line 6: 14
LocalVariableTable:
Start Length Slot Name Signature
2 13 0 i I
7 8 1 j I
public static void f2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: iconst_0
1: istore_0
2: iinc 0, 1
5: iload_0
6: istore_1
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
14: return
LineNumberTable:
line 8: 0
line 9: 2
line 10: 7
line 11: 14
LocalVariableTable:
Start Length Slot Name Signature
2 13 0 i I
7 8 1 j I
}
SourceFile: "TestSelfAdd.java"
我们抽取了他们关键的部分:
i++ ++i
0: iconst_0 0: iconst_0
1: istore_0 1: istore_0
2: iload_0 2: iinc 0, 1
3: iinc 0, 1 5: iload_0
6: istore_1 6: istore_1
10: iload_1 10: iload_1
我们仔细分析,最后iload_1赋值输出的是本地变量表的索引为1的元素,本地变量表那个元素的来源是栈,栈的元素来源又是本地变量表,iinc 0,1的意思是本地变量表索引为0的元素增加1。在栈的元素更新之前,也就是之心iload_0之前,++i的代码已经iinc过了,而i++的没有,所以最终iload_1之后,i++结果为0,而++i为1。
分析完i++和++i的原理,我们思考一个问题,他们谁的效率高?
同样我们写个demo:
public class TestSelfAddEfficiency {
public static void f1() {
for(int i=0;i<10;i++) {
System.out.println(i);
}
}
public static void f2() {
for(int i=0;i<10;++i) {
System.out.println(i);
}
}
}
编译之后看字节码,可以看到两个函数他们的字节码是一样的,所以效率上没有差别
Classfile /G:/WorkingProjects/learn/project/test/out/production/test/TestSelfAddEfficiency.class
Last modified 2018-11-19; size 646 bytes
MD5 checksum a78a263c0750a489f5999bc1a6905ad2
Compiled from "TestSelfAddEfficiency.java"
public class TestSelfAddEfficiency
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #23.#24 // java/io/PrintStream.println:(I)V
#4 = Class #25 // TestSelfAddEfficiency
#5 = Class #26 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 LTestSelfAddEfficiency;
#13 = Utf8 f1
#14 = Utf8 i
#15 = Utf8 I
#16 = Utf8 StackMapTable
#17 = Utf8 f2
#18 = Utf8 SourceFile
#19 = Utf8 TestSelfAddEfficiency.java
#20 = NameAndType #6:#7 // "<init>":()V
#21 = Class #27 // java/lang/System
#22 = NameAndType #28:#29 // out:Ljava/io/PrintStream;
#23 = Class #30 // java/io/PrintStream
#24 = NameAndType #31:#32 // println:(I)V
#25 = Utf8 TestSelfAddEfficiency
#26 = Utf8 java/lang/Object
#27 = Utf8 java/lang/System
#28 = Utf8 out
#29 = Utf8 Ljava/io/PrintStream;
#30 = Utf8 java/io/PrintStream
#31 = Utf8 println
#32 = Utf8 (I)V
{
public TestSelfAddEfficiency();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LTestSelfAddEfficiency;
public static void f1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=0
0: iconst_0
1: istore_0
2: iload_0
3: bipush 10
5: if_icmpge 21
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_0
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
15: iinc 0, 1
18: goto 2
21: return
LineNumberTable:
line 3: 0
line 4: 8
line 3: 15
line 6: 21
LocalVariableTable:
Start Length Slot Name Signature
2 19 0 i I
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 18
public static void f2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=0
0: iconst_0
1: istore_0
2: iload_0
3: bipush 10
5: if_icmpge 21
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_0
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
15: iinc 0, 1
18: goto 2
21: return
LineNumberTable:
line 8: 0
line 9: 8
line 8: 15
line 11: 21
LocalVariableTable:
Start Length Slot Name Signature
2 19 0 i I
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 18
}
SourceFile: "TestSelfAddEfficiency.java"