String,StringBuffer,StringBuilder有什么区别?
文章目录
一、考点分析及深入探讨
-
通过String和相关类,考察基本的线程安全设计和实现,各种基础编程实践
-
考察JVM对象缓存机制的理解以及如何良好地使用
-
考察JVM优化Java代码的一些技巧
-
String相关类的演进,比如Java9中实现的巨大变化
。。。
二、概念
1、String
特点:
-
String是典型的immutable类,被声明为final class,所有属性都是final的。
-
由于他的不可变性,类似拼接,裁剪字符串等动作,都会产生新的String对象
-
String保证了基础线程安全,因为你无法对它内部数据进行任何修改,这种便利甚至体现在拷贝构造函数中,由于不可变,Immutable对象在拷贝时不需要额外复制数据。
-
String操作不当可能会产生大量临时字符串。
应用:
- 在字符串内容不经常发生变化的业务场景优先使用String类.例如:常量声明、少量的字 符串拼接操作等。如果有大量的字符串内容拼接, 避免使用String与String之间的 “+” 操 作。因为这样会产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)
2、StringBuffer
特点:
- StringBuffer是为了解决上面提到拼接产生太多中间对象的问题提供的一个类
- 可以用append或者add方法,把字符串添加到已有序列的末尾或者指定位置
- StringBuffer本质是一个线程安全的可修改字符序列 ,通过把各种修改数据的方法都加上synchronized关键字实现。它保证了线程安全,也随之带来了额外的性能开销。
- 所以除非有线程安全的需要,不然还是推荐使用StringBuilder。
应用:
- 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程环境下。建议使 用StringBuffer ,例如XML解折、HTTP参数解析与封装。
备注:
- 不必纠结于synchronized性能之类的,有人说“过早优化是万恶之源”,考虑可靠性,正确性和代码可读性才是大多数应用开发最重要的因素
3、StringBuilder
特点:
- StringBuilder在能力上和StringBuffer没有本质的区别,但是它去掉了线程安全的部分,有效减少了开销,是绝大部分情况下进行字符串拼接的首选。
应用:
- 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使 用StringBuilder, 例如SQL语句拼装、JSON封装等。
三、实现
1、如何修改字符序列
- 为了实现修改字符序列的目的,StringBuffer和StringBuilder底层都是利用可修改的数组,二者继承了AbstractStringBuilder。区别在于是否加了synchronized
- 这个内部数组应该创建多大的呢?如果太小,拼接的时候可能要重新创建足够大的数组;如果太大,优惠浪费空间。目前的实现是,构建时初始字符串长度加16(即最小初始值为16)。
- 我们如果确定拼接会发生非常多次,而且大概是可预计的,那么就可以指定合适的大小,避免很多次扩容的开销。
- 扩容会产生多重开销,因为要抛弃原有数组,创建新的数组,还要进行arrayCopy
2、字符串缓存
Ⅰ、 intern()显式排重机制
特点:
-
String在Java 6以后提供了 intern()方法,目的是提示JVM把相应字符串缓存来,以备重 复使用 。在创建字符串对象并调用intern()方法的时候,如果已经有缓存的字符串,就会 返回缓存里的实例,否则将其缓起来。
-
一般来说, JVM会将所有的类似“abc"这样的文本字 符串,或者字符串常量缓存起来 ,但是实际情况让你大跌眼镜。Java6这种历史版本不推荐大量使用intern(),
原因是**被缓存的字符串存在所谓的PermGen(永久代)**里 的,这个空间是很有限的,也基本不会被FullGC之外的垃圾收集照顾到。所以如果使用不当,就会出现OOM。
-
在后序版本中,这个缓存被放置在堆中,这样就极大避免了永久代占满的问题,甚至永久代在 JDK8 被MetaSpace(元数据区)替代了。
-
Intern是一种显式排重机制,但是它也有一定的副作用,因为需要开发者写代码时明确调 用,一是不方便,每一个显式调用是非常麻烦的;另外就是我们很难保证效率。应用开发阶段 很难淸楚地预计字符串的重复情况,有人认为这是染代码的实践。
改进:
-
JDK8之后,推出了一个新的特性,也就是G1 GC下的字符串排重.它是通过将相同数据的字符串指向同一份数据来做到的,是JVM底层的改变,并不需要Java类库
做什么修改
-
注意G1 GC这个功能目前是默认关闭的,你需要使用下面参数开启,并且记指定使用G1 GC
: -XX:+UseStringDeduplication
Ⅱ、Intrinsic:
- 前面说到的几个方面.只是Java底层对字符串各种优化的一角,在运行时,字符串的 操作会直剧用JVM内部的Intrinsic机制,往往运行的就是特殊优化的本地代码,而根本就不 是Java代码生成的字节码。Intrinsic可以简单理解为,是一种利用native方式hard-coded 的逻辑,算是一种特别的内联。很多优化还是需要直接使用特定的CPU指令,具体可以看相关 源码。
3、String自身的演化
-
如果你仔细观察过Java的字符串,在历史版本中,它是使用char数组来数据的,这样非常 直接。但是Java中的char是两个bytes大小,拉丁语系语言的字符,根本就不需要太宽的 char,这样无区别的实现就造成了一定的浪费。密度是编程语言平台永恒的话题,因为归根结底 绝大部分任务是要来操作数据的.
-
在Java 6就提供了压缩字符串的特性,但是这个特性的实现并不是开 源的,而且在实践中也暴露出了一些问题,所以在最新的JDK版本中已经将它移除了。
-
在Java 9中,我们引入了 Compact Strings的设计,对字符串进行了大刀阔斧的改进,将数据 存储方式从char数组,改变为一个byte数组加上一个标识编码的所谓coder,并且将相关字符串操作类进行了修改。
-
在通用的性能测试和产品实验中,我们能非常明显地看到紧凑字符串带来的优势,即更小的内存 占用、更快的操作速度。
4、备注:
- “可以看出,仅仅是字符串一个实现,就需要Java平台工程师和科学家付出如此大且默默无闻的 努力,我们得到的很多便利都是来源于此”
- 我想说的是,对技术要永葆好奇心,并保持谦虚学习的心态,这是对科学家们最好的尊重和敬畏。
四、网友问答
1、网友1
- String
- StringBuffer/StringBuilder
五、参考文档
- 极客时间第五讲:String、StringBuffer、StringBuilder有什么区别?