关于String、StringBuilder、StringBuffer的使用

  • 背景
  • 结论
  • 原理
  • 概述
  • 用法
  • 区别
  • 特殊情况
  • 引用


背景

今天在刷leetcode的时候做了一道题,对代码的内存优化产生了兴趣,于是去百度自行学习了一下,其中谈到String、StringBuilder和StringBuffer的区别和使用,在此记录。

结论

(1)基本原则:
如果要操作少量的数据,用String ;
单线程操作大量数据,用StringBuilder ;
多线程操作大量数据,用StringBuffer。

(2)不要频繁使用String类的"+"来进行拼接,那样性能极差,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。

(3)为了获得更好的性能,在构造 StringBuffer 或 StringBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能。

(4)StringBuilder 一般使用在方法内部来完成类似 + 功能,因为是线程不安全的,所以用完以后可以丢弃。
StringBuffer 主要用在全局变量中。

(5)相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多线程模式下,才可以采用 StringBuilder;否则还是用 StringBuffer。

原理

概述

String是不可变的字符串,它的底层是一个用final修饰的字符数组,String 对象赋值之后就会在字符串常量池中缓存,如果下次创建会判定常量池是否已经有缓存对象,如果有的话直接返回该引用给创建者。

StringBuffer(Synchronized,即线程安全)可变字符序列(字符串变量)。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。大多方法与String相同。

StringBuilder 可变字符序列。线程不安全,执行效率高。大多方法与StringBuffer相同。

用法

String的用法就不再赘述,这里讲一下另外两个的用法。

StringBuffer:
构造器介绍:StringBuffer对象必须要由构造器来生成

StringBuffer():初始容量为16的字符串缓冲区,开辟了16位的字符数组。
StringBuffer(int size):构造指定容量的字符串缓冲区。
StringBuffer(String str):将内容初始化为指定字符串内容。

常用方法:
在StringBuffer类中包含了各种对字符串的增删改查方法:
append(Object obj):追加。
insert(int offset, Object obj):插入指定的对象到指定位置(对obj使用valueOf()方法先转字符串)
length():获取数组中已有字符的个数。
toString():返回的是一个String字符串。
deleteCharAt(int index):删除指定位置的字符。
setCharAt(int index, char ch):修改指定位置的字符。
charAt(int index):查询指定位置的字符。
StringBuilder:
与StringBuffer提供的方法基本一致。

区别

效率排名:String<StringBuffer<StringBuilder

String:由于本身是不可变的字符序列,所以每次使用字符串的拼接都需要重新创建内存空间(特例在下面),效率大大减低,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。
StringBuffer:可变的字符序列,使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用,是线程安全的,效率会略微慢些。
StringBuilder:可变的字符序列,非线程安全的,效率最高。

特殊情况

在某些特别情况下, String 对象的字符串拼接其实是被 Java Compiler 编译成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,

String s1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);

这里生成 String s1 对象的速度并不比 StringBuffer 慢。java存在常量优化机制,其实在 Java Compiler 里,自动做了如下转换:

编译时会直接将s1的This is only a simple test当做一个String在常量池中查找并创建

String s1 = “This is only a simple test”;

所以速度很快。但要注意的是,如果拼接的字符串来自另外的 String 对象的话,Java Compiler 就不会自动转换了,速度也就没那么快了,例如:

String s2 = “This is only a”;  
String s3 = “ simple”;  
String s4 = “ test”;  
String s1 = s2 + s3 + s4;

这时候,Java Compiler 会规规矩矩的按照原来的方式去做,String 的 concatenation(即+)操作利用了 StringBuilder(或StringBuffer)的append 方法实现,此时,对于上述情况,若 s2,s3,s4 采用 String 定义,拼接时需要额外创建一个 StringBuffer(或StringBuilder),之后将StringBuffer 转换为 String,若采用 StringBuffer(或StringBuilder),则不需额外创建 StringBuffer。