使用javap深入查看类文件
2024年03月07日
Java开发人员熟悉在一个循环中使用StringBuffer来代替串联String对象能获得最佳性能。然而,多数开发人员从来没有比较两种方法产生的字节代码的区别。在Java开发工具包(JDK)中有一个叫做javap的工具可以告诉你为什么这样做可以获得最佳性能。Javap将一个类和它的方法的一些转储信息输出到标准输出。该工具不把代码反编译为java源代码,但是它会把字节代码反汇编成为由Java虚拟机规范定义的字节代码指令。
在你需要查看编译器为你或者给你做了什么的时候,或者你想要看一处代码的改动对编译后的类文件有什么影响的时候,javap相当有用。
现在以我们前面提到的StringBuffer和String作为一个例子。下面是一个专门为例子设计的类,它有两个方法,都返回一个由0到n的数字组成的String,其中n由调用者提供。两个方法唯一的区别在于一个使用String构建结果,另外一个使用StringBuffer构建结果。
public class JavapTip{
public static void main(String[]args){
}
private static String withStrings(int count){
String s="";
for(int i=0;i<count;i++){
s+=i;
}
return s;
}
private static String withStringBuffer(int count){
StringBuffer sb=new StringBuffer();
for(int i=0;i<count;i++){
sb.append(i);
}
return sb.toString();
}
}
现在让我们看看对这个类使用–c选项运行javap的输出。-c选项告诉javap反汇编在类中遇到的字节代码。
运行方式如下:
>javap-c JavapTip
此命令的输出为:
Method java.lang.String withStrings(int)
0 ldc#2
2 astore_1
3 iconst_0
4 istore_2
5 goto 30
8 new#3
11 dup
12 invokespecial#4
15 aload_1
16 invokevirtual#5
19 iload_2
20 invokevirtual#6
23 invokevirtual#7
26 astore_1
27 iinc 2 1
30 iload_2
31 iload_0
32 if_icmplt 8
35 aload_1
36 areturn
Method java.lang.String withStringBuffer(int)
0 new#3
3 dup
4 invokespecial#4
7 astore_1
8 iconst_0
9 istore_2
10 goto 22
13 aload_1
14 iload_2
15 invokevirtual#6
18 pop
19 iinc 2 1
22 iload_2
23 iload_0
24 if_icmplt 13
27 aload_1
28 invokevirtual#7
31 areturn
如果你以前没有看过Java汇编器,那么这个输出对你来说就会比较难懂,但是你应该可以看到withString方法在每次循环的时候都新创建了一个StringBuffer实例。然后它将已有的String的当前值追加到StringBuffer上,然后追加循环的当前值。最后,它对buffer调用toString并将结果赋给现有的String引用。
withStringBuffer方法与这个方法正好相反,在每次循环的时候withStringBuffer只调用现有StringBuffer的append方法,没有创建新的对象,也没有新的String引用。
在这种情况下,我们已经知道了使用StringBuffer代替String是一种好的做法,但是如果我们不知道呢?那么javap可以帮助我们找到答案。在这里你可以看到更详细的关于String,StringBuffer的解释
你并不会经常需要一个Java反汇编器,但是当你需要的时候,知道你自己的机器已经有一个并且用法相当简单的反汇编器当然是一件好事。如果你感兴趣,看书看看javap的其它选项——或许你会发现在你的环境中需要的特性。