字符串,就是一系列字符的集合。

Java里面提供了String,StringBuffer和StringBuilder三个类来封装字符串,其中StringBuilder类是到jdk 1.5才新增的。字符串操作可以说是几乎每门编程语言中所必不可少的,你真的理解其内幕吗?

下面让我们开始探秘之旅吧!

1、既然都是用来封装字符串的,那为什么还要3个类来封装呢?

2、它们三者之间到底有何区别?

3、它们三者之间的使用场景分别是什么?

4、它们三者之间从内存角度来看又是怎么来实现的呢?

5、它们三者之间的性能效率是怎么排列的?

下面就让我们逐一破解这几个谜团吧!

一、概述

字符串是由若干个字符线性排列组成的,所以我们可以把字符串当作数组(Array)来看待。众所周知,数组就是内存里线性排列的有序地址空间块。既然是线性排列,有序的一组地址块,那么在分配数组空间时就必须指定空间大小也就是数组大小,这也是大多数编程语言里规定在定义数组时要指定数组大小的原因( 某些编程语言中可变数组的实现另当别论 )。换言之,
数组就分为可变数组和不可变数组。可变数组能够动态插入和删除,而不可变数组一旦分配好空间后则不能进行动态插入或删除操作。

二、从实际应用可能的场景中分析String,StringBuilder,StringBuffer产生的背景

在实际应用当中我们可能会对字符串经常做如下几种操作:插入,删除,修改,拼接,截取,查到,替换……其中,“插入”和“删除”操作就涉及到对 原字符串的长度 进行修改( 其实,“拼接”和“截取”也分为可以理解为插入和删除操作 )。

然而,在jdk 1.0中就出现的 String类封装的字符串是不可变的 ,即不能在原字符串上进行 插入 和删除 操作,String的API是通过新建临时变量的方式来实现字符串的插入和删除操作。因为是新建临时变量,所以
当你调用String类的API对字符串进行插入和删除操作时原来的字符串是不会有任何改变的,返回给你的是一个新的字符串对象 。关于这一点,我们将在后面通过分析源码的方式来证实!既然String类封装的是不可变数组,那么对应的就应该有一个类来封装可变数组。没错! StringBuffer类封装的就是可变数组,并且还是线程安全的。 所以,在非多线程环境下效率相对较低。正如大家所想的一样,JDK里也提供了非多线程环境下使用的可变字符串的封装类,它就是在jdk 1.5里面才姗姗来迟的StringBuilder类, StringBuilder类是非线程安全的可变字符串封装类 ,也是我们今天要讨论的成员之一。

到此,我们基本上就已经回答了在文章开始处提出的第1,2,3个问题。接下来,我们再从源码的角度来更深入讨论。

三、从源码角度进一步探讨其内部实现方式

1、String类的关键源码分析如下:

  1. public final class String
  2. implements java.io.Serializable, Comparable<String>, CharSequence {
  3. /** The value is used for character storage. */
  4. private final char value[];//final类型char数组
  5. //省略其他代码……
  6. ……
  7. }

从上述的代码片段中我们可以看到,String类在类开始处就定义了一个final 类型的char数组value。也就是说通过 String类定义的字符串中的所有字符都是存储在这个final
类型的char数组中的。

下面我们来看一下String类对字符串的截取操作,关键源码如下:

  1. public String substring(int beginIndex) {
  2. if (beginIndex < 0) {
  3. throw new StringIndexOutOfBoundsException(beginIndex);
  4. }
  5. int subLen = value.length - beginIndex;
  6. if (subLen < 0) {
  7. throw new StringIndexOutOfBoundsException(subLen);
  8. }
  9. //当对原来的字符串进行截取的时候(beginIndex >0),返回的结果是新建的对象
  10. return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
  11. }

当我们对字符串从第beginIndex(beginIndex >0) 个字符开始进行截取时,返回的结果是重新new出来的对象。所以,在对String类型的字符串进行大量“插入”和“删除”操作时会产生大量的临时变量。

2、StringBuffer和StringBuilder类关键源码分析:

在进行这两个类的源码分析前,我们先来分析下一个抽象类AbstractStringBuilder,因为,StringBuffer和StringBuilder都继承自这个抽象类,即AbstractStringBuilder类是StringBuffer和StringBuilder的共同父类。AbstractStringBuilder类的关键代码片段如下:

  1. abstract class AbstractStringBuilder implements Appendable, CharSequence {
  2. /**
  3. * The value is used for character storage.
  4. */
  5. char[] value;//一个char类型的数组,非final类型,这一点与String类不同
  6. /**
  7. * This no-arg constructor is necessary for serialization of subclasses.
  8. */
  9. AbstractStringBuilder() {
  10. }
  11. /**
  12. * Creates an AbstractStringBuilder of the specified capacity.
  13. */
  14. AbstractStringBuilder(int capacity) {
  15. value = new char[capacity];//构建了长度为capacity大小的数组
  16. }
  17. //其他代码省略……
  18. ……
  19. }

StringBuffer类的关键代码如下:

  1. public final class StringBuffer
  2. extends AbstractStringBuilder
  3. implements java.io.Serializable, CharSequence
  4. {
  5. /**
  6. * Constructs a string buffer with no characters in it and an
  7. * initial capacity of 16 characters.
  8. */
  9. public StringBuffer() {
  10. super(16);//创建一个默认大小为16的char型数组
  11. }
  12. /**
  13. * Constructs a string buffer with no characters in it and
  14. * the specified initial capacity.
  15. *
  16. * @param capacity the initial capacity.
  17. * @exception NegativeArraySizeException if the {@code capacity}
  18. * argument is less than {@code 0}.
  19. */
  20. public StringBuffer(int capacity) {
  21. super(capacity);//自定义创建大小为capacity的char型数组
  22. }
  23. //省略其他代码……
  24. ……

StringBuilder类的构造函数与StringBuffer类的构造函数实现方式相同,此处就不贴代码了。

下面来看看StringBuilder类的append方法和insert方法的代码,因StringBuilder和StringBuffer的方法实现基本上一致,不同的是StringBuffer类的方法前多了个synchronized关键字,即StringBuffer是线程安全的。所以接下来我们就只分析StringBuilder类的代码了。StringBuilder类的append方法,insert方法都是Override
父类AbstractStringBuilder的方法,所以我们直接来分析AbstractStringBuilder类的相关方法。

  1. public AbstractStringBuilder append(String str) {
  2. if (str == null)
  3. return appendNull();
  4. int len = str.length();
  5. //调用下面的ensureCapacityInternal方法
  6. ensureCapacityInternal(count + len);
  7. str.getChars(0, len, value, count);
  8. count += len;
  9. return this;
  10. }
  11. private void ensureCapacityInternal(int minimumCapacity) {
  12. // overflow-conscious code
  13. if (minimumCapacity - value.length > 0)
  14. //调用下面的expandCapacity方法实现“扩容”特性
  15. expandCapacity(minimumCapacity);
  16. }
  17. /**
  18. * This implements the expansion semantics of ensureCapacity with no
  19. * size check or synchronization.
  20. */
  21. void expandCapacity(int minimumCapacity) {
  22. //“扩展”的数组长度是按“扩展”前数组长度的2倍再加上2 byte的规则来扩展
  23. int newCapacity = value.length * 2 + 2;
  24. if (newCapacity - minimumCapacity < 0)
  25. newCapacity = minimumCapacity;
  26. if (newCapacity < 0) {
  27. if (minimumCapacity < 0) // overflow
  28. throw new OutOfMemoryError();
  29. newCapacity = Integer.MAX_VALUE;
  30. }
  31. //将value变量指向Arrays返回的新的char[]对象,从而达到“扩容”的特性
  32. value = Arrays.copyOf(value, newCapacity);
  33. }

从上述代码分析得出,StringBuilder和StringBuffer的append方法“扩容”特性本质上是通过调用Arrays类的copyOf方法来实现的。接下来我们顺藤摸瓜,再分析下Arrays.copyOf(value,
newCapacity)这个方法吧。代码如下:

  1. public static char[] copyOf(char[] original, int newLength) {
  2. //创建长度为newLength的char数组,也就是“扩容”后的char 数组,并作为返回值
  3. char[] copy = new char[newLength];
  4. System.arraycopy(original, 0, copy, 0,
  5. Math.min(original.length, newLength));
  6. return copy;//返回“扩容”后的数组变量
  7. }

其中,insert方法也是调用了expandCapacity方法来实现“扩容”特性的,此处就不在赘述了。

接下来,分析下delete(int start, int end)方法,代码如下:

  1. public AbstractStringBuilder delete(int start, int end) {
  2. if (start < 0)
  3. throw new StringIndexOutOfBoundsException(start);
  4. if (end > count)
  5. end = count;
  6. if (start > end)
  7. throw new StringIndexOutOfBoundsException();
  8. int len = end - start;
  9. if (len > 0) {
  10. //调用native方法arraycopy对value数组进行复制操作,然后重新赋值count变量达到“删除”特性
  11. System.arraycopy(value, start+len, value, start, count-end);
  12. count -= len;
  13. }
  14. return this;
  15. }

从源码可以看出delete方法的“删除”特性是调用native方法arraycopy对value数组进行复制操作,然后重新赋值count变量实现的

最后,来看下substring方法,源码如下 :

  1. public String substring(int start, int end) {
  2. if (start < 0)
  3. throw new StringIndexOutOfBoundsException(start);
  4. if (end > count)
  5. throw new StringIndexOutOfBoundsException(end);
  6. if (start > end)
  7. throw new StringIndexOutOfBoundsException(end - start);
  8. //根据start,end参数创建String对象并返回
  9. return new String(value, start, end - start);
  10. }

四、总结:

1、String类型的字符串对象是不可变的,一旦String对象创建后,包含在这个对象中的字符系列是不可以改变的,直到这个对象被销毁。

2、StringBuilder和StringBuffer类型的字符串是可变的,不同的是StringBuffer类型的是线程安全的,而StringBuilder不是线程安全的

3、如果是多线程环境下涉及到共享变量的插入和删除操作,StringBuffer则是首选。如果是非多线程操作并且有大量的字符串拼接,插入,删除操作则StringBuilder是首选。毕竟String类是通过创建临时变量来实现字符串拼接的,耗内存还效率不高,怎么说StringBuilder是通过JNI方式实现终极操作的。

4、StringBuilder和StringBuffer的“可变”特性总结如下:

(1)append,insert,delete方法最根本上都是调用System.arraycopy()这个方法来达到目的

(2)substring(int, int)方法是通过重新new String(value, start, end - start)的方式来达到目的。因此,在执行substring操作时,StringBuilder和String基本上没什么区别。

Java基础学习总结(65)——Java中的String,StringBuilder和StringBuffer比较的更多相关文章

  1. Java基础学习笔记七 Java基础语法之继承和抽象类

    继承 继承的概念 在现实生活中,继承一般指的是子女继承父辈的财产.在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系. 例如公司中的研发部员工和维护部员工都属于员工, ...

  2. java基础学习03(java基础程序设计)

    java基础程序设计 一.完成的目标 1. 掌握java中的数据类型划分 2. 8种基本数据类型的使用及数据类型转换 3. 位运算.运算符.表达式 4. 判断.循环语句的使用 5. break和con ...

  3. java基础学习总结一(java语言发展历史、jdk的下载安装以及配置环境变量)

    最近一段时间计划复习一下java基础知识,使用的视频课程是尚学堂高淇老师的,上课过程中的心得体会直接总结一下,方便以后复习. 一:计算机语言的发展 1:机器语言,最原始的语言,主要有“01”构成,最早 ...

  4. Java基础学习笔记一 Java介绍

    java语言概述 Java是sun公司开发的一门编程语言,目前被Oracle公司收购,编程语言就是用来编写软件的. Java的应用 开发QQ.迅雷程序(桌面应用软件) 淘宝.京东(互联网应用软件) 安 ...

  5. Java基础学习笔记三 Java基础语法

    Scanner类 Scanner类属于引用数据类型,先了解下引用数据类型. 引用数据类型的使用 与定义基本数据类型变量不同,引用数据类型的变量定义及赋值有一个相对固定的步骤或格式. 数据类型 变量名 ...

  6. Java基础学习笔记四 Java基础语法

    数组 数组的需求 现在需要统计某公司员工的工资情况,例如计算平均工资.最高工资等.假设该公司有50名员工,用前面所学的知识完成,那么程序首先需要声明50个变量来分别记住每位员工的工资,这样做会显得很麻 ...

  7. Java基础学习笔记九 Java基础语法之this和super

    构造方法 我们对封装已经有了基本的了解,接下来我们来看一个新的问题,依然以Person为例,由于Person中的属性都被private了,外界无法直接访问属性,必须对外提供相应的set和get方法.当 ...

  8. JAVA基础学习——1.0 Java概述

    Java语言 SUN公司  1995年推出的高级编程语言 ■  主要应用方向 Web开发和Android开发 ■  主要特点 平台无关性:能运行于不同的平台上    安全性:去掉了指针操作,内存由操作 ...

  9. Java基础学习笔记十 Java基础语法之final、static、匿名对象、内部类

    final关键字 继承的出现提高了代码的复用性,并方便开发.但随之也有问题,有些类在描述完之后,不想被继承,或者有些类中的部分方法功能是固定的,不想让子类重写.可是当子类继承了这些特殊类之后,就可以对 ...

随机推荐

  1. JSON以及Java转换JSON的方法(前后端经常使用处理方法)

    本文主要讲述例如以下几个内容: 1.JSON定义以及JSON的特性 2.怎样在JavaScript中解释JSON格式数据 3.怎样在Java代码中使用JSON(讲对象转换成JSON对象以及解释JSON ...

  2. HDU3535 AreYouBusy 混合背包

    题目大意 给出几组物品的体积和价值,每组分为三种:0.组内物品至少选一个:1.组内物品最多选一个:2.组内物品任意选.给出背包容量,求所能得到的最大价值. 注意 仔细审题,把样例好好看完了再答题,否则 ...

  3. The Unique MST--hdoj

    The Unique MST Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 20000/10000K (Java/Other) Tota ...

  4. 2017-3-13 leetcode 4 11 15

    ji那天居然早起了,惊呆我了,眼睛有点儿疼,一直流泪....继续保持 ========================================================== leetco ...

  5. MyBatis的关联映射和动态SQL

    CREATE TABLE tb_card ( id INT PRIMARY KEY AUTO_INCREMENT, CODE ) ); '); CREATE TABLE tb_person ( id ...

  6. 2015 多校赛 第一场 1001 (hdu 5288)

    Description OO has got a array A of size n ,defined a function f(l,r) represent the number of i (l&l ...

  7. jQuery hooks源码学习

    段落不够清晰,待整理 看jQuery源码的时候,经常见到含有hooks标志的对象,如cssHooks, attrHooks, propHooks, valHooks. 下面对其中的一段进行解读. jQ ...

  8. 从 Zero 到 Hero ,一文掌握 Python--转

    https://www.oschina.net/translate/learning-python-from-zero-to-hero 第一个问题,什么是 Python ?根据 Python 之父 G ...

  9. JqGrid saveRow方法报404错误

    TCX_1807工艺配置/检测项配置页面为jqgrid可编辑页面,使用的脚本为 ){ jQuery('#gridList').saveRow(lastId, true);//保存上一个修改的单元行 } ...

  10. 非阻塞方式connect编程

    参考博客: ①setsockopt()函数使用详解:http://blog.csdn.net/tody_guo/article/details/5972588 ②setsockopt :SO_LING ...