Java String、StringBuilder、StringBuffer类设计思路、实现及最佳实践

Java String、StringBuilder、StringBuffer类设计思路、实现及最佳实践

本文从源码、JVM运行模型入手,分析String类设计思路及具体实现,扩展分析StringBuilder、StringBuffer类,最后总结一些最佳实践。

预备知识

JVM相关内存区域

String类

主要源码

public final class String
  implements Serializable, Comparable<String>, CharSequence
{
  private final char[] value;
  private int hash;
  private static final long serialVersionUID = -6849794470754667710L;
  private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
  public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(null);
  ...
}

设计思路及实现

不可变

为什么设计为不可变?

  1. 不可变对象可以共享底层数据,从而节省堆空间。一般系统在运行时创建最多的对象就是String对象,如果JVM运行时每当遇到创建String对象指令就在JVM堆上创建一个完整对象,将占据大量堆空间。因此,Java在String类对象不可变的前提下,在String对象初始化阶段做了相关优化,稍后描述。
  2. 减少GC次数,提高系统稳定性。减少堆空间占用,相应地会减少触发GC的次数。

如何实现?

  1. 语法层面,String类被final关键字修饰,并且它的成员变量value被final修饰,String对象一旦创建就不能再修改。

在Java中,被final类型修饰的类不允许被其他类继承,被final修饰的变量赋值后不允许被修改

  1. JVM层面,在语法层面保证String对象不可变的前提下,JVM在创建String对象时做了相关优化,即,相同的String对象共享相同的value数组。举例如下:
public class NewStringDemo {
	public static void main(String[] args) {
		String a = new String("hello world");
		String b = new String("hello world");
		String c = new String("hello world");
		System.out.println(c);//此处断点后,调试运行至此
	}
}

在JDK1.8版本下,调试运行至断点处,通过命令:

cd %JAVA_HOME%/lib
java -cp ./sa-jdi.jar sun.jvm.hotspot.HSDB

启动HSDB,分析该进程虚拟机堆内存分布:
String a 对应的value数组地址
String b 对应的value数组地址
String c 对应的value数组地址
由此可见,虽然new了3个String对象,但他们共享了同一个char数组,节约了堆上空间。

StringBuilder

String对象一旦创建则不可变,那在遇到需要拼接字符串的场景时应该怎么办呢,一个比较笨的办法是创建多个String对象,经过累加后,再赋值给一个新的String对象,例如:

String hw = new String("hello") + new String("world");

此过程将产生多个String对象,代价较大。Java为字符串更新场景提供了专用类StringBuilder,可使用下面的方式替代创建多个String对象的方案,性能更优:

String hw = new StringBuilder("hello").append("world").toString();

特别是在大量字符串拼接场景,使用StringBuilder的性能比使用String好一个数量级。StringBuilder在调用toString方法前仅在不断操作char[] value数组,调用toString方法后才真正通过copy char[] 创建一个新的String对象。

StringBuffer

StringBuilder专门设计来用于字符串拼接场景,但它不是线程安全的。在多线程并发场景,需要选用StringBuffer,它基于AbstractStringBuilder设置了一个char[] toStringCache数组,用于暂存多线程场景下当前的字符串结果,其append、toString等方法均加了synchronized关键字,以此保证线程安全。

最佳实践

  • 单线程,在需要更新字符串值的场景下,使用StringBuilder。
  • 多线程,在需要更新字符串值的场景下,使用StringBuffer。
  • 在不需要更新字符串值的场景,才使用String。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值