String、StringBuilder、StringBuffer的区别

面试官可以考察的内容

如果继续深入,面试官可以从各种不同的角度考察,比如可以:

  • 通过 String 和相关类,考察基本的线程安全设计与实现,各种基础编程实践。
  • 考察 JVM 对象缓存机制的理解以及如何良好地使用。
  • 考察 JVM 优化 Java 代码的一些技巧。
  • String 相关类的演进,比如 Java 9 中实现的巨大变化。...

String

String、StringBuilder、StringBuffer的区别

  • 虚拟机的特殊处理

    • String类是Java里面一个比较特殊的类,因为String类使用的非常频繁,所以虚拟机维护了一个字符串常量池,String类可以像基本类型那样,以字面量的方式创建字符串,也可以使用new的方式创建一个字符串对象。如果字面量的方式,会先从常量池中获取,如果常量池没有的话,再去创建一个字符串对象,并将字符串的引用保存在字符串常量池里。
    • StringBuilder、StringBuffer就是普通的类。
  • 可变性

    • String类是不可变的,满足了不可变类的定义(详细见下一个问题),对String进行拼接、或者trim()方法去掉左右空格时,都会产生一个新的字符串对象。
    • StringBuilder和StringBuffer都是可变的,都继承AbstractStringBuilder类,和String类一样,底层也是使用字符数组来保存字符串,不过没有使用final和private关键字修饰,并且提供了修改字符串的方法。比如:append、insert等,都是直接在原对象上进行操作。
  • 线程安全性

    • String对象不可变,线程操作某个String对象,并不能修改其内容,所以多线程的情况下,不需要做同步,就能获取到正确的结果,所以是线程安全的(参考文章:为什么String类不可变,就是线程安全的?
    • StringBuffer中操作字符串的方法都加了同步锁,也是线程安全的
    • StringBuilder没有对操作字符串的方法加同步锁,所以是非线程安全的
  • 性能

    • String对象每次进行改变的时候,都会生成一个新的对象,所以比较适合于字符串少量操作的情况,否则会在堆区生成大量的字符串
    • StringBuffer因为加了同步锁,所以性能上比StringBuilder稍差一些。在单线程操作大量字符串的情况,比较适合使用StringBuilder;多线程操作大量字符串比较适合使用StringBuffer。

String为什么不可变

String类满足了不可变类的定义。

  • 用final关键修饰,所以String类不会被继承,避免了子类破坏String类的不可变性
  • 保存字符串的数组用final修饰并且是私有的,而且String类没有提供修改这个字符串的方法,所以外部类也无法访问和修改。

不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并且在对象的整个生命周期内固定不变。为了使类不可变,要遵循下面五条规则:

1. 不要提供任何会修改对象状态的方法。

2. 保证类不会被扩展。一般的做法是让这个类成为 final的,防止子类化,破坏该类的不可变行为。

3. 使所有的域都是 final 的。

4. 使所有的域都成为私有的。防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象。

5. 确保对于任何可变性组件的互斥访问。 如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。

Java 9 为何要将 String的底层实现由char[]改成了byte[] ?

新版的 String 其实支持两个编码方案:Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),byte 相较 char 节省一半的内存空间。

JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。

如果字符串中包含的汉字超过 Latin-1 可表示范围内的字符,bytechar 所占用的空间是一样的。

这是官方的介绍:openjdk.java.net/jeps/254

字符串拼接,用+ 还是StringBuilder

Java 语言本身并不支持运算符重载(?),"+"和"+="是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。

java 复制代码
String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;

上面的代码对应的字节码如下:

可以看出,字符串对象通过"+"的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

不过,在for循环内使用"+"进行字符串的拼接的话,存在比较明显的缺陷:StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对象。编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象

不过,使用 "+" 进行字符串拼接会产生大量的临时对象的问题在 JDK9 中得到了解决。在 JDK9 当中,字符串相加 "+" 改为了用动态方法 makeConcatWithConstants() 来实现,而不是大量的 StringBuilder 了。这个改进是 JDK9 的 JEP 280open in new window

字符串常量池和intern方法

详见文章:JVM:字符串常量池

String 类型的变量和常量做"+"运算时发生了什么。

java 复制代码
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";  //常量池中的对象
String str4 = str1 + str2;  //在堆上创建的新的对象
String str5 = "string";  //常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false

对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。

参考文章

  1. JavaGuide: Java基础常见面试题总结(中)
  2. 面试题系列第2篇:new String()创建几个对象?
相关推荐
原机小子1 小时前
SpringBoot在线教育系统:从零到一的构建过程
数据库·spring boot·后端
2401_857439691 小时前
SpringBoot在线教育平台:设计与实现的深度解析
java·spring boot·后端
总是学不会.1 小时前
SpringBoot项目:前后端打包与部署(使用 Maven)
java·服务器·前端·后端·maven
凡人的AI工具箱4 小时前
15分钟学 Python 第38天 :Python 爬虫入门(四)
开发语言·人工智能·后端·爬虫·python
码农超哥同学4 小时前
Python知识点:在Python编程中,如何使用Gensim进行主题建模
开发语言·python·面试·编程
丶21364 小时前
【SQL】深入理解SQL:从基础概念到常用命令
数据库·后端·sql
木子02044 小时前
Nacos的应用
后端
哎呦没4 小时前
Spring Boot框架在医院管理中的应用
java·spring boot·后端
陈序缘5 小时前
Go语言实现长连接并发框架 - 消息
linux·服务器·开发语言·后端·golang
络75 小时前
Spring14——案例:利用AOP环绕通知计算业务层接口执行效率
java·后端·spring·mybatis·aop