看下面这段代码:

  1. public class Main {
  2.  
  3. public static void main(String[] args) {
  4. String string = "";
  5. for(int i=0;i<10000;i++){
  6. string += "hello";
  7. }
  8. }
  9. }

这句 string += "hello";的过程相当于将原有的string变量指向的对象内容取出与"hello"作字符串相加操作再存进另一个新的String对象当中,再让string变量指向新生成的对象。

从这段反编译出的字节码文件可以很清楚地看出:从第8行开始到第35行是整个循环的执行过程,并且每次循环会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象。也就是说这个循环执行完毕new出了10000个对象,试想一下,如果这些对象没有被回收,会造成多大的内存资源浪费。从上面还可以看出:string+="hello"的操作事实上会自动被JVM优化成:

  1. StringBuilder str = new StringBuilder(string);
  2. str.append("hello");
  3. str.toString();

再看下面这段代码:

  1. public class Main {
  2.  
  3. public static void main(String[] args) {
  4. StringBuilder stringBuilder = new StringBuilder();
  5. for(int i=0;i<10000;i++){
  6. stringBuilder.append("hello");
  7. }
  8. }
  9. }

反编译字节码文件得到:

从这里可以明显看出,这段代码的for循环式从13行开始到27行结束,并且new操作只进行了一次,也就是说只生成了一个对象,append操作是在原有对象的基础上进行的。因此在循环了10000次之后,这段代码所占的资源要比上面小得多。

那么有人会问既然有了StringBuilder类,为什么还需要StringBuffer类?查看源代码便一目了然,事实上,StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的。

下面摘了2段代码分别来自StringBuffer和StringBuilder,insert方法的具体实现:

StringBuilder的insert方法

  1. public StringBuilder insert(int index, char str[], int offset,
  2. int len)
  3. {
  4. super.insert(index, str, offset, len);
  5.   return this;
  6. }

StringBuffer的insert方法:

  1. public synchronized StringBuffer insert(int index, char str[], int offset,
  2. int len)
  3. {
  4. super.insert(index, str, offset, len);
  5. return this;
  6. }

性能测试

  1. public class Main {
  2. private static int time = 50000;
  3. public static void main(String[] args) {
  4. testString();
  5. testStringBuffer();
  6. testStringBuilder();
  7. test1String();
  8. test2String();
  9. }
  10.  
  11. public static void testString () {
  12. String s="";
  13. long begin = System.currentTimeMillis();
  14. for(int i=0; i<time; i++){
  15. s += "java";
  16. }
  17. long over = System.currentTimeMillis();
  18. System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
  19. }
  20.  
  21. public static void testStringBuffer () {
  22. StringBuffer sb = new StringBuffer();
  23. long begin = System.currentTimeMillis();
  24. for(int i=0; i<time; i++){
  25. sb.append("java");
  26. }
  27. long over = System.currentTimeMillis();
  28. System.out.println("操作"+sb.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
  29. }
  30.  
  31. public static void testStringBuilder () {
  32. StringBuilder sb = new StringBuilder();
  33. long begin = System.currentTimeMillis();
  34. for(int i=0; i<time; i++){
  35. sb.append("java");
  36. }
  37. long over = System.currentTimeMillis();
  38. System.out.println("操作"+sb.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
  39. }
  40.  
  41. public static void test1String () {
  42. long begin = System.currentTimeMillis();
  43. for(int i=0; i<time; i++){
  44. String s = "I"+"love"+"java";
  45. }
  46. long over = System.currentTimeMillis();
  47. System.out.println("字符串直接相加操作:"+(over-begin)+"毫秒");
  48. }
  49.  
  50. public static void test2String () {
  51. String s1 ="I";
  52. String s2 = "love";
  53. String s3 = "java";
  54. long begin = System.currentTimeMillis();
  55. for(int i=0; i<time; i++){
  56. String s = s1+s2+s3;
  57. }
  58. long over = System.currentTimeMillis();
  59. System.out.println("字符串间接相加操作:"+(over-begin)+"毫秒");
  60. }
  61.  
  62. }

结果

上面提到string+="hello"的操作事实上会自动被JVM优化,看下面这段代码:

  1. public class Main {
  2. private static int time = 50000;
  3. public static void main(String[] args) {
  4. testString();
  5. testOptimalString();
  6. }
  7.  
  8. public static void testString () {
  9. String s="";
  10. long begin = System.currentTimeMillis();
  11. for(int i=0; i<time; i++){
  12. s += "java";
  13. }
  14. long over = System.currentTimeMillis();
  15. System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
  16. }
  17.  
  18. public static void testOptimalString () {
  19. String s="";
  20. long begin = System.currentTimeMillis();
  21. for(int i=0; i<time; i++){
  22. StringBuilder sb = new StringBuilder(s);
  23. sb.append("java");
  24. s=sb.toString();
  25. }
  26. long over = System.currentTimeMillis();
  27. System.out.println("模拟JVM优化操作的时间为:"+(over-begin)+"毫秒");
  28. }
  29.  
  30. }

得到验证。

  下面对上面的执行结果进行一般性的解释:

1)对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如"I"+"love"+"java"; 的字符串相加,在编译期间便被优化成了"Ilovejava"。这个可以用javap -c命令反编译生成的class文件进行验证。

  对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。

2)String、StringBuilder、StringBuffer三者的执行效率:  

  1. StringBuilder > StringBuffer > String

当然这个是相对的,不一定在所有情况下都是这样。

比如String str = "hello"+ "world"的效率就比 StringBuilder st  = new StringBuilder().append("hello").append("world")要高。

  因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:

  当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;

  当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。

关于String、StringBuffer的面试题

1. 下面这段代码的输出结果是什么?

  1. String a = "hello2";   
  2. String b = "hello" + 2;   
  3. System.out.println((a == b));

  输出结果为:true。原因很简单,"hello"+2在编译期间就已经被优化成"hello2",因此在运行期间,变量a和变量b指向的是同一个对象。

2.下面这段代码的输出结果是什么?  

  1. String a = "hello2";  
  2. String b = "hello";
  3. String c = b + 2;
  4. System.out.println((a == c));

  输出结果为:false。由于有符号引用的存在,所以  String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象。javap -c得到的内容:

3.下面这段代码的输出结果是什么?

  1. String a = "hello2";  
  2. final String b = "hello";
  3. String c = b + 2;
  4. System.out.println((a == c));

输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = "hello" + 2;

4.下面这段代码输出结果为:

  1. public class Main {
  2. public static void main(String[] args) {
  3. String a = "hello2";
  4. final String b = getHello();
  5. String c = b + 2;
  6. System.out.println((a == c));
  7. }
  8.  
  9. public static String getHello() {
  10. return "hello";
  11. }
  12. }

输出结果为false。这里面虽然将b用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此a和c指向的不是同一个对象。

5.下面这段代码的输出结果是什么?

  1. public class Main {
  2. public static void main(String[] args) {
  3. String a = "hello";
  4. String b = new String("hello");
  5. String c = new String("hello");
  6. String d = b.intern();
  7.  
  8. System.out.println(a==b);
  9. System.out.println(b==c);
  10. System.out.println(b==d);
  11. System.out.println(a==d);
  12. }
  13. }

这里面涉及到的是String.intern方法的使用。在String类中,intern方法是一个本地方法,在JAVA SE6之前,intern方法会在运行时常量池中查找是否存在内容相同的字符串,如果存在则返回指向该字符串的引用,如果不存在,则会将该字符串入池,并返回一个指向该字符串的引用。因此,a和d指向的是同一个对象。

6.String str = new String("abc")创建了多少个对象

这个问题自身就没有合理的答案,大部分网上流传的以及一些面试书籍上都说是2个对象(一个是“xyz”,一个是指向“xyz”的引用对象s),这种说法是片面的。

这问题的毛病是什么呢?它并没有定义“创建了”的意义。 
什么叫“创建了”?什么时候创建了什么? 
而且这段Java代码片段实际运行的时候真的会“创建两个String实例”么?

如果这道是面试题,那么可以当面让面试官澄清“创建了”的定义,然后再对应的回答。这种时候面试官多半会让被面试者自己解释,那就好办了,好好晒给面试官看。

如果是笔试题就没有提问要求澄清的机会了。不过会出这种题目的地方水平多半也不怎么样。说不定出题的人就是从各种宝典上把题抄来的,那就按照宝典把那不大对劲的答案写上去就能混过去了

先换成另一个问题来问:

String s = new String("xyz");  在运行时涉及几个String实例?

一种合理的解答是:

  1. 两个,一个是字符串字面量"xyz"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(String)创建并初始化的、内容与"xyz"相同的实例

也就是规定了Java语言一般是编译为Java虚拟机规范所定义的Class文件,但并没有规定“一定”(must),留有不使用JVM来实现Java语言的余地。 
考虑上Java虚拟机规范,确实在这段代码里涉及的常量种类为CONSTANT_String_info的字符串常量也只有"xyz"一个。CONSTANT_String_info是用来表示Java语言中String类型的常量表达式的值(包括字符串字面量)用的常量种类,只在这个层面上考虑的话,这个解答也没问题。 
所以这种解答可以认为是合理的。

值得注意的是问题中“在运行时”既包括类加载阶段,也包括这个代码片段自身执行的时候。

String s = new String("xyz");  涉及用户声明的几个String类型的变量?

  1. 一个,就是String s

String s = null;  涉及用户声明的几个String类型的变量?

  1. Java里变量就是变量,引用类型的变量只是对某个对象实例或者null的引用,不是实例本身。声明变量的个数跟创建实例的个数没有必然关系,像是说:
  1. String s1 = "a";
  2. String s2 = s1.concat("");
  3. String s3 = null;
  4. new String(s1);

这段代码会涉及3个String类型的变量, 
1、s1,指向下面String实例的1 
2、s2,指向与s1相同 
3、s3,值为null,不指向任何实例

以及3个String实例, 
1、"a"字面量对应的驻留的字符串常量的String实例 
2、""字面量对应的驻留的字符串常量的String实例 
String.concat()是个有趣的方法,当发现传入的参数是空字符串时会返回this,所以这里不会额外创建新的String实例) 
3、通过new String(String)创建的新String实例;没有任何变量指向它。

....具体讨论查阅http://rednaxelafx.iteye.com/blog/774673/

7.下面这段代码1)和2)的区别是什么?

  1. public class Main {
  2. public static void main(String[] args) {
  3. String str1 = "I";
  4. //str1 += "love"+"java"; 1)
  5. str1 = str1+"love"+"java"; //2)
  6.  
  7. }
  8. }

1)的效率比2)的效率要高,1)中的"love"+"java"在编译期间会被优化成"lovejava",而2)中的不会被优化。

Java基础-String、StringBuffer、StringBuilder的更多相关文章

  1. java中 String StringBuffer StringBuilder的区别

    * String类是不可变类,只要对String进行修改,都会导致新的对象生成. * StringBuffer和StringBuilder都是可变类,任何对字符串的改变都不会产生新的对象. 在实际使用 ...

  2. Java基础 -- String,StringBuilder,StringBuffer三者的区别

    结论 1-String,StringBuilder,StringBuffer 之间的区别主要是在两个方面,即运行速度和线程安全这两方面: 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:St ...

  3. java中String,StringBuffer,StringBuilder之间的区别

    文章转载自:http://www.cnblogs.com/frankliiu-java/archive/2010/07/05/1771537.html String是固定长度的字符串,如果要发生变化必 ...

  4. java 比较String StringBuffer StringBuilder

    String 字符串常量StringBuffer 字符串变量(线程安全)StringBuilder 字符串变量(非线程安全) 简要的说, String 类型和 StringBuffer 类型的主要性能 ...

  5. Java学习|String,StringBuffer,StringBuilder?

    1 String   (1) String的创建机理 由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池.其运行机制是:创建一个字 ...

  6. Java基础 String/StringBuff/StringBuilder 常用操作方法复习/内存分析/三者的效率比较

    附:jdk1.8使用IDEA安装.创建.使用JUnit单元测试 笔记总结: /**String 复习 * 1.像C++这样的char arr[]="..." 的方式无法声明Java ...

  7. Java中String/StringBuffer/StringBuilder区别(转)

    1.三者在执行速度方面的比较:StringBuilder >  StringBuffer  >  String 2.String <(StringBuffer,StringBuild ...

  8. Java基础-String和StringBuilder类型(11)

    String类概述 字符串是由多个字符组成的一串数据字符串可以看成是字符数组 构造方法 public String(String original)public String(char[] value ...

  9. Java的String&StringBuffer&StringBuilder

    一:String类 1.String对象的初始化 由于String对象特别用,所以在对String对象进行初始化时,Java提供了一种简化的特殊语法,格式如下: String s = "ab ...

  10. Java之String,StringBuffer,StringBuilder类

    在 java 语言中, 用来处理字符串的的类常用的有 3 个: String.StringBuffer.StringBuilder. 它们的异同点: 1) 都是 final 类, 都不允许被继承; 2 ...

随机推荐

  1. 孙鑫视频学习:VS2010中找不到【Tab order】菜单项

    在学习孙鑫视频中,修改Tab顺序时,找不到VC6.0中提到的[Layout]->[Tab order]菜单项,但VC2010中可以用Ctrl+D调出来Tab顺序,或者[格式]->[Tab键 ...

  2. sqlzoo.net刷题

    只发后面提升题目的题解,前面的太简单,写下来也没有意义 12.查找尤金•奧尼爾EUGENE O'NEILL得獎的所有細節 Find all details of the prize won by EU ...

  3. mysqli预处理和事务处理

    1 应用环境 mysqli预处理功能(大量数据处理时使用) 2 步骤 a)mysqli连接数据库 $mysqli = new mysqli('localhost','root','root','chu ...

  4. 13Spring_AOP编程(AspectJ)_后置通知

    后置通知和前置通知差不多.最大的特点是因为后置通知是运行在目标方法之后的,所以他可以拿到目标方法的运行的结果. 给出案例: 案例结构图:

  5. 调用Oracle存储过程并获取out参数值

    原文: http://tech.it168.com/oldarticle/2006-04-02/200604021512359.shtml http://www.cnblogs.com/m-cnblo ...

  6. WPF RadioButton 转换

    模型 public class people { public string name{get;set;} public bool? sex{get;set;} } 转换器 namespace Hel ...

  7. TabControl的SelectionChanged事件

    DataGrid作为TabControl控件的TabItem的content元素. 当操作DataGrid的不同cell时,会引发了TabControl的SelectionChanged事件的问题. ...

  8. 【C#】【Thread】Monitor和Lock

    所谓锁,就是之锁定的区域只能单个线程进入进行操作,其他线程在锁的外围等待.Monitor锁通过Monitor.Enter(obj)和Monitor.Exit(obj)来锁定和解锁.Lock锁则直接Lo ...

  9. salt进程查看插件&salt批量创建用户

    接受key 剔除主机   启动 salt-minion-d     软件包的安装   salt '*' state.sls init.env-init test=true   salt批量创建用户: ...

  10. 如何自定义kindeditor编辑器的工具栏items即去除不必要的工具栏或者保留部分工具栏

    kindeditor编辑器的工具栏主要是指编辑器输入框上方的那些可以操作的菜单,默认情况下编辑器是给予了所有的工具栏.针对不同的用户,不同的项目,不同的环境,可能就需要保留部分工具栏.那么我们应该如何 ...