前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 中的字符串深入解读 (String、StringBuffer、StringBuilder)

Java 中的字符串深入解读 (String、StringBuffer、StringBuilder)

原创
作者头像
Lorin 洛林
修改2024-01-26 21:09:01
3984
修改2024-01-26 21:09:01
举报
文章被收录于专栏:Java 技术小屋Java 技术小屋

导读

  • String、StringBuffer、StringBuilder 的区别以及如何正确使用
  • 如何理解 String 不可变?
  • String、StringBuffer、StringBuilder 在 JVM 内存中的分布,以及面试常问:Java 不同字符类会创建几个对象?

版本及环境

  • JDK 运行版本:JDK8

String、StringBuffer、StringBuilder

  • String 是不可变字符串,StringBuffer、StringBuilder 是可变字符串。
  • StringBuffer 是线程安全的,StringBuilder 是线程非安全的。StringBuilder 具有 StringBuffer 一切能力,当不涉及多线程安全时,优先使用 StringBuilder,没有同步操作,性能更好。

如何理解 String 不可变

  • 即无法通过引用地址修改 String 对象的值。
代码语言:java
复制
// String 类
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    ....
}
1、value[] 数组是 final 类型,表示 value 的引用地址不可变,但是 Arrary 数组是可变的
    final char[] charArray = {'a', 'b', 'c', 'd', 'e'};

    char ch = 'z';
    charArray[0] = ch;
    System.out.println(charArray);
2、但是由于 value[] 数组私有,且没有提供外部访问能力,因此无法修改

String 不可变的好处

String HashCode缓存

  • String的HashCode在比如HashMap等容器当中都有使用,String的不变性保证了HashCode的不变性,不必每次去计算新的HashCode,这也是Map喜欢将String作为Key的原因,这是一种性能考虑。

安全性

  • String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患,比如使用的url被修改。

可变 StringBuilder vs StringBuffer 与线程安全

  • StringBuilder、StringBuffer 都是可变的,StringBuffer 是线程安全的,StringBuilder 是线程非安全的。StringBuilder 具有 StringBuffer 一切能力,当不涉及多线程安全是,优先使用 StringBuilder,没有同步操作,性能更好。
代码语言:java
复制
    // 继承 AbstractStringBuilder 实现不同
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

字符常量池

  • Java 内部维护了一个字符常量池,当创建一个字符 String 时,如果已经在字符常量池中存在,则使用该存在该变量,而不会创建新的。

思考一下

  • 下面的代码你可以说出它们的创建过程,以及在 JVM 内存中的分配?
代码语言:java
复制
        // 在常量池中
        String str1 = "abc";

        // 在堆上
        String str2 = new String("abc");

        // 字符串常量 + 号的重载 编译时合并成 abcdef 字符串
        String str3 = "abc" + "def";

        String str4 = "abc" + new String("def");

        String str41 = new String("def") + "abc";

        String str5 = new String("abc") + new String("def");

        String str6 = "defabc";

""、new String()、new StringBuffer()、new StringBuilder() 与字符串常量池

  • 从上面可以看出,""、new String()、StringBuffer()、StringBuilder() 创建字符串的过程和结果并不一样,我们分为以下几种情况讨论:

"" 和 new String()

代码语言:java
复制
String str1 = "abc";
String str2 = "abc";
// 直接在字符串常量池中查找,如果存在直接使用,不存在创建,即str1、str2指向常量池中的同一个值

String str2 = new String("abc");
// 在堆中创建一个 String 对象,判断 abc 在字符串常量是否存在,存在char数组直接指向,不存在创建新的字符串常量再指向
// 上述检查常量池是否有相同Unicode的字符串常量时,使用的方法是String中的intern()方法

StringBuffer() 和 StringBuilder()

  • 两者都是在堆中创建对象,并不涉及字符串常量池,++char 数组维护在堆的对象中++。

拓展: 当两个字符串连接时,JVM 底层是如何实现的

代码语言:java
复制
        // 字符串常量 + 号的重载 编译时优化合并成 abcdef 字符串
        String str3 = "abc" + "def";

        String str4 = "abc" + new String("def");

        String str41 = new String("def") + "abc";

        String str5 = new String("abc") + new String("def");
        // 上面三种情况,都会先创建一个 StringBuilder 对象再进行字符串拼接,最后调用toString()方法创建一个 String 对象
        // 注意: 在这里创建 String 时,并没有在字符串常量池中创建,而是直接指向了 StringBuilder 的 value 数组
        // @Override
        // public String toString() {
        // Create a copy, don't share the array
        //    return new String(value, 0, count);
        // }
        
        String str6 = "defabc";
        // 因此,上述 String str6 = "defabc" 会在字符串常量池中创建 "defabc" 常量

字节码分析

完整源码

代码语言:java
复制
public class MainTest {
    public static void main(String[] args) throws InterruptedException {
        // 在常量池中
        String str1 = "abc";

        // 在堆上
        String str2 = new String("abc");

        // 字符串常量 + 号的重载 编译时合并成 abcdef 字符串
        String str3 = "abc" + "def";

        String str4 = "abc" + new String("def");

        String str41 = new String("def") + "abc";

        String str5 = new String("abc") + new String("def");

        String str6 = "defabc";
    }
}

字节码

  • 使用 javap 命令查看字节码
代码语言:c
复制
// javap -c .\MainTest.class
Compiled from "MainTest.java"
public class org.example.MainTest {
  public org.example.MainTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.InterruptedException;
    Code:
---------------------------------------------String str1 = "abc"; --------------------------------------------------------------------------
       0: ldc           #2                  // String abc
       2: astore_1
---------------------------------------------String str2 = new String("abc");---------------------------------------------------------------  
       3: new           #3                  // class java/lang/String // 创建 String 对象
       6: dup
       7: ldc           #2                  // String abc
       9: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
      12: astore_2
---------------------------------------------String str3 = "abc" + "def";-------------------------------------------------------------------      
      13: ldc           #5                  // String abcdef  编译时优化为 "abcdef"
      15: astore_3
---------------------------------------------String str4 = "abc" + new String("def");-------------------------------------------------------
      16: new           #6                  // class java/lang/StringBuilder // 创建 StringBuilder
      19: dup
      20: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      23: ldc           #2                  // String abc
      25: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; //拼接 abc
      28: new           #3                  // class java/lang/String 创建 String 对象(def)
      31: dup
      32: ldc           #9                  // String def
      34: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
      37: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; //拼接 def 
      40: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; // 调用 toString 方法生成一个新 String 对象
      43: astore        4
--------------------------------------------------------------------------------------------------------------------------------------------
      45: new           #6                  // class java/lang/StringBuilder
      48: dup
      49: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      52: new           #3                  // class java/lang/String
      55: dup
      56: ldc           #9                  // String def
      58: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
      61: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      64: ldc           #2                  // String abc
      66: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      69: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      72: astore        5
      74: new           #6                  // class java/lang/StringBuilder
      77: dup
      78: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      81: new           #3                  // class java/lang/String
      84: dup
      85: ldc           #2                  // String abc
      87: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
      90: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      93: new           #3                  // class java/lang/String
      96: dup
      97: ldc           #9                  // String def
      99: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
     102: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     105: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     108: astore        6
     110: ldc           #11                 // String defabc
     112: astore        7
     114: return
}

总结

  • 当用于 url、路径地址 等需要保证不可以变的使用 String,可变的场景下需要保证线程安全使用 StringBuffer,不需要保证线程安全使用 StringBuilder,减少同步加锁开销。
  • 常见面试题:不同使用字符串的方式会创建多少个对象:
代码语言:java
复制
案例一:
String str3 = "abc" + "def";

编译阶段优化为 “abcdef” 在常量池创建一个对象 “abcdef” 

案例二:
String str4 = "abc" + new String("def")

"abc" 在常量池创建一个对象
new String("def")  在堆中创建一个 String 对象,常量池创建对象 "def"
创建一个 StringBuilder 对象用于拼接两个字符串,最后调用 toString() 方法创建一个 String 对象

// 即在不考虑字符串常量池已存在常量字符串的情况,需要创建 5 个对象
// 再思考一下:其实还有一个强引用对象 str4 对创建字符串的强引用

个人简介

👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • 版本及环境
  • String、StringBuffer、StringBuilder
    • 如何理解 String 不可变
      • String 不可变的好处
        • String HashCode缓存
        • 安全性
      • 可变 StringBuilder vs StringBuffer 与线程安全
        • 字符常量池
          • 思考一下
            • ""、new String()、new StringBuffer()、new StringBuilder() 与字符串常量池
              • "" 和 new String()
              • StringBuffer() 和 StringBuilder()
              • 拓展: 当两个字符串连接时,JVM 底层是如何实现的
            • 字节码分析
              • 完整源码
              • 字节码
          • 总结
          • 个人简介
          相关产品与服务
          云数据库 MySQL
          腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档