好吧,写这篇博客又是因为一个有趣的帖子。原问题是“String str = “abc” + ‘/’;和”abc” + “/”;的区别”,感觉这个问题相当有意思,我们天天使用String类,但是有谁考虑到这么细致的问题了。这里给出的是我的个人见解,限于本人水平,有什么错误还请大家见谅

原问题

把斜杠/当作字符或字符串有什么区别呢?

一个是当作基本数据类型char,一个是对象String。具体有什么区别呢?

当作字符效率会更高吗?

String str = "abc" + '/';

String str = "abc" + "/";

看到这个问题以及下面几条回复,我首先想到的是,查看这些语句的调用链:

'/'到底是通过valueOf()方法变成字符串的,还是通过构造方法变成字符串的。"/"呢,又是通过哪个途径呢?于是有了第一次尝试。

修改String类的代码,记录调用链。失败

由于以前的坏习惯,不喜欢调试,习惯直接在代码中加入打印语句,打印一些变量或者标记程序执行流程。于是,要查看调用链?好,修改String类代码,在所有参数为char或者char[]的valueOf()方法和构造方法里面加一句打印语句。结果发现直接把JDK弄挂了,连编译Hello World都不行了(原因至今不明,可能是因为System.out为null吧)。然后,改成写文件,写一个方法,调用堆栈层数大于6(防止死递归)或者线程名不是main(排除无用信息)的直接返回,满足条件的把信息写到一个文件中。仍旧失败。后来发现,获取调用堆栈信息也得用String类,于是,死递归了。。。然后JDK又挂了。貌似编译程序时,也得用到一些Java写的程序,而作为最常用的String类的构造方法出现了死递归,结果就栈溢出了,,,

好吧,第一次花了我一天时间,结果还是失败的!?然后突然就想到javap了(注:关于javap可以看看这个博客:《每个Java开发者都应该知道的5个JDK工具》),于是开始第二次尝试。。。

第一次使用javap,失败

有了思路,就开始动手。于是写了这个测试程序:

public class Test{

public static void main(String[] args){

String str1 = "abc" + '/';

String str2 = "abc" + "/";

System.out.println(str1 == str2);

}

}

javac Test.java

javap -l -c -v Test.class > Test.s

然后看Test.s(注:javap生成文件中的指令的含义可以参考这几篇博客:《Java栈和局部变量操作(一)》,《Java栈和局部变量操作(二)》,《java指令集》):

...

Constant pool:

#1 = Methodref #6.#19 // java/lang/Object."":()V

#2 = String #20 // abc/

#3 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;

...

0: ldc #2 // String abc/

2: astore_1

3: ldc #2 // String abc/

5: astore_2

注:下面使用的“行号”是指指令前的数字,如0-2是指第7-8行

0-2:从常量池中取出"abc/"的引用赋给第一个变量,也就是str1

3-5:从常量池冲取出"abc/"的引用赋给第二个变量,也就是str2

好吧,被优化掉了,,,又找不到关闭优化的选项,只找到一个开启优化选项-O,还是默认关闭的!既然常量会被优化掉,那么变量呢?于是开始了第三次尝试

再次使用javap

这次修改代码:

public class Test{

public static void main(String[] args) {

String str = "abc";

char ch1 = '/';

String ch2 = "/";

System.out.println((str+ch1) == (str+ch2));

}

}

既然常量会被优化掉,那么就使用变量。这次使用javap反编译产生的文件大多了:

...

Constant pool:

#1 = Methodref #12.#25 // java/lang/Object."":()V

#2 = String #26 // abc

#3 = String #27 // /

...

Code:

stack=4, locals=4, args_size=1

0: ldc #2 // String abc

2: astore_1

3: bipush 47

5: istore_2

6: ldc #3 // String /

8: astore_3

9: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;

12: new #5 // class java/lang/StringBuilder

15: dup

16: invokespecial #6 // Method java/lang/StringBuilder."":()V

19: aload_1

20: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

23: iload_2

24: invokevirtual #8 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;

27: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

30: new #5 // class java/lang/StringBuilder

33: dup

34: invokespecial #6 // Method java/lang/StringBuilder."":()V

37: aload_1

38: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

41: aload_3

42: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

45: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

48: if_acmpne 55

51: iconst_1

52: goto 56

55: iconst_0

56: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V

59: return

从这里已经可以看出这段代码的执行过程了:

0-2:从常量池中取出”abc”的引用压栈,再弹栈赋给第一个变量(不知道为什么非得压栈再弹栈,为什么不直接赋值?)

3-5:将47从byte型变为int压栈,再弹栈赋给第二个变量。

6-8:从常量池中取出”/”的引用压栈,再弹栈赋给第三个变量

…(以下会省略压栈、弹栈等操作,因为两段完全一样。有兴趣的读者可以自己分析一下)

12-27:接着创建一个StringBuilder类实例,使用第一个变量(“abc”)作为参数调用append方法,使用第二个变量(‘/’)为参数调用append方法,调用toString方法获得字符串

30-45:然后再创建一个StringBuilder类实例,使用第一个变量(“abc”)作为参数调用append方法,使用第三个变量(“/”)为参数调用append方法,调用toString方法获得字符串

比较,输出…

那么,比较"abc"+'/'和"abc"+"/"的区别的问题就变为比较StringBuilder类的append(char ch)方法和append(String str)方法了。

StringBuilder类的append(char)方法和append(String)方法

这里,我们直接查看源码就好了(我的是jdk1.8.0_45附带的源码)。

注意,查看的是抽象类AbstractStringBuilder的源码,虽然文档上没有显示,但是StringBuilder确实继承自抽象类AbstractStringBuilder。而且append方法是由AbstractStringBuilder实现的。

AbstractStringBuilder.append(char):

public AbstractStringBuilder append(char c) {

ensureCapacityInternal(count + 1); // 确保数组能够容纳count+1个字符

value[count++] = c;

return this;

}

AbstractStringBuilder.append(String):

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类会多几个字段,有更深层次的函数堆栈,但是由于一些缓存以及更直接的拷贝,效率应该会更好一些。

新的问题

好吧,其实这里又产生一个新的问题:老师告诉我们,当有大量字符串连接操作时,StringBuffer比String更好,更省内存。StringBuilder的文档告诉我们:

如果可能,建议优先采用该类(StringBuilder),因为在大多数实现中,它比 StringBuffer 要快。

而我们看到字符串连接的执行过程实际上创建了一个临时的StringBuilder对象,那么StringBuffer到底是不是真的比String更好,更省内存呢?大家可以参考我写的另一篇博客:《java中String和StringBuffer哪个效率高》.

结语:

这两天的经历让我真正的体会到了一个道理:坚持很重要,但有时换个方向继续坚持才能更快的到达目标!

写于2015/04/24

用java比较abc大小的程序_Java中 abc + '/'和abc + /的区别相关推荐

  1. java的对象是什么意思_Java中对象和对象引用的区别,引用、指向是什么意思

    Java的变量分为两大类:基本数据类型和引用数据类型. 其中基本类型变量有四类8种:byte short int long float double char boolean,除了8种基本数据类型变量 ...

  2. java面试题25 在程序代码中写的注释太多,会使编译后的程序尺寸变大。

    java面试题25 在程序代码中写的注释太多,会使编译后的程序尺寸变大. A:正确 B:错误 蒙蔽树上蒙蔽果,蒙蔽树下你和我 拿到这道题,我觉得说的贼有道理,注释太多,尺寸变大.无疑与就和驾考 一样, ...

  3. java中堆与栈的区别_java中堆和栈的区别分析

    堆和栈是Java数据结构里非常重要的概念,本文较为详细的分析了二者之间的区别.供大家参考.具体如下: Java的堆是一个运行时数据区,类的(对象从中分配空间.这些对象通过new.newarray.an ...

  4. java堆和客栈_java中堆和栈的区别分析

    堆和栈是java数据结构里非常重要的概念,本文较为详细的分析了二者之间的区别.供大家参考.具体如下: Java的堆是一个运行时数据区,类的(对象从中分配空间.这些对象通过new.newarray.an ...

  5. java字面量和符号引用_java中字面量,常量和变量之间的区别(附:Integer缓存机制)...

    一.引子 在各种教科书和博客中这三者经常被引用,今天复习到内存区域,想起常量池中就是存着字面量和符号引用,其实这三者并不是只在java中才有,各个语言中都有类似的定义,所以做一下总结,以示区分. 二. ...

  6. java栈和堆的区别_Java中堆和栈的区别

    堆和栈都是Java用来在RAM中存放数据的地方. 堆 (1)Java的堆是一个运行时数据区,类的对象从堆中分配空间.这些对象通过new等指令建立,通过垃圾回收器来销毁. (2)堆的优势是可以动态地分配 ...

  7. java什么是栈和堆_JAVA中的栈和堆

    JAVA在程序运行时,在内存中划分5片空间进行数据的存储.分别是:1:寄存器.2:本地方法区.3:方法区.4:栈.5:堆. 基本,栈stack和堆heap这两个概念很重要,不了解清楚,后面就不用学了. ...

  8. java常量能改变值吗_java中的常量和变量

    变量 含义:在程序过程中变量的值会发生变化,直白来说就是用来存储可变化的数据 变量的好处:使内存空间的得到重复利用 举个例子: 1 System.out.println(10);2 System.ou ...

  9. java出现errors是什么错误_java中错误(error)和异常(exception)有什么主要区别?

    jdk8中文发翻译Throwable类的描述:Throwable类是Java语言中所有错误和异常的Throwable类. 只有作为此类(或其一个子类)的实例的对象由Java虚拟机抛出,或者可以由Jav ...

最新文章

  1. python01-变量,运算符与数据类型+位运算
  2. linux中ctrl+z 、ctrl+c、 ctrl+d区别
  3. 图数据库的知识表示与推理
  4. 老年机按键串号_为什么老人机依然很多人在用?
  5. OS: 读者写者问题(写者优先+LINUX+多线程+互斥量+代码)(转)
  6. 小程序 图片上传php后台,微信小程序图片选择、上传到服务器、预览(PHP)实现实例...
  7. OSPF特殊区域之stub和totally stub配置(二)
  8. JDBC连接错误:通过端口 1433 连接到主机 localhost 的 TCP/IP 连接失败。。。
  9. mysql备份脚本 shell_MySQL数据库备份Shell脚本
  10. Android:访问网络资源,在手机本地显示网络资源源代码
  11. 边看边练之Django(二)---- Django的URL设置
  12. 如何从12306网站下载自己喜欢的手机铃声
  13. 基于SSM的企业人事人员管理系统
  14. 最小生成树-Prim + Kruskal算法
  15. java调用加密机实例_Enigma Java模拟实现恩尼格玛密码机加密解密过程 Develop 238万源代码下载- www.pudn.com...
  16. java实现lbs_如何在 Java 中利用 redis 实现 LBS 服务
  17. Python一行代码实现正三角形
  18. MacOS编译LibreCAD
  19. Win 98系统启动过程全揭密
  20. 数据库发展史2--数据仓库

热门文章

  1. 工程院院士李德毅:数据挖掘就是云环境下的搜索服务
  2. macOS Catalina 10.15.x 无法添加屏幕录制权限解决办法
  3. HTTP 中的 Cookie
  4. 免费CMS建站系统哪个比较好?如何选择?
  5. IPTV机顶盒测试方法
  6. 论文解读--K-Radar:4D Radar Object Detection for Autonomous Driving in Various Weather Conditions
  7. PDF转Word转换器哪个好用
  8. python乒乓球小游戏_100行-python乒乓球小游戏
  9. 寻找凸四边形的四个顶点
  10. jupyter设置中/英文语言