前言

有这么一段代码:

 public class TestMain
{
public static void main(String[] args)
{
String str0 = "123";
String str1 = "123";
System.out.println(str0 == str1);
}
}

运行结果是什么?答案当然是true。对,答案的确是true,但是这是为什么呢?很多人第一反应肯定是两个"123"的String当然相等啊,这还要想。但是"=="在Java比较的不是两个对象的值,而是比较两个对象的引用是否相等,和两个String都是"123"又有什么关系呢?或者我们把程序修改一下

 public class TestMain
{
public static void main(String[] args)
{
String str2 = new String("234");
String str3 = new String("234");
System.out.println(str2 == str3);
}
}

这时候运行结果就是false了,因为尽管两个String对象都是"234",但是str2和str3是两个不同的引用,所以返回的false。OK,围绕第一段代码返回true,第二段代码返回false,开始文章的内容。

为什么String=String?

在JVM中有一块区域叫做常量池,关于常量池,我在写虚拟机的时候有专门提到http://www.cnblogs.com/xrq730/p/4827590.html。常量池中的数据是那些在编译期间被确定,并被保存在已编译的.class文件中的一些数据。除了包含所有的8种基本数据类型(char、byte、short、int、long、float、double、boolean)外,还有String及其数组的常量值,另外还有一些以文本形式出现的符号引用。

Java栈的特点是存取速度快(比堆块),但是空间小,数据生命周期固定,只能生存到方法结束。我们定义的boolean b = true、char c = 'c'、String str = “123”,这些语句,我们拆分为几部分来看:

1、true、c、123,这些等号右边的指的是编译期间可以被确定的内容,都被维护在常量池中

2、b、c、str这些等号左边第一个出现的指的是一个引用,引用的内容是等号右边数据在常量池中的地址

3、boolean、char、String这些是引用的类型

栈有一个特点,就是数据共享。回到我们第一个例子,第五行String str0 = "123",编译的时候,在常量池中创建了一个常量"123",然后走第六行String str1 = "123",先去常量池中找有没有这个"123",发现有,str1也指向常量池中的"123",所以第七行的str0 == str1返回的是true,因为str0和str1指向的都是常量池中的"123"这个字符串的地址。当然如果String str1 = "234",就又不一样了,因为常量池中没有"234",所以会在常量池中创建一个"234",然后str1代表的是这个"234"的地址。分析了String,其实其他基本数据类型也都是一样的:先看常量池中有没有要创建的数据,有就返回数据的地址,没有就创建一个

第二个例子呢?Java虚拟机的解释器每遇到一个new关键字,都会在堆内存中开辟一块内存来存放一个String对象,所以str2、str3指向的堆内存中虽然存储的是相等的"234",但是由于是两块不同的堆内存,因此str2 == str3返回的仍然是false,网上找到一张图表示一下这个概念:

为什么要使用StringBuilder和StringBuffer拼接字符串?

大家在开发中一定有一个原则是"利用StringBuilder和StringBuffer拼接字符串",但是为什么呢?用一段代码来分析一下:

 public class StringTest {

     @Test
public void testStringPlus() {
String str = "111";
str += "222";
str += "333";
System.out.println(str);
} }

这段代码,我们找到编译后的StringTest.class文件,使用"javap -verbose StringTest"或者"javap -c StringTest"都可以,反编译一下class获取到对应的字节码:

  public void testStringPlus();
Code:
0: ldc #17 // String 111
2: astore_1
3: new #19 // class java/lang/StringBuilder
6: dup
7: aload_1
8: invokestatic #21 // Method java/lang/String.valueOf:(Ljava/lang/Object;)L
java/lang/String;
11: invokespecial #27 // Method java/lang/StringBuilder."<init>":(Ljava/lang/S
tring;)V
14: ldc #30 // String 222
16: invokevirtual #32 // Method java/lang/StringBuilder.append:(Ljava/lang/Str
ing;)Ljava/lang/StringBuilder;
19: invokevirtual #36 // Method java/lang/StringBuilder.toString:()Ljava/lang/
String;
22: astore_1
23: new #19 // class java/lang/StringBuilder
26: dup
27: aload_1
28: invokestatic #21 // Method java/lang/String.valueOf:(Ljava/lang/Object;)L
java/lang/String;
31: invokespecial #27 // Method java/lang/StringBuilder."<init>":(Ljava/lang/S
tring;)V
34: ldc #40 // String 333
36: invokevirtual #32 // Method java/lang/StringBuilder.append:(Ljava/lang/Str
ing;)Ljava/lang/StringBuilder;
39: invokevirtual #36 // Method java/lang/StringBuilder.toString:()Ljava/lang/
String;
42: astore_1
43: getstatic #42 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload_1
47: invokevirtual #48 // Method java/io/PrintStream.println:(Ljava/lang/String
;)V
50: return
}

这段字节码不用看得很懂,大致上能明白就好,意思很明显:编译器每次碰到"+"的时候,会new一个StringBuilder出来,接着调用append方法,在调用toString方法,生成新字符串

那么,这意味着,如果代码中有很多的"+",就会每个"+"生成一次StringBuilder,这种方式对内存是一种浪费,效率很不好。

在Java中还有一种拼接字符串的方式,就是String的concat方法,其实这种方式拼接字符串也不是很好,具体原因看一下concat方法的实现:

public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}

意思就是通过两次字符串的拷贝,产生一个新的字符数组buf[],再根据字符数组buf[],new一个新的String对象出来,这意味着concat方法调用N次,将发生N*2次数组拷贝以及new出N个String对象,无论对于时间还是空间都是一种浪费。

根据上面的解读,由于"+"拼接字符串与String的concat方法拼接字符串的低效,我们才需要使用StringBuilder和StringBuffer来拼接字符串。以StringBuilder为例:

 public class TestMain
{
public static void main(String[] args)
{
StringBuilder sb = new StringBuilder("111");
sb.append("222");
sb.append("111");
sb.append("111");
sb.append("444");
System.out.println(sb.toString());
}
}

StringBuffer和StringBuilder原理一样,无非是在底层维护了一个char数组,每次append的时候就往char数组里面放字符而已,在最终sb.toString()的时候,用一个new String()方法把char数组里面的内容都转成String,这样,整个过程中只产生了一个StringBuilder对象与一个String对象,非常节省空间。StringBuilder唯一的性能损耗点在于char数组不够的时候需要进行扩容,扩容需要进行数组拷贝,一定程度上降低了效率

StringBuffer和StringBuilder用法一模一样,唯一的区别只是StringBuffer是线程安全的,它对所有方法都做了同步,StringBuilder是线程非安全的,所以在不涉及线程安全的场景,比如方法内部,尽量使用StringBuilder,避免同步带来的消耗。

另外,StringBuffer和StringBuilder还有一个优化点,上面说了,扩容的时候有性能上的损耗,那么如果可以估计到要拼接的字符串的长度的话,尽量利用构造函数指定他们的长度。

真的不能用"+"拼接字符串?

虽然说不要用"+"拼接字符串,因为会产生大量的无用StringBuilder对象,但也不是不可以,比如可以使用以下的方式:

 public class TestMain
{
public static void main(String[] args)
{
String str = "111" + "222" + "333" + "444";
System.out.println(str);
}
}

就这种连续+的情况,实际上编译的时候JVM会只产生一个StringBuilder并连续append等号后面的字符串。

不过上面的例子要注意一点,因为"111"、"222"、"333"、"444"都是编译期间即可得知的常量,因为第5行的代码JVM在编译的时候并不会生成一个StringBuilder而是直接生成字符串"111222333444"

但是这么写得很少,主要原因有两点:

1、例子比较简单,但实际上大量的“+”会导致代码的可读性非常差

2、待拼接的内容可能从各种地方获取,比如调用接口、从.properties文件中、从.xml文件中,这样的场景下尽管用多个“+”的方式也不是不可以,但会让代码维护性不太好

从为什么String=String谈到StringBuilder和StringBuffer的更多相关文章

  1. 不可变字符串String与可变字符串StringBuilder、StringBuffer使用详解

    String字符串 char类型只能表示一个字符,而String可以表示字符串,也就是一个字符序列.但String不是基本类型,而是一个定义好的类,是一个引用类型.在Java中,可以将字符串直接量赋给 ...

  2. String详解, String和CharSequence区别, StringBuilder和StringBuffer的区别 (String系列之1)

    本章主要介绍String和CharSequence的区别,以及它们的API详细使用方法. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/string01. ...

  3. String详解, String和CharSequence区别, StringBuilder和StringBuffer的区别

    本章主要介绍String和CharSequence的区别,以及它们的API详细使用方法. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/string01. ...

  4. Java不可变序列String和可变序列StringBuilder、StringBuffer

    String String变量是不可变的,源码里面用了final修饰. private final char value[]; String str = "Hello"; Syst ...

  5. Java String、StringBuilder、StringBuffer[笔记]

    String对象是字符串常量(创建之后不可更改),StringBuilder和StringBuffer对象是字符串变量(可更改),三者主要区别在于执行速度和线程安全. 执行速度 执行速度:String ...

  6. Java中String, StringBuilder和StringBuffer

    Java中常用来处理字符串的类有三个: String, StringBuffer和StringBuilder. 区别 三者都继承自CharSequence接口, 首先说明三者间主要区别 String字 ...

  7. String、Stringbuilder、StringBuffer异同

    相同点: String.Stringbuilder.StringBuffer 都可以操作字符串 String 是被final修饰的,容量定长 Stringbuilder 和 Stringbuilder ...

  8. StringBuilder与StringBuffer的区别

    相信大家看到过很多比较String和StringBuffer区别的文章,也明白这两者的区别,然而自从Java 5.0发布以后,我们的比较列表上将多出一个对象了,这就是StringBuilder类.St ...

  9. String/ StringBuilder/ StringBuffer

    1. 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些特性. 2. ne ...

随机推荐

  1. URAL 1992 CVS 可持久化链栈

    http://www.cnblogs.com/tedzhao/archive/2008/11/12/1332112.html 看这篇的链表部分的介绍应该就能理解“可持久化”了 动态分配内存的会T,只能 ...

  2. Jackson fasterxml跟codehaus的区别 (fasterxml vs. codehaus) -- 转载

    Jackson fasterxml和codehaus的区别: 他们是Jackson的两大分支.也是两个版本的不同包名.Jackson从2.0开始改用新的包名fasterxml:1.x版本的包名是cod ...

  3. git conifg

    1. git config简介 我们知道config是配置的意思,那么git config命令就是对git进行一些配置.而配置一般都是写在配置文件里面,那么git的配置文件在哪里呢?互动一下,先问下大 ...

  4. 关于C#怎么固定窗口大小属性详解

    原文地址:http://zhidao.baidu.com/link?url=aDqlJMuABC8IxXz5drmZcPIHHlTKX2mrfIpyRZNFp0IAB6RSxT24B2XXMRCK1e ...

  5. Http请求中Content-Type讲解以及在Spring MVC中的应用

    引言: 在Http请求中,我们每天都在使用Content-type来指定不同格式的请求信息,但是却很少有人去全面了解content-type中允许的值有多少,这里将讲解Content-Type的可用值 ...

  6. iOS—如何申请苹果公司开发者账号流程详细图文介绍(包括邓白氏编码的申请方法详细介绍)

    我们要申请开发者账号,首先就需要先注册一个苹果的apple id,然后再这个账号的基础上去继续,这个相信大家都知道 这是申请appleid的地址:https://appleid.apple.com/a ...

  7. Android Fragment应用实战,使用碎片向ActivityGroup说再见

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/13171191 现在Fragment的应用真的是越来越广泛了,之前Android在3 ...

  8. python subprocess 自动运行实验室程序

    import threading, os, subprocess, time exec_path = "/home/xhz/gems/ruby/amd...../bin/tester.exe ...

  9. 关于windows下QT以及QT creator的安装

    普及  之  windows下qt的安装及配置   qt介绍 : Qt,分为商业.开源两个版本,商业版需要花钱购买license,而开源版本则遵守GPL协议,提供了源码,用户需要自行编译,才能生产动态 ...

  10. JAVA 异常类

    1.Exception(异常) :是程序本身可以处理的异常. 2.Error(错误): 是程序无法处理的错误.这些错误表示故障发生于虚拟机自身.或者发生在虚拟机试图执行应用时,一般不需要程序处理. 3 ...