区分java中String+String和String+char
我们来考虑一个关于java中string的问题: "abc" + '/'和 "abc" + "/"的区别. 通过这个例子, 我们可以顺便练习一下jdk工具中javap的用法, 原问题是这样的:
把斜杠/当作字符或字符串有什么区别呢?
一个是当作基本数据类型char,一个是对象string。具体有什么区别呢?
当作字符效率会更高吗?
string str = "abc" + '/';
和
string str = "abc" + "/";
1、编译器优化
首先大家应该知道, 上面那两句效果是一样的, 因为编译器会把上面那两句都优化成下面的样子:
string str = "abc/";
我们可以通过javap证明这一点. 关于javap, 可以参考《每个java开发者都应该知道的5个jdk工具》. 我们首先创建一个类: stringone, 在主方法中填入下面的代码:
stringone.java string str1 = "abc" + '/'; string str2 = "abc" + "/"; system.out.println(str1 == str2);
编译并运行, 输出结果为true. 接下来, 该我们的javap登场了, 在命令行中输入下面的命令:
javap -v -l stringone.class > stringone.s
然后查看生成的stringone.s文件. 就会发现其中有这么几行
stringone.s #2 = string #20 // abc/ ... #20 = utf8 abc/ ... 0: ldc #2 // string abc/ 2: astore_1 3: ldc #2 // string abc/ 5: astore_2
说明str1和str2都引用字符串"abc\".
2、使用javap分析差异
现在我们换一个问法, 下面的代码中stringaddstring和stringaddchar方法有什么区别?
stringtwo public static string stringaddstring(string str1, string str2){ return str1 + str2; } public static string stringaddchar(string str, char ch){ return str + ch; }
这次再使用javap进行反编译, 生成文件的部分内容如下所示
stringtwo.s public java.lang.string stringaddstring(java.lang.string, java.lang.string); descriptor: (ljava/lang/string;ljava/lang/string;)ljava/lang/string; flags: acc_public code: stack=2, locals=3, args_size=3 0: new #2 // class java/lang/stringbuilder 3: dup 4: invokespecial #3 // method java/lang/stringbuilder."< init>":()v 7: aload_1 8: invokevirtual #4 // method java/lang/stringbuilder. append:(ljava/lang/string;)ljava/lang/stringbuilder; 11: aload_2 12: invokevirtual #4 // method java/lang/stringbuilder. append:(ljava/lang/string;)ljava/lang/stringbuilder; 15: invokevirtual #5 // method java/lang/stringbuilder. tostring:()ljava/lang/string; 18: areturn public java.lang.string stringaddchar(java.lang.string, char); descriptor: (ljava/lang/string;c)ljava/lang/string; flags: acc_public code: stack=2, locals=3, args_size=3 0: new #2 // class java/lang/stringbuilder 3: dup 4: invokespecial #3 // method java/lang/stringbuilder."<init>":()v 7: aload_1 8: invokevirtual #4 // method java/lang/stringbuilder.append:(ljava/lang/string;)ljava/lang/stringbuilder; 11: iload_2 12: invokevirtual #6 // method java/lang/stringbuilder.append:(c)ljava/lang/stringbuilder; 15: invokevirtual #5 // method java/lang/stringbuilder.tostring:()ljava/lang/string; 18: areturn
现在, 我们已经可以很清楚的看出这两个方法执行的流程了:
stringaddstring
- 创建一个stringbuilder对象
- 使用append方法, 依次将两个参数添加到刚才创建的stringbuilder中.
- 调用tostring方法.
- return tostring方法的返回值.
stringaddchar的过程和stringaddstring一样, 只是在第二次调用append方法时stringaddstring的参数是string类型, 而stringaddchar的参数是char类型.
3、stringbuilder类的append(char)方法和append(string)方法
这里,我们直接查看源码就好了(我的是jdk1.8.0_60附带的源码)。 注意,虽然文档上显示stringbuilder继承自object, 但是从源码来看, 它是继承自抽象类abstractstringbuilder的。而且append方法是由abstractstringbuilder实现的。
abstractstringbuilder.java
public abstractstringbuilder append(char c) { ensurecapacityinternal(count + 1); // 确保数组能够容纳count+1个字符 value[count++] = c; return this; } public abstractstringbuilder append(string str) { if (str == null) return appendnull(); int len = str.length(); ensurecapacityinternal(count + len); str.getchars(0, len, value, count); // 拷贝字符串中的字符数组到本对象的字符数组中 count += len; return this; }
剩下的就不再贴出来了。string.getchars(int, int, char[], int)最终依赖于public static native void arraycopy(object, int, object, int, int)。也就是说有可能是c语言写的,在拷贝大型数组时效率应该会比java写的程序好一些。 那么,现在说说我的理解:
从直接内存来说, 由于string中包含char数组, 而数组应该是有长度字段的, 同时string类还有一个int hash属性, 再加上对象本身会占用额外的内存存储其他信息,所以字符串会多占用一些内存. 但是如果字符串非常长, 那么这些内存开销差不多就可以忽略了; 而如果像"/"这种情况, 字符串比较(非常)短,那么就很可能有许多个共享引用来分担这些内存开销, 那么多余的内存开销还是可以忽略的.
从调用堆栈上, 由于这里string只比char多了一两层函数调用,所以如果不考虑函数调用开销(包括时间和空间), 应该差不多;考虑函数调用开销, 应该 "abc" + '/'更好一些; 但是当需要连接若干个字符时(感觉这种情况应该更常见吧?), 由于使用char需要循环好多次才能完成连接, 调用的函数次数只会比使用string更多. 同时拷贝也不会比string直接拷贝一个数组更快. 所以这个时候就变成了"abc" + "/"吞吐量更大.
现在感觉这个问题像是在问: 读写文件时使用系统调用效率高, 还是使用标准函数库中的io库效率高. 个人感觉, 虽然标准io库最后还得调用系统调用, 而且这之间会产生一些临时变量, 以及更深层次的调用堆栈, 但是由于io库的缓冲等机制, 所以io库的吞吐量会更大, 而系统调用的实时性会好一些. 同样, 虽然string类会多几个字段, 有更深层次的函数堆栈, 但是由于缓存以及更直接的拷贝, 吞吐量应该会更好一些.
新的问题
从上面javap的反编译代码来看, 两个string相加, 会变成向stringbuilder中append字符串. 那么理论上, 下面哪段代码的效率好呢?
string str1 = "abc" + "123"; // 1 stringbuilder stringbuilder = new stringbuilder(); // 2 stringbuilder.append("abc"); stringbuilder.append("123"); string str2 = stringbuilder.tostring();
这个问题就留给大家思考吧!
以上就是本文的全部内容,帮助大家更好的区分java中string+string和string+char,希望对大家的学习有所帮助。