Java SE 字符串类型 String、StringBuilder、StringBuffer

Java提供了三种字符串类型:String、StringBuilder、StringBuffer

运行性能,或者说是执行速度:StringBuilder > StringBuffer > String

String

String 是日常工作中用到最的字符串,然而却是最慢的,并且经常会遇到一些问题。

String 最慢的原因:String 为字符串常量,而 StringBuilder 和 StringBuffer 均为字符串变量,即 String 对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。

String str="abc";
System.out.println(str);
str = str + "de";
System.out.println(str);

如果运行这段代码会发现先输出 “abc”,然后又输出 “abcde”,好像是 str 这个对象被更改了,其实,这只是一种假象罢了,JVM 对于这几行代码是这样处理的,首先创建一个 String 对象 str,并把 “abc” 赋值给 str,然后在第三行中,其实 JVM 又创建了一个新的对象也名为 str,然后再把原来的 str 的值和 “de” 加起来再赋值给新的 str,而原来的 str 就会被 JVM 的垃圾回收机制(GC)给回收掉了,所以,str 实际上并没有被更改,也就是前面说的 String 对象一旦创建之后就不可更改了。所以,Java 中对 String 对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

StringBuilder 和 StringBuffer 的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比 String 快很多。

String 对象赋值方法

(1)字面量赋值方式

String str = "Hello";

该种直接赋值的方法,JVM 会去字符串常量池(String对象不可变)中寻找是否有 equals("Hello") 的 String 对象,如果有,就把该对象在字符串常量池中 "Hello" 的引用复制给字符串变量 str,如若没有,就在堆中新建一个对象,同时把引用驻留在字符串常量池中,再把引用赋给字符串变量 str。
用该方法创建字符串时,无论创建多少次,只要字符串的值(内容)相同,那么它们所指向的都是堆中的同一个对象。
该方法直接赋值给变量的字符串存放在常量池里

(2)new关键字创建新对象

String str = new String("Hello");

利用 new 来创建字符对象串时,无论字符串常量池中是否有与当前值相同的对象引用,都会在堆中新开辟一块内存,创建一个新的对象。

String 字符串拼接

对字符串进行拼接操作,即做"+"运算的时候,分2种情况:

(1)表达式右边是纯字符串常量会么存放在常量池里面

String str = "Hello" + "World";

(2)表达式右边如果存在字符串引用,也就是字符串对象的句柄,会存放在堆里面

String str1 = "Hello";
String str2 = World";
String str = str1 + str2;

为了证明以上的结论,可以看以下两段示例代码:

示例代码1:

String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // true
System.out.println(s1.equals(s2)); // true

示例代码2:

String str1 = "Hello";
String str2 = "World";
String s1 = str1 + str2;
String s2 = str1 + str2;
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true

关于常量池

常量池是方法区的一部分,而方法区是线程共享的,所以常量池也是线程共享的,且它是线程安全的,它让有相同值的引用指向同一个位置,如果引用值变化了,但是常量池中没有新的值,那么就会新建一个常量结果来交给新的引用,对于同一个对象,new 出来的字符串存放在堆中,而直接赋值给变量的字符串存放在常量池里。

判断String相等 == 和 equals() 方法的区别

Java中的 == 是比较值是否相等:

1)对于byte、short、int、long、float、double、char、boolean 这8种基本类型的比较,是比较值是否相等;

2)对于对象的比较,是比较地址是否相等。

equals() 是比较对象的内容是否相等

字符串用这两种比较方式都是可行的,具体看想要比较什么,总体来看 "==" 稍微强大些,因为它既要求内容相同,也要求引用对象相同,但不太符合面向对象的编程方式~

示例代码如下:

String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // true
System.out.println(s1.equals(s2)); // true

String 对象不可变

JDK 中 String 类的定义为

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

从这里可以看出,String 类是被 final 关键字所修饰的,因此 String 类对象不可变,也不可继承。

这里要注意一个误区,字符串对象不可变,但字符串变量所指的值是可变的,即引用地址可变。String 变量存储的是对 String 对象的引用,String 对象里存储的才是字符串的值【注意区分对象和对象的引用】。看下面的例子

String str = "abc"; //str是个对象引用
System.out.println(str); //输出abc
str = "abcde"; //不会出错
System.out.println(str); //输出abcde

当给 str 第二次赋值的时候,对象 "abc" 并没有被销毁,仍存放在常量池中(String自带),只是让 str 指向了 "abcde" 的内存地址,而字符串对象 "abcde" 是新生成的,即 str 第二次赋值时并不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。记住,对 String 对象的任何改变都不影响到原对象,相关的任何改变的操作都会生成新的对象。

StringBuilder

StringBuilder 实际上用得也很多,我们先来看一段代码:

public class StringTest {
    public void demo1() {
        String str = "Hello";
        str += "World";
        System.out.println(str);
    }
}

利用命令查看字节码内容:

javap -c -l StringTest.class

字节码内容如下:

D:\> javap -c -l StringTest.class
Compiled from "StringTest.java"
public class StringTest {
  public StringTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 1: 0

  public void demo();
    Code:
       0: ldc           #2                  // String Hello
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: ldc           #6                  // String World
      16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_1
      23: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      26: aload_1
      27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      30: return
    LineNumberTable:
      line 3: 0
      line 4: 3
      line 5: 23
      line 6: 30
}

看到以上内容,会发现反编译的代码和我们实际写的代码不太一样,写的代码用的是 String 字符串,而反编译以后得到的却是 StringBuilder 字符串对象,并且使用加号拼接字符串也变成了 StringBuilder 对象的 append() 方法

是这样的,Java 编译器对我们写的代码做了编译优化。这就是为什么我们一定要会使用 StringBuilder 的原因 —— 这也是一个经典技术面试题“为什么 Java 不能在for循环中使用加号拼接字符串”的最佳答案。

StringBuffer

在线程安全方面,StringBuilder 是线程不安全的,而 StringBuffer 是线程安全的,因此 StringBuffer 要比 StringBuilder 慢一些。

如果一个 StringBuffer 对象在字符串缓冲区被多个线程使用时,StringBuffer 中很多方法可以带有 synchronized 关键字,所以可以保证线程是安全的,但 StringBuilder 的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用 StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的 StringBuilder。

StringJoiner

JDK 8 官方提供字符串拼接的工具类,其底层也是使用 StringBuilder 对象实现的,最经典的一个应用示例代码如下:

List<String> strs = Arrays.asList("张三", "李四", "王五");
StringJoiner joiner = new StringJoiner(",", "start", "end");
for (String str : strs) {
    joiner.add(str);
}
System.out.println("start张三,李四,王五end".equals(joiner.toString())); //true

总结

String:适用于少量的字符串操作的情况

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

StringJoiner:适用于简单处理字符串拼接的情况

参考资料

Java中的String,StringBuilder,StringBuffer三者的区别

https://www.cnblogs.com/su-feng/p/6659064.html

Java详解【String】+【StringBuilder vs StringBuffer】+【字符串拼接】

https://blog.csdn.net/zhsihui429/article/details/87941734

Java--StringBuilder,StringBuffer,StringJoiner

https://cloud.tencent.com/developer/article/1347563

JavaSe: String的编译期优化

https://www.cnblogs.com/f1194361820/p/8012393.html

浅谈java语言String字符串优化

https://www.cnblogs.com/zhengyu940115/p/6652938.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336