【Java源码笔记】String_StringBuffer_StringBuilder

本文参考 http://www.cnblogs.com/dolphin0520/p/3778589.html 基础上梳理而成

分析采用JDK 1.8源码


该总结核心知识点汇总:

1.String中使用 + 字符串连接符进行字符串连接时,连接操作最开始时如果都是字符串常量,编译后将尽可能多的直接将字符串常量连接起来,形成新的字符串常量参与后续连接(通过反编译工具jd-gui也可以方便的直接看出);

2.接下来的字符串连接是从左向右依次进行,对于不同的字符串,首先以最左边的字符串为参数创建StringBuilder对象,然后依次对右边进行append操作,最后将StringBuilder对象通过toString()方法转换成String对象(注意:中间的多个字符串常量不会自动拼接)。

通常情况下,执行效率上 StringBuilder > StringBuffer(多线程) > String(通过StringBuilder拼装)

应用场景选择:
       String:当字符串相加操作或者改动较少的情况;
       StringBuilder:当字符串相加操作较多的情况下,建议使用StringBuilder;
       StringBuffer:多线程场景

从反编译结果看:string+="hello"的操作事实上会自动被JVM优化成:
  StringBuilder str = new StringBuilder(string);
  str.append("hello");
  str.toString();


 通过源码可以得到的信息

 
 
 

1、类采用final修饰符,不可继承;

2、因char[]数组使用final修饰,则代表不可重新赋值。如下小程序证明不可以重新赋值(编译报错)。

      

    ===》 对字符串尝试内容修改的操作,必然都是新建一个String对象。 

小程序测试

 1 public static void main(String[] args) {
 2 
 3        String str1 = "hello world";
 4        String str2 = new String("hello world");
 5        String str3 = "hello world";
 6        String str4 = new String("hello world");
 7        
 8        System.out.println(str1 == str2);  // false
 9        System.out.println(str1 == str3);  // true
10        System.out.println(str2 == str4);  // false
11      
12        return;
13 }

测试结果说明(可以参考如下反编译结果):
1)str1和str3对应的字符常量涉及到常量池概念。通过这种方式来将String对象跟引用绑定的话,JVM执行引擎会先在运行时常量池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量;否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。

2)通过new关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的。  

 


String、StringBuffer以及StringBuilder的区别

StringBuilder和StringBuffer两个类都继承自AbstractStringBuilder。

同时两个类的append方法都直接继承基类AbstractStringBuilder的append方法实现。

 1     // StringBuilder的append方法
 2     @Override
 3     public StringBuilder append(String str) {
 4         super.append(str);
 5         return this;
 6     }
 7 
 8 
 9     // StringBuffer的append方法
10     @Override
11     public synchronized StringBuffer append(String str) {
12         toStringCache = null;
13         super.append(str);
14         return this;
15     }

 

小程序测试 String对象的 + 操作 

1 public static void main(String[] args) {
2         String string = "";
3         for (int i = 0; i < 10000; i++) {
4             string += "hello";    // 此处反编译发现通过StringBuilder进行拼接
5         }
6         return;
7 }

反编译结果:通过不断的new StringBuilder对象,并借助其append方法,最终借助StringBuilder.toString 方法返回最终String对象   // 这个循环执行完毕new出了10000个StringBuilder对象

从反编译结果看:string+="hello"的操作事实上会自动被JVM优化成:

  StringBuilder str = new StringBuilder(string);

  str.append("hello");

  str.toString();


StringBuilder 和 StringBuffer的对比

StringBuffer相对StringBuilder的差别是线程安全的,如下是通过BeyondCompare进行比较的结果部分截图


String  vs  StringBuilder  vs StringBuffer 的效率对比

 小程序测试

 1 private static int time = 50000;
 2 
 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     }
View Code

执行结果:

操作java.lang.String类型使用的时间为:2418毫秒
操作java.lang.StringBuffer类型使用的时间为:5毫秒
操作java.lang.StringBuilder类型使用的时间为:0毫秒
字符串直接相加操作:0毫秒 字符串间接相加操作:0毫秒

对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如"I"+"love"+"java"; 的字符串相加,在编译期间便被优化成了"Ilovejava"。

执行效率:StringBuilder > StringBuffer(多线程) > String(优化成StringBuilder)

应用场景选择:
       String:当字符串相加操作或者改动较少的情况;
       StringBuilder:当字符串相加操作较多的情况下,建议使用StringBuilder;
       StringBuffer:多线程场景

JDK源码中对StringBuilder和StringBuffer的类注释中关注线程安全的说明

StringBuilder

* A mutable sequence of characters. This class provides an API compatible with {@code StringBuffer}, but with no guarantee of synchronization. This class is designed for use as a drop-in replacement for

{@code StringBuffer} in places where the string buffer was being used by a single thread (as is generally the case). Where possible, it is recommended that this class be used in preference to {@code StringBuffer} as it will be faster under most implementations.

 

StringBuffer 

* A thread-safe, mutable sequence of characters.* A string buffer is like a {@link String}, but can be modified.At any* point in time it contains some particular sequence of characters, but* the length and content of the sequence can be changed through certain* method calls.* 

* String buffers are safe for use by multiple threads. The methods are synchronized where necessary so that all the operations on any particular instance behave as if they occur in some serial order
* that is consistent with the order of the method calls made by each of the individual threads involved.

 


常见面试题:

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

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

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

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

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

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

  

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

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

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

  

 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 }

测试结果:

false
false
false
true

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

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

  这个问题在很多书籍上都有说到比如《Java程序员面试宝典》,包括很多国内大公司笔试面试题都会遇到,大部分网上流传的以及一些面试书籍上都说是2个对象,这种说法是片面的。

  如果有不懂得地方可以参考这篇帖子:

  http://rednaxelafx.iteye.com/blog/774673/

  首先必须弄清楚创建对象的含义,创建是什么时候创建的?这段代码在运行期间会创建2个对象么?毫无疑问不可能,用javap -c反编译即可得到JVM执行的字节码内容:

  

  很显然,new只调用了一次,也就是说只创建了一个对象。

  而这道题目让人混淆的地方就是这里,这段代码在运行期间确实只创建了一个对象,即在堆上创建了"abc"对象。而为什么大家都在说是2个对象呢,这里面要澄清一个概念  该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个"abc"对象,而在代码执行过程中确实只创建了一个String对象。

  因此,这个问题如果换成 String str = new String("abc")涉及到几个String对象?合理的解释是2个。

  个人觉得在面试的时候如果遇到这个问题,可以向面试官询问清楚”是这段代码执行过程中创建了多少个对象还是涉及到多少个对象“再根据具体的来进行回答。

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)中的不会被优化。下面是两种方式的字节码:

  1)的字节码:

  

  2)的字节码:

  

  可以看出,在1)中只进行了一次append操作,而在2)中进行了两次append操作。

转载于:https://www.cnblogs.com/clarino/p/8808795.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值