Java 下 String、StringBuffer、StringBuilder 分析
String、StringBuffer、StringBuilder 常用于字符串处理,但是适用的场景各有不同。
先说结论。
共性:
- 都是处理字符串的类。
- 三者都是 final 类,不允许被继承。生成的对象不可变更。
区别:
类 | 内部数据(char)变更 | 线程安全性 | 说明 |
---|---|---|---|
String | 数据不可变更 | 安全 | 数据本身是 final 封装的,对象本身不可改变,所以线程安全 |
StringBuffer | 数据可变更 | 安全 | 数据虽然可以变更,但是对外接口都加了同步锁,所以线程安全 |
StringBuilder | 数据可变更 | 不安全 | 数据可以变更,但是接口没有上锁,线程不安全 |
字符串常量池
字符串常量池 (String pool, String intern pool, String 保留池) 是 Java 堆内存中一个特殊的存储区域。
字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM 为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。
- 为了减少在 JVM 中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM 会首先检查字符串常量池。
- 如果字符串已经存在池中,就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。
Java 能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享。
如代码:
String str1 = "Hello";
String str2 = "Hello";
String
String 类型是为了利用已有的字符串数据(利用字符串常量池),降低资源消耗(heap 空间),需要 String 为 final 不可变的对象。
另外由于 String 对象不可变更,所以其 HashCode 只需生成一次,也就避免了后续的重新计算,提高了性能。
同时数据不可变更,也就保证了多线程访问的安全性。
缺点:
大量数据的操作是,会频繁生成 String 对象(改变对象引用),耗时严重。
StringBuffer
StringBuffer 对象是 final 的,但其对象的数据本身可以通过本身的接口,进行修改(修改本身而不是生成对象改变引用)。
StringBuffer 本身由于数据可变更,为了保证多线程访问下的安全性,接口都用 synchronized 加入同步锁保证线程安全。
因此在单线程下通过 StringBuffer 处理大量数据,也就有不必要的同步锁开销。
StringBuilder
StringBuilder 与 StringBuffer 类似,只是其接口不会加入任何线程安全的锁操作,显然是线程不安全的。
缺点:
多线程下操作线程不安全,需要手动加锁。
总结
不同场景下,建议使用不同的字符串处理对象。
String:少量数据的操作。
StringBuffer:多线程下操作大量数据。
StringBuilder:单线程下操作大量数据。