热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

String,StringBuilder和StringBuffer整理汇总

本文为joshua317原创文章,转载请注明:转载自joshua317博客 https:www.joshua317.comarticle241一、简单了解下,String,Stri

本文为joshua317原创文章,转载请注明:转载自joshua317博客 https://www.joshua317.com/article/241


一、简单了解下,String,StringBuilder和StringBuffer的区别在哪?

String 是 Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。也由于它的不可变性,类似拼接、裁剪字符串等动作,每次对String的操作都会生成新的 String对象

private final char value[];

StringBuffer是为解决上面提到拼接产生太多中间对象的问题而提供的一个类,可以用 append 或者 add 方法,把字符串添加到已有序列的末尾或者指定位置。StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销,所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是 StringBuilder。

StringBuilder是 Java 1.5 中新增的,在能力上和 StringBuffer 没有本质区别,但是它去掉了线程安全的部分,减小了开销,是绝大部分情况下进行字符串拼接的首选。

StringBuffer和StringBuilder他们两都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder 抽象类中我们可以看到

/**
* The value is used for character storage.
*/
char[] value;

StringBuilder和StringBuffer的内部实现跟String类一样,都是通过一个char数组存储字符串的,不同的是String类里面的char数组是final修饰的,是不可变的,而StringBuilder和StringBuffer的char数组是可变的。所以在进行频繁的字符串操作时,建议使用StringBuffer和 StringBuilder来进行操作。 另外StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

 



 

 

当我们需要对字符串进行大量修改时,推荐使用StringBufferStringBuilder类。

String类不同,StringBufferStringBuilder类的对象可以反复修改,而不会留下大量新的未使用对象。

StringBuilder类是从Java 5开始引入的,StringBufferStringBuilder之间的主要区别在于StringBuilders方法不是线程安全的(不同步)。

建议尽可能使用StringBuilder类,因为它比StringBuffer更快。 但是,如果需要线程安全性,最好是使用StringBuffer类。


二、代码编程说明StringBuilder不是线程安全的

接下来,我们用StringBuilder和StringBuffer进行字符串追加操作,看下有何不同

我们做下简单的测试

首先,创建10个线程;
然后,每个线程循环100次往StringBuilder或者StringBuffer对象里面append字符。
我们预想结果是输出结果为1000,但是实际运行会输出什么呢?

2.1 StringBuilder测试

package com.joshua317;
public class StringBuilderTest {
public static void main(String[] args) throws InterruptedException {
StringBuilder stringBuilder = new StringBuilder();
for (int i=0; i<10; i++) {
(new Thread(new ThreadTestStringBuilder(stringBuilder))).start();
}
Thread.sleep(100);
System.out.println(stringBuilder.length());
}
}
class ThreadTestStringBuilder implements Runnable {
public StringBuilder stringBuilder;
ThreadTestStringBuilder(StringBuilder stringBuilder) {
this.stringBuilder = stringBuilder;
}
@Override
public void run()
{
for (int j=0; j <100; j++) {
stringBuilder.append("a");
}
}
}

 



 

 

 



 

 


2.2 StringBuffer测试

package com.joshua317;
import java.util.Collection;
public class StringBufferTest {
public static void main(String[] args) throws InterruptedException {
StringBuffer stringBuffer = new StringBuffer();
for (int i=0; i<10; i++) {
(new Thread(new ThreadTestStringBuffer(stringBuffer))).start();
}
Thread.sleep(100);
System.out.println(stringBuffer.length());
}
}
class ThreadTestStringBuffer implements Runnable {
public StringBuffer stringBuffer;
ThreadTestStringBuffer(StringBuffer stringBuffer) {
this.stringBuffer = stringBuffer;
}
@Override
public void run()
{
for (int j=0; j<1000; j++) {
stringBuffer.append("a");
}
}
}

 



 

 

通过上面俩个例子,我们发现,StringBuilder在多线程执行的过程中,可能会出现字符长度小于1000,甚至出现了ArrayIndexOutOfBoundsException异常(异常非必现)。而StringBuffer则正常输出字符串长度为1000,从而我们可以简单得出StringBuilder是非线程安全的


三、分析StringBuilder执行所出现的问题


3.1 为什么输出值跟预期值不一样

我们先看一下StringBuilder的两个成员变量(这两个成员变量实际上是定义在AbstractStringBuilder里面的,StringBuilder和StringBuffer都继承了AbstractStringBuilder)

/**
* The value is used for character storage.
* 用于字符存储
*/
char[] value;
/**
* The count is the number of characters used.
* 用于记录字符数的数量
*/
int count;

 



 

 

再实例化StringBuilder时,我们可以看出,StringBuilder默认的容量大小为16。当然也可以指定初始容量,或者以一个已有的字符序列给StringBuilder对象赋初始值。

StringBuilder stringBuilder = new StringBuilder();
//StringBuilder类
public StringBuilder() {
super(16);
}
//AbstractStringBuilder类
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}

再看StringBuilder的append方法

@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}

 



 

 

然后,StringBuilder的append方法调用了父类AbstractStringBuilder的append方法。

public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}

 



 

 

从代码count += len;我们可以得知它不是一个原子操作。假设这个时候count值为100,len值为1,两个线程同时执行到了这一行,拿到的count值都是101,执行完加法运算后将结果赋值给count,所以两个线程执行完后count值为101,而不是102。这样就导致了字符的计数值要比我们预期结果1000小的原因。

而我们再来看下StringBuffer的append操作

@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

 



 

 

StringBuffer 的append方法是synchronized 修饰的,保证了多线程情况下,同步操作。


3.2 为什么会抛出ArrayIndexOutOfBoundsException异常

我们看下AbstractStringBuilder的append()方法里面的ensureCapacityInternal()方法,它主要是用来检查StringBuilder对象的原char数组的容量能不能容纳下新的字符串,如果容纳不下就调用ensureCapacityInternal()方法对char数组进行扩容,也就是说StringBuilder 里面的ensureCapacityInternal()方法是用来扩展value数组的。

public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}


private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}


private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length <<1) + 2;
if (newCapacity - minCapacity <0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity <0)
? hugeCapacity(minCapacity)
: newCapacity;
}

private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity <0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}

//java.lang.Integer
@Native public static final int MAX_VALUE = 0x7fffffff;

//java.util.Arrays类中
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

//java.lang.System类中
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);

扩容的方法最终是由expandCapacity()实现的,在这个方法中首先把把容量变为原来容量的2倍加2,如果此时仍小于指定的容量,那么就把新的容量设为minimumCapacity。然后判断是否溢出,如果溢出了,把容量设为MAX_ARRAY_SIZE。最后通过Arrays.copyOf()方法调用System.arraycopy()方法,把value值进行拷贝。

然后继续往下看代码,AbstractStringBuilder类的append()方法中str.getChars(0, len, value, count);

这一行作用是将String对象里面char数组里面的内容拷贝到StringBuilder对象的char数组里面

str.getChars(0, len, value, count);

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin <0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

我们用流程图来表示字符串拷贝的过程

1.假设现在有两个线程同时执行了StringBuilder的append()方法,两个线程都执行完了AbstractStringBuilder的ensureCapacityInternal()方法,此刻count=101。

 



 

 

2.此时线程1的cpu时间片用完了,线程2继续执行,线程2执行完整个append()方法后count变成102了。



 

 

3.线程1继续执行AbstractStringBuilder的str.getChars()方法的时候拿到的count值就是102了,执行char数组拷贝的时候就会抛出ArrayIndexOutOfBoundsException异常。

至此,StringBuilder执行所出现的问题已经分析完了,同时StringBuilder多线程不安全的问题也就迎刃而解。


四、扩展


4.1 什么是线程安全?

当多个线程访问某一个类(对象或方法)时,对象对应的公共数据区始终都能表现正确,那么这个类(对象或方法)就是线程安全的。通俗地理解就是:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。


4.2 如何使用String、StringBuffer、StringBuilder呢?























StringStringBufferStringBuilder
不可变字符串可变的字符序列可变的字符序列
 效率低效率高
 线程安全线程不安全

所以,



  • 如果要操作少量的数据使用String;

  • 多线程操作字符串缓冲区下操作大量数据使用StringBuffer;

  • 单线程操作字符串缓冲区下操作大量数据使用StringBuilder。

记住,StringBuffer 适用于用在多线程操作同一个 StringBuffer 的场景,如果是单线程场合 StringBuilder 更适合。


4.3 具体的应用场景



  • 在字符串内容不经常发生变化的业务场景优先使用String类。例如:常量声明、少量的字符串拼接操作等。



  • 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用StringBuffer,例如XML解析、HTTP参数解析与封装。



  • 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用StringBuilder,例如SQL语句拼装、JSON封装等。




4.4 为什么说StringBuilder是线程不安全的?

因为相对StringBuffer,StringBuilder没有在方法上使用 synchronized 关键字。Synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。至于如何分析,请参考上文。

 

本文为joshua317原创文章,转载请注明:转载自joshua317博客 https://www.joshua317.com/article/241



推荐阅读
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 多维数组的使用
    本文介绍了多维数组的概念和使用方法,以及二维数组的特点和操作方式。同时还介绍了如何获取数组的长度。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
author-avatar
原来我不帅S_420
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有