《Java 核心技术面试》课程笔记(五)

String、StringBuffer、StringBuilder 有什么区别?

典型回答

  • String 是 Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。它是
    典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。
  • StringBuffer 是为解决上面提到拼接产生太多中间对象的问题而提供的一个类。
    • 我们可以用 append 或者 add 方法,把字符串添加到已有序列的末尾或者指定位置。
    • StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销。
    • 除非有线程安全的需要,不然还是推荐使用它的后继者,也就是 StringBuilder。
  • StringBuilder 是 Java 1.5 中新增的,在能力上和 StringBuffer 没有本质区别,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选。

考点分析

  • 几乎所有的应用开发都离不开操作字符串,理解字符串的设计和实现以及相关工具如拼接类的使用,对写出高质量代码是非常有帮助的。
  • 面试官可以从各种不同的角度考察,比如可以:
    • 通过 String 和相关类,考察基本的线程安全设计与实现,各种基础编程实践。
    • 考察 JVM 对象缓存机制的理解以及如何良好地使用。
    • 考察 JVM 优化 Java 代码的一些技巧。
    • String 相关类的演进,比如 Java 9 中实现的巨大变化。

知识扩展

  • 字符串设计和实现考量
    • String 是 Immutable 类的典型实现,原生的保证了基础线程安全,因为你无法对它内部数据进行任何修改,这种便利甚至体现在拷贝构造函数中,由于不可变,Immutable 对象在拷贝时不需要额外复制数据。
    • StringBuffer 的线程安全是通过把各种修改数据的方法都加上 synchronized 关键字实现的,非常直白。
    • 为了实现修改字符序列的目的,StringBuffer 和 StringBuilder 底层都是利用可修改的(char,JDK 9 以后是 byte)数组,二者都继承了 AbstractStringBuilder,里面包含了基本操作,区别仅在于最终的方法是否加了 synchronized。
    • 这个内部数组应该创建成多大的呢?
      • 如果太小,拼接的时候可能要重新创建足够大的数组;
      • 如果太大,又会浪费空间。
      • 目前的实现是,构建时初始字符串长度加 16(这意味着,如果没有构建对象时输入最初的字符串,那么初始值就是 16)。
      • 我们如果确定拼接会发生非常多次,而且大概是可预计的,那么就可以指定合适的大小,避免很多次扩容的开销。
      • 扩容会产生多重开销,因为要抛弃原有数组,创建新的(可以简单认为是倍数)数组,还要进行 arraycopy。
    • 非静态的拼接逻辑在 JDK 8 中会自动被 javac 转换为 StringBuilder 操作;而 Java 9 利用 InvokeDynamic,将字符串拼接的优化与 javac 生成的字节码解耦,假设未来 JVM 增强相关运行时实现,将不需要依赖 javac 的任何修改。

      在日常编程中,保证程序的可读性、可维护性,往往比所谓的最优性能更重要,你可以根据实际需求酌情选择具体的编码方式。

  • 字符串缓存
    • 把常见应用进行堆转储(Dump Heap),然后分析对象组成,会发现平均 25% 的对象是字符串,并且其中约半数是重复的。如果能避免创建重复字符串,可以有效降低内存消耗和对象创建开销。
    • String 在 Java 6 以后提供了 intern() 方法,目的是提示 JVM 把相应字符串缓存起来,以备重复使用。
      • 在我们创建字符串对象并调用 intern() 方法的时候,如果已经有缓存的字符串,就会返回缓存里的实例,否则将其缓存起来。
      • 一般来说,JVM 会将所有的类似“abc”这样的文本字符串,或者字符串常量之类缓存起来。
      • Intern 是一种显式地排重机制,但是它也有一定的副作用,因为需要开发者写代码时明确调用,一是不方便,每一个都显式调用是非常麻烦的;另外就是我们很难保证效率,应用开发阶段很难清楚地预计字符串的重复情况,有人认为这是一种污染代码的实践。
      • 在 Oracle JDK 8u20 之后,推出了一个新的特性,也就是 G1 GC 下的字符串排重。它是通过将相同数据的字符串指向同一份数据来做到的,是 JVM 底层的改变,并不需要 Java 类库做什么修改。
    • 在运行时,字符串的一些基础操作会直接利用 JVM 内部的 Intrinsic 机制,往往运行的就是特殊优化的本地代码,而根本就不是 Java 代码生成的字节码。Intrinsic 可以简单理解为,是一种利用 native 方式 hard-coded 的逻辑,算是一种特别的内联,很多优化还是需要直接使用特定的 CPU 指令。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值