一、异同点:

  1. 都是 final 类, 都不允许被继承;

  2. String 长度是不可变的, StringBuffer、StringBuilder 长度是可变的;

  3. StringBuffer 是线程安全的, StringBuilder 不是线程安全的。

StringBuilder 与 StringBuffer 支持的所有操作基本上是一致的, 不同的是, StringBuilder 不需要执行同步。同步操作意味着要耗费系统的一些额外的开销, 或时间, 或空间, 或资源等, 甚至可能会造成死锁。从理论上来讲, StringBuilder 的速度要更快一些。

其继承关系如下图

二、应用场景

使用 String 类的场景:在字符串不经常变化的场景中可以使用 String 类,例如常量的声明、少量的变量运算。

使用 StringBuffer 类的场景:在频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用 StringBuffer,例如 XML 解析、HTTP 参数解析和封装。

使用 StringBuilder 类的场景:在频繁进行字符串运算(如拼接、替换、和删除等),并且运行在单线程的环境中,则可以考虑使用 StringBuilder,如 SQL 语句的拼装、JSON 封装等。

三、分析

串联字符串的性能小测:

 1 public class Application {
2
3 private final int LOOP_TIMES = 200000;
4 private final String CONSTANT_STRING = "min-snail";
5
6 public static void main(String[] args) {
7
8 new Application().startup();
9 }
10
11 public void testString(){
12 String string = "";
13 long beginTime = System.currentTimeMillis();
14 for(int i = 0; i < LOOP_TIMES; i++){
15 string += CONSTANT_STRING;
16 }
17 long endTime = System.currentTimeMillis();
18 System.out.print("String : " + (endTime - beginTime) + "\t");
19 }
20
21 public void testStringBuffer(){
22 StringBuffer buffer = new StringBuffer();
23 long beginTime = System.currentTimeMillis();
24 for(int i = 0; i < LOOP_TIMES; i++){
25 buffer.append(CONSTANT_STRING);
26 }
27 buffer.toString();
28 long endTime = System.currentTimeMillis();
29 System.out.print("StringBuffer : " + (endTime - beginTime) + "\t");
30 }
31
32 public void testStringBuilder(){
33 StringBuilder builder = new StringBuilder();
34 long beginTime = System.currentTimeMillis();
35 for(int i = 0; i < LOOP_TIMES; i++){
36 builder.append(CONSTANT_STRING);
37 }
38 builder.toString();
39 long endTime = System.currentTimeMillis();
40 System.out.print("StringBuilder : " + (endTime - beginTime) + "\t");
41 }
42
43 public void startup(){
44 for(int i = 0; i < 6; i++){
45 System.out.print("The " + i + " [\t ");
46 testString();
47 testStringBuffer();
48 testStringBuilder();
49 System.out.println("]");
50 }
51 }
52 }

上面示例是频繁的去串联一个比较短的字符串, 然后反复调 6 次。测试是一个很漫长的过程, 在本人的笔记本电脑上总共花去了 23 分钟之多, 下面附上具体数据:

Number String StringBuffer StringBuilder
0 231232 17 14
1 233207 6 6
2 231294 8 6
3 235481 7 6
4 231987 9 6
5 230132 8 7
平均 3'52'' 9.2 7.5

从表格数据可以看出, 使用 String 的 "+" 符号串联字符串的性能差的惊人, 大概会维持在 3分40秒 的时候可以看到一次打印结果;

其次是 StringBuffer, 平均花时 9.2 毫秒; 然后是 StringBuilder, 平均花时 7.5 毫秒。

  1. 耗时大的惊人的 String 到底是干嘛去了呢?

实际上这个已经在底层上是将循环体内的string += CONSTANT_STRING; 语句转成了:

string = (new StringBuilder(String.valueOf(string))).append("min-snail").toString();

所以在二十万次的串联字符串中, 每一次都先去创建 StringBuilder 对象, 然后再调 append() 方法来完成 String 类的 "+" 操作。

这里的大部分时间都花在了对象的创建上, 而且每个创建出来的对象的生命都不能长久, 朝生夕灭, 因为这些对象创建出来之后没有引用变量来引用它们,

那么它们在使用完成时候就处于一种不可到达状态, java 虚拟机的垃圾回收器(GC)就会不定期的来回收这些垃圾对象。因此会看到堆内存中的曲线起伏变化很大。

但如果是遇到如下情况:

1 String concat1 = "I" + " am " + "min-snail";
2
3 String concat2 = "I";
4 concat2 += " am ";
5 concat2 += "min-snail";

java 对 concat1 的处理速度也是快的惊人。本人在自己的笔记本上测试多次, 耗时基本上都是 0 毫秒。这是因为 concat1 在编译期就可以被确定是一个字符常量。

当编译完成之后 concat1 的值其实就是 "I am min-snail", 因此, 在运行期间自然就不需要花费太多的时间来处理 concat1 了。如果是站在这个角度来看, 使用

StringBuilder 完全不占优势, 在这种情况下, 如果是使用 StringBuilder 反而会使得程序运行需要耗费更多的时间。

但是 concat2 不一样, 由于 concat2 在编译期间不能够被确定, 因此, 在运行期间 JVM 会按老一套的做法, 将其转换成使用 StringBuilder 来实现。

  1. 从表格数据可以看出, StringBuilder 与 StringBuffer 在耗时上并不相差多少, 只是 StringBuilder 稍微快一些, 但是 StringBuilder 是冒着多线程不安全的潜在风险。这也是 StringBuilder 为赚取表格数据中的 1.7 毫秒( 若按表格的数据来算, 性能已经提升 20% 多 )所需要付出的代价。

  2. 综合来说:

StringBuilder 是 java 为 StringBuffer 提供的一个等价类, 但不保证同步。在不涉及多线程的操作情况下可以简易的替换 StringBuffer 来提升系统性能; StringBuffer 在性能上稍略于 StringBuilder, 但可以不用考虑线程安全问题; String 的 "+" 符号操作起来简单方便,String 的使用也很简单便捷, java 底层会转换成StringBuilder 来实现, 特别如果是要在循环体内使用, 建议选择其余两个。

使用 StringBuffer、StringBuilder 的无参构造器产生的对象默认拥有 16 个字符长度大小的字符串缓冲区, 如果是调参数为 String 的构造器,默认的字符串缓冲区容量是 String 对象的长度 + 16 个长度的大小(留 16 个长度大小的空缓冲区)。详细信息可见 StringBuilder 源码:

public StringBuilder() {
super(16);
} public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(int capacity) {
super(16);
}

当使用 append 或 insert 方法向源字符串追加内容的时候, 如果内部缓冲区的大小不够, 就会自动扩张容量, 具体信息看 AbstractStringBuilder 源码:

    void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
} 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;
}

StringBuffer 与 StringBuilder 是相类似的, 这里就不贴 StringBuffer 的源码了。

不同构造器间的差异:

 1 public static void main(String[] args) {
2
3 StringBuilder builder1 = new StringBuilder("");
4 StringBuilder builder2 = new StringBuilder(10);
5 StringBuilder builder3 = new StringBuilder("min-snail"); // [ 9个字符 ]
6
7 System.out.println(builder1.length()); // 0
8 System.out.println(builder2.length()); // 0
9 System.out.println(builder3.length()); // 9
10
11 System.out.println(builder1.capacity()); // 16
12 System.out.println(builder2.capacity()); // 10
13 System.out.println(builder3.capacity()); // 25 [ 25 = 9 + 16 ]
14
15 builder2.append("I am min-snail"); // [ 14个字符 ]
16
17 System.out.println(builder2.length()); // 14
18 System.out.println(builder2.capacity()); // 22 [ 22 = (10 + 1) * 2 ]
19 }

从上面的示例代码可以看出, length() 方法计算的是字符串的实际长度, 空字符串的长度为 0 (这个和 String 是一样的: "".length() == 0)。

capacity() 方法是用来计算对象字符串缓冲区的总容量大小:

builder1 为: length + 16 = 0 + 16 = 16;

builder3 为: length + 16 = 9 + 16 = 25;

builder2 由于是直接指定字符串缓冲区的大小, 因此容量就是指定的值 10, 这个从源码的构造器中就能很容易的看出;

当往 builder2 追加 14 个字符长度大小的字符串时, 这时候原有的缓冲区容量不够用, 那么就会自动的扩容: (10 + 1) * 2 = 22

这个从源码的 expandCapacity(int) 方法的第一行就能够看的出。

不同构造器的性能小测:

 1 public class Application {
2
3 private final int LOOP_TIMES = 1000000;
4 private final String CONSTANT_STRING = "min-snail";
5
6 public static void main(String[] args) {
7
8 new Application().startup();
9 }
10
11 public void testStringBuilder(){
12 StringBuilder builder = new StringBuilder();
13 long beginTime = System.currentTimeMillis();
14 for(int i = 0; i < LOOP_TIMES; i++){
15 builder.append(CONSTANT_STRING);
16 }
17 builder.toString();
18 long endTime = System.currentTimeMillis();
19 System.out.print("StringBuilder : " + (endTime - beginTime) + "\t");
20 }
21
22 public void testCapacityStringBuilder(){
23 StringBuilder builder = new StringBuilder(LOOP_TIMES * CONSTANT_STRING.length());
24 long beginTime = System.currentTimeMillis();
25 for(int i = 0; i < LOOP_TIMES; i++){
26 builder.append(CONSTANT_STRING);
27 }
28 builder.toString();
29 long endTime = System.currentTimeMillis();
30 System.out.print("StringBuilder : " + (endTime - beginTime) + "\t");
31 }
32
33 public void startup(){
34 for(int i = 0; i < 10; i++){
35 System.out.print("The " + i + " [\t ");
36 testStringBuilder();
37 testCapacityStringBuilder();
38 System.out.println("]");
39 }
40 }
41 }

示例中是频繁的去调 StringBuilder 的 append() 方法往源字符串中追加内容, 总共测试 10 次, 下面附上测试的结果的数据:

Number StringBuilder() StringBuilder(int)
0 60 33
1 43 26
2 41 25
3 42 24
4 51 30
5 92 24
6 55 24
7 40 24
8 55 21
9 44 21
平均 52.3 25.2

从表格数据可以看出, 合理的指定字符串缓冲区的容量可以大大的提高系统的性能(若按表格的数据来算, 性能约提升了 108%), 这是因为 StringBuilder 在缓冲区容量不足的时候会自动扩容, 而扩容就会涉及到数组的拷贝(StringBuilder 和 StringBuffer 底层都是使用 char 数组来实现的), 这个也可以在源码的 expandCapacity(int) 方法中看的出。这些额外的开销都是需要花费掉一定量的时间的。

在上示代码中, 如果将 StringBuilder 换成 StringBuffer, 其余保持不变, 测试的结果的数据如下:

Number SstingBuffer() StringBuffer(int)
0 85 58
1 70 56
2 73 56
3 71 55
4 73 58
5 117 55
6 84 55
7 69 55
8 70 52
9 73 52
平均 78.5 55.2

与 StringBuilder 相类似的, 指定容量的构造器在性能上也得到了较大的提升(若按表格数据来算, 性能约提升了 42%), 但由于 StringBuffer 需要执行同步, 因此性能上会比 StringBuilder 差一些。

[Java]String、 StringBuffer、StringBuilder的区别的更多相关文章

  1. String,StringBuffer,StringBuilder的区别

    public static void main(String[] args) { String str = new String("hello...."); StringBuffe ...

  2. [置顶] String StringBuffer StringBuilder的区别剖析

    这是一道很常见的面试题目,至少我遇到过String/StringBuffer/StringBuilder的区别:String是不可变的对象(final)类型,每一次对String对象的更改均是生成一个 ...

  3. String,StringBuffer,StringBuilder的区别及其源码分析

    String,StringBuffer,StringBuilder的区别这个问题几乎是面试必问的题,这里做了一些总结: 1.先来分析一下这三个类之间的关系 乍一看它们都是用于处理字符串的java类,而 ...

  4. Question 20171115 String&&StringBuffer&&StringBuilder的区别与联系?

    Question 20171114 String&&StringBuffer&&StringBuilder的区别和联系 创建成功的String对象,其长度是固定的,内容 ...

  5. java中 String StringBuffer StringBuilder的区别

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

  6. Android/Java 中的 String, StringBuffer, StringBuilder的区别和使用

    Android 中的 String, StringBuffer 和 StringBuilder 是移动手机开发中经常使用到的字符串类.做为基础知识是必须要理解的,这里做一些总结. A.区别 可以从以下 ...

  7. 在JAVA中,String,Stringbuffer,StringBuilder 的区别

    首先是,String,StringBuffer的区别 两者的主要却别有两方面,第一是线程安全方面,第二是效率方面 线程安全方面: String  不是线程安全的,这意味着在不同线程共享一个String ...

  8. Java String StringBuffer StringBuilder

    String  字符串常量存储在常量区,每次追加操作会创建新的对象: StringBuffer  字符串变量  线程安全 在堆上创建,每次追加操作在原对象上进行操作:  速度 StringBuffer ...

  9. 深入理解String, StringBuffer, StringBuilder的区别(基于JDK1.8)

    String.StringBuffer.StringBuilder都是JAVA中常用的字符串操作类,对于他们的区别大家也都能耳熟能详,但底层到底是怎样实现的呢?今天就再深入分析下这三种字符串操作的区别 ...

  10. 从源码看String,StringBuffer,StringBuilder的区别

    前言 看了一篇文章,大概是讲面试中的java基础的,有如题这么个面试题.我又翻了一些文章看了下,然后去看源码.看一下源码大概能更加了解一些. String String类是final的,表示不可被继承 ...

随机推荐

  1. the art of seo(chapter eleven)

    Tracking Results and Measuring Success goal -> driver ***Why Measuring Success Is Essential to th ...

  2. memcached高可用

    http://sourceforge.net/projects/repcached/ memcached-1.2.8-repcached-2.2.tar.gz tar zxvf memcached-1 ...

  3. scala初步

    1 安装 http://scala-ide.org/ http://scala-lang.org/

  4. Custom Database Integration Guide

    Introduction This document provides instructions for integrating Openfire authentication, users, and ...

  5. hls协议(最清晰的讲解)

    今天来介绍一下HLS协议,这个协议是由苹果公司提出并推广开来的.来一段维基百科的定义. HTTP Live Streaming(缩写是HLS)是一个由苹果公司提出的基于HTTP的流媒体网络传输协议.是 ...

  6. Visual Studio 2012简体中文专业版密钥(激活码)

    VS2012 正式版在Beta版的基础上进行了很多改进,尤其是加入了全新的用户界面. VS2012 的硬件需求与VS2010相同,不过由于 Visual Studio 2012 利用了新版 Windo ...

  7. ubuntu 安装cuda 成功

    洗洗睡了

  8. CodeForces - 660F:Bear and Bowling 4(DP+斜率优化)

    Limak is an old brown bear. He often goes bowling with his friends. Today he feels really good and t ...

  9. 递归------python实现列表创建二叉树

    # -*- coding:utf-8 -*- '二叉树结点类' class TreeNode: def __init__(self, x): self.val = x self.left = None ...

  10. P2766 [网络流24题]最长不下降子序列问题

    ha~ «问题描述: 给定正整数序列$x_1,...,x_n$ .$n<=500$ 求(1)计算其最长不下降子序列的长度$s$. (2)计算从给定的序列中最多可取出多少个长度为$s$的不下降子序 ...