小论 String、StringBuffer 与 StringBuilder

某日,一AS前端开发的同事问道:Java中是不是有个叫StringBuffer的东东啊?答曰:没有吧,向来用的都是 StringBuilder。然却觉有些耳熟,立马百度之...顿时狂汗。历史再次证明,基础不扎实是万万不能够的。下面就了解一下这三兄弟吧。

基本理解:
* String 对象的内容为 final 的 char 数组,因此 String 对象一旦被创建,就不能改变;我们平时表面上对 String 对象内容的修改,其实质是通常是创建了一个新的 String 对象,然后将当前引用指向这个新的对象,而原来的对象往往成为垃圾;当需要频繁修改 String 对象的内容时,String 的性能比 StringBuffer 和 StringBuilder 低了 n 个重量级,强烈不建议采用。
* StringBuffer 是可变对象,通过其 public 方法 append() 等修改其内容,不会创建新对象,同时其对同步(Synchronized)问题做了处理,是线程安全的。
* StringBuilder 与 StringBuffer 类似,但线程不安全,因此相比之下会有更高的效率。 

较为详细的解释:
1 Java.lang.String:String 是干嘛的?这个没法解答了。  

String 类声明:

public final class String   
    implements java.io.Serializable, Comparable<String>, CharSequence  

String 中使用 final char [] 来保存 String 的内容: 
/** The value is used for character storage. */  
   private final char value[];  
所以,String 对象为不可变对象。

2 StringBuffer 和 StringBuilder
2.1 Java.lang.StringBuffer:线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。   
2.2 java.lang.StringBuilder:一个可变的字符序列。StringBuilder 是 JDK 5.0 新增的,此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

StringBuilder 类声明: 
public final class StringBuilder   extends AbstractStringBuilder  
    implements java.io.Serializable, CharSequence  
StringBuffer 类声明: 
public final class StringBuffer    extends AbstractStringBuilder  
   implements java.io.Serializable, CharSequence  
可以看出,StringBuilder 、StringBuffer 继承自相同的抽象类 AbstractStringBuilder,并且实现了相同的接口。对应于 String 类中的 final 属性 private final char value[],AbstractStringBuilder 的属性声明如下:
/** 
     * The value is used for character storage. 
     */  
    char value[];  
在 AbstractStringBuilder 类中,其属性 char[] 的长度是动态变化的。默认构建长度为16的 char 数组;只有当其长度不够的时候才会扩展,不会提前扩展;扩展时每次的增长公式为:(length + 1) * 2;大量使用 System.arraycopy 函数实现拷贝的功能。
StringBuiler 、StringBuffer 类的方法大体一样,其方法体都只是在调用父类的对应的方法,只不过 StringBuffer 的方法声明都有 synchronized 关键字,所以 StringBuffer 是线程安全的,而除去了对同步问题的处理,StringBuilder 拥有较好的性能。

相关知识补充:
* 栈(stack):主要保存基本类型(或者叫内置类型)(char、byte、short、int、long、float、double、boolean)和对象的引用,数据可以共享,速度仅次于寄存器(register),快于堆。
* 堆(heap):用于存储对象。
* String 池(String 常量池):String对象是不可变的,随着应用程序的增长,String 字面值占用大量的程序内存,对于程序而言,全部的 String 的字面值往往有大量的冗余。为了使 java 更高效地使用内存,JVM 留出一块特殊的内存区域,称为"String 常量池"。当编译器遇到 String 字面值时,它先检查该池内是否存在一个相同的 String 字面值。如果找到了,则将新的引用指向这个现有的 String,而不是创建新的 String 对象。如果在 String 常量池没有这个 String,则 java 将在常规内存创建一个新的 String 对象,并且引用它,而这个 String 也会放入池中。字符串池保存着很多 String 对象,并且可以被共享使用,因此它提高了效率。由于 String 类是 final 的,它的值一经创建就不可改变,因此我们不用担心 String 对象共享而带来程序的混乱。字符串池由 String 类维护,我们可以调用 intern() 方法来访问字符串池。如果决定对 String 对象进行大量的处理。则最终会导致 String 池中产生大量的被丢弃的 String 对象。但是 StringBuffer 与 StringBuilder 类型的对象就能够被一次次的修改而不会遗留下大量的被丢弃的 String 对象。

String 包含几种不同的创建方式,并且不同的创建方式都会将 String 存到入不同的区域内,例如:String 池或者堆中。遵循以下4个原理:
原理1:当使用任何方式来创建一个字符串对象 s 时,Java运行时(运行中JVM)会拿着这个 s 在 String 池中找是否存在内容相同的字符串对象,如果不存在,则在 String 池中创建一个字符串 s,否则,不在池中添加。 
原理2:Java中,只要使用 new 关键字来创建对象,则一定会(在堆区或栈区)创建一个新的对象。 
原理3:使用直接指定或者使用纯字符串串联来创建 String 对象,则仅仅会检查维护 String 池中的字符串,池中没有就在池中创建一个,有则罢了!但绝不会在堆栈区再去创建
该 String 对象。 
原理4:使用包含变量的表达式来创建 String 对象,则不仅会检查维护 String 池,而且还会在堆栈区创建一个新的 String 对象。 

String 类对象创建方式举例:
String s = new String();
s = "abc";
这将创建两个 String 对象,分别位于堆栈区和 String 池。
String s = "abc";
这将创建一个 String 对象,位于 String 池。
String s = new String("abc");
这与方式1其实是一致的,将创建两个 String 对象,分别位于堆栈区和 String 池。
String a="abc";
String b="abc";
那这里呢?只会创建一个 String 对象,位于 String 池。
String s="ab"+"cd";
这将创建3个 String 对象:"ab"、"cd"和"abcd",均位于 String 池。
String a="abc";
String b="abc";
String s=a+b+"def";
这将创建4个 String 对象:"abc"、"def"、"abcabcdef"和"abcabcdef",最后一个位于堆栈区,其余位于 String 池。

使用 new 方式新建对象,符合面向对象程序设计的规范。但对于 String ,应该特殊考虑,提倡大家用引号包含文本的方式来创建 String 对象以提高效率,实际上这也是我们在编程中常采用的。

其他补充:

在某些特别情况下,String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的: 
String Str = “This is only a” + “ simple” + “ test”; 
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”); 
你会很惊讶的发现,生成 String Str 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个 
String Str = “This is only a” + “ simple” + “test”;
其实就是: 
String Str = “This is only a simple test”; 
所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如: 
String S2 = “This is only a”; 
String S3 = “ simple”; 
String S4 = “ test”; 
String S1 = S2 +S3 + S4; 

这时候 JVM 会规规矩矩的按照原来的方式去做,S1 对象的生成速度就不像刚才那么快了。

再列出一个诡异的情况:

String s = null;
s += "abc";
System.out.println(s);
毫无疑问,这是一个及其猥琐的程序员才能写出的代码。其输出应该是什么呢?正确答案是:

nullabc;

诡异吧。




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值