字符串不可变性以及StringBuilder和StringBuffer在字符串拼接中的作用和扩容机制

本文主要是介绍字符串不可变性以及StringBuilder和StringBuffer在字符串拼接中的作用和扩容机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

java字符串的不可变性

在jdk1.8及以前,字符串底层存储用的是一个字符(char)类型的数组,jdk1.9之后用的是整型中的字节型(byte)数组来存储字符串。以下下主要以jdk1.8为例子展开。

 private final char value[];
/*
底层的字符类型的数组声明时用private和final来修饰
private修饰代表在类之外不能直接操作,只能通过该对象的公共方法来操作,而公共方法最后都返回的是新字符串
final来修饰代表引用数据类型的地址不能变,也就是说只能修改里面的元素内容,但是在外部无法直接操作,只能借助类中的方法,但是方法都返回一个新的字符串
从这两个角度,字符串无法变,这就是字符串的不可变性
*/对于一个方法的解析
public String substring(int beginIndex, int endIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex > value.length) {throw new StringIndexOutOfBoundsException(endIndex);}int subLen = endIndex - beginIndex;if (subLen < 0) {throw new StringIndexOutOfBoundsException(subLen);}return ((beginIndex == 0) && (endIndex == value.length)) ? this: new String(value, beginIndex, subLen);}substring:字符串的截取, 当截取值就是整个字符串时,返回自己,没改变。如果不是new了一个新的String对象,原字符串没有发生改变。
通过这个方法可以了解到,/*无法在不改变内部代码的情况下,修改字符串的内容,那么字符串不可变*/

大量字符串拼接中StringBuilder和StringBuffer的作用

因为字符串的不可变性,字符串的拼接最终都是产生一个新的字符串,在大量拼接操作过程中就会产生大量的新字符串(临时字符串),将会短时间占用大量空间,导致空间的浪费。

StringBuilder、StringBuffer非相关拓展介绍:

StringBuilder:jdk1.5,非线程安全,稍慢

StringBuffer:jdk1.0,带线程锁(synchronized),线程安全,稍慢

下面以StringBuilder为例分析其在大量字符串拼接中的作用

StringBuilder类:
public final class StringBuilderextends AbstractStringBuilderimplements java.io.Serializable, CharSequenceAbstractStringBuilder抽象类:
abstract class AbstractStringBuilder implements Appendable, CharSequence    public StringBuilder(int capacity) {super(capacity);
}
AbstractStringBuilder(int capacity) {value = new char[capacity];}/*
StringBuilder这个类继承AbstractStringBuilder抽象类,StringBuilder这个类中构造方法调用了抽象类的构造方法,创建了一个字符(char)类型的数组。
*/至此我们可以创建一个类似于字符串的一个StringBuilder对象,并可以创建一个初始值,如果我在创建对象时使用的无参构造方法,底层会给我创建一个长度为16的字符类型的数组,作为成员会有默认值就是空字符
下面开始拼接操作,看字符串的拼接和StringBuilder对象来做拼接有什么不同
StringBuilder对象的拼接需要用到append方法
StringBuilder sc = new StringBuilder("123");
sc.append(",");public StringBuilder append(String str) {super.append(str);return this;}
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 AbstractStringBuilder appendNull() {int c = count;ensureCapacityInternal(c + 4);final char[] value = this.value;value[c++] = 'n';value[c++] = 'u';value[c++] = 'l';value[c++] = 'l';count = c;return this;}
/*
sc这个对象在调用时用了父类的append方法,将自己返回
父类中的append方法,首先判断了一下传入变量是否为null,是执行appendNull()方法将结果返回
appendNull()方法:首先判断是否需要扩容,用另一个变量指向对象的字符数组,将null拆分成字符添加到字符数组中,直接改变对象底层的字符数组。
接着如果添加的元素不是null,就执行字符串的getChars实际是System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);将一个数组中的元素复制到另一个数组,前面的数组是源数组,后一个是目标数组,将元素组中的元素,复制到目标数组。此操作仍是在对象的底层字符数组上直接操作。
所以整个一次拼接过程不产生临时额外数组,完美的解决了原始字符串拼接,每次拼接操作都会产生临时数据浪费资源的问题
*/

StringBuffer与StringBuilder拼接过程基本一致,只是它们一个线程安全效率稍低,一个非线程安全效率比较而言稍高。

StringBuffer与StringBuilder中的扩容机制

仍然以StringBuilder为例

StringBuilder有多种构造方法生成对象,无参的,有参传数值的,有参传字符串的等
以无参、有参传数值、有参传字符串这三种常见的创建对象的形式,了解其中的扩容机制
StringBuilder sc = new StringBuilder();
public StringBuilder() {super(16);}
AbstractStringBuilder(int capacity) {value = new char[capacity];}
/*
无参,调用抽象类的构造方法,创建一个长度为16的字符数组,初始值空字符
*/
StringBuilder sc1 = new StringBuilder(1024);
public StringBuilder(int capacity) {super(capacity);}
AbstractStringBuilder(int capacity) {value = new char[capacity];}
/*
传数值的这种,自己定义最初字符数组长度
*/StringBuilder sc2 = new StringBuilder("123");
public StringBuilder(String str) {super(str.length() + 16);append(str);}
AbstractStringBuilder(int capacity) {value = new char[capacity];}
/*
传字符串这种,先调用抽象类的构造方法,得到一个字符串长度再加16的字符数组
只有调用append(str)方法,将字符串底层字符数组复制到对象底层维护的数组
*/
当以上三种方法创建出对象后做拼接操纵,会通过ensureCapacityInternal(count + len)方法来判断需不需要扩容
minimumCapacity=count + len,拼接后字符数组数据元素长度=原本的字符数组实际长度+要拼接字符串长度
当minimumCapacity - value.length > 0时,也就是拼接后字符数组元素长度>对象维护的数组长度时我们需要扩容value = Arrays.copyOf(value,newCapacity(minimumCapacity));通过newCapacity方法得到最终扩容后数组长度,通过Arrays.copyOf()复制对象维护数组值的同时开辟相应的空间,最终数组复制给对象维护字符数组。
newCapacity方法,首先在原对象维护数组长度基础上*2+2;此时看扩容后的长度是否满足最低要求,满足,扩容长度为原对象维护数组长度基础上*2+2,不满足则扩充最低要求长度,最终如果这个长度不小于等于0或者超过定义的最大长度,那么最终长度确定,否则和Integer.MAX_VALUE值作比较,比这个值都大,报异常内存溢出,否则和数组最大长度(MAX_ARRAY_SIZE)比较,如果小,就返回当前值,否则就把数组最大值返回。

这篇关于字符串不可变性以及StringBuilder和StringBuffer在字符串拼接中的作用和扩容机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/621341

相关文章

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

Maven 配置中的 <mirror>绕过 HTTP 阻断机制的方法

《Maven配置中的<mirror>绕过HTTP阻断机制的方法》:本文主要介绍Maven配置中的<mirror>绕过HTTP阻断机制的方法,本文给大家分享问题原因及解决方案,感兴趣的朋友一... 目录一、问题场景:升级 Maven 后构建失败二、解决方案:通过 <mirror> 配置覆盖默认行为1. 配置示

Springboot3+将ID转为JSON字符串的详细配置方案

《Springboot3+将ID转为JSON字符串的详细配置方案》:本文主要介绍纯后端实现Long/BigIntegerID转为JSON字符串的详细配置方案,s基于SpringBoot3+和Spr... 目录1. 添加依赖2. 全局 Jackson 配置3. 精准控制(可选)4. OpenAPI (Spri

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

SpringBoot 中 CommandLineRunner的作用示例详解

《SpringBoot中CommandLineRunner的作用示例详解》SpringBoot提供的一种简单的实现方案就是添加一个model并实现CommandLineRunner接口,实现功能的... 目录1、CommandLineRunnerSpringBoot中CommandLineRunner的作用

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

使用Python实现base64字符串与图片互转的详细步骤

《使用Python实现base64字符串与图片互转的详细步骤》要将一个Base64编码的字符串转换为图片文件并保存下来,可以使用Python的base64模块来实现,这一过程包括解码Base64字符串... 目录1. 图片编码为 Base64 字符串2. Base64 字符串解码为图片文件3. 示例使用注意

pandas实现数据concat拼接的示例代码

《pandas实现数据concat拼接的示例代码》pandas.concat用于合并DataFrame或Series,本文主要介绍了pandas实现数据concat拼接的示例代码,具有一定的参考价值,... 目录语法示例:使用pandas.concat合并数据默认的concat:参数axis=0,join=

Jvm sandbox mock机制的实践过程

《Jvmsandboxmock机制的实践过程》:本文主要介绍Jvmsandboxmock机制的实践过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、背景二、定义一个损坏的钟1、 Springboot工程中创建一个Clock类2、 添加一个Controller

golang float和科学计数法转字符串的实现方式

《golangfloat和科学计数法转字符串的实现方式》:本文主要介绍golangfloat和科学计数法转字符串的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望... 目录golang float和科学计数法转字符串需要对float转字符串做处理总结golang float