字符串不可变性以及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

相关文章

C# async await 异步编程实现机制详解

《C#asyncawait异步编程实现机制详解》async/await是C#5.0引入的语法糖,它基于**状态机(StateMachine)**模式实现,将异步方法转换为编译器生成的状态机类,本... 目录一、async/await 异步编程实现机制1.1 核心概念1.2 编译器转换过程1.3 关键组件解析

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

Spring Security 单点登录与自动登录机制的实现原理

《SpringSecurity单点登录与自动登录机制的实现原理》本文探讨SpringSecurity实现单点登录(SSO)与自动登录机制,涵盖JWT跨系统认证、RememberMe持久化Token... 目录一、核心概念解析1.1 单点登录(SSO)1.2 自动登录(Remember Me)二、代码分析三、

C# $字符串插值的使用

《C#$字符串插值的使用》本文介绍了C#中的字符串插值功能,详细介绍了使用$符号的实现方式,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录$ 字符使用方式创建内插字符串包含不同的数据类型控制内插表达式的格式控制内插表达式的对齐方式内插表达式中使用转义序列内插表达式中使用

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont

Spring Boot 中的默认异常处理机制及执行流程

《SpringBoot中的默认异常处理机制及执行流程》SpringBoot内置BasicErrorController,自动处理异常并生成HTML/JSON响应,支持自定义错误路径、配置及扩展,如... 目录Spring Boot 异常处理机制详解默认错误页面功能自动异常转换机制错误属性配置选项默认错误处理

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

详解MySQL中JSON数据类型用法及与传统JSON字符串对比

《详解MySQL中JSON数据类型用法及与传统JSON字符串对比》MySQL从5.7版本开始引入了JSON数据类型,专门用于存储JSON格式的数据,本文将为大家简单介绍一下MySQL中JSON数据类型... 目录前言基本用法jsON数据类型 vs 传统JSON字符串1. 存储方式2. 查询方式对比3. 索引

Java中的xxl-job调度器线程池工作机制

《Java中的xxl-job调度器线程池工作机制》xxl-job通过快慢线程池分离短时与长时任务,动态降级超时任务至慢池,结合异步触发和资源隔离机制,提升高频调度的性能与稳定性,支撑高并发场景下的可靠... 目录⚙️ 一、调度器线程池的核心设计 二、线程池的工作流程 三、线程池配置参数与优化 四、总结:线程