Java源码深度分析String与StringBuffer及StringBuilder详解
作者:星辰与晨曦 发布时间:2022-04-01 09:55:50
String的字符串是不可变的,StringBuffer和StringBuilder是可变的
String:是字符常量,适用于少量的字符串操作的情况。
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况 。
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。
StringBuffer和StringBuild的区别
StingBuffer是线程安全的,StringBuild是线程不安全的。
然后再往上查看,就会发现他两都是调用了AbstractStringBuilder类来实现的。
我们不是说StringBuffer和StringBuilder创建的字符串是可变的。
至于源码就直接看源码,就直接在源码当中详细的体现出来了。
在这篇博客我主要说一下StringBuffer,因为StingBuild和StringBuffer的方法几乎一样,我在这就不做过多的详细赘述了。
创建StringBuffer()
第一种创建方法:
StringBuffer stringBuffer = new StringBuffer();
在使用这个方法创建的Buffer字符串的时候,看底层代码它就调用了这个方法:
public StringBuffer() {
super(16);
}
它虽然没有给传入参数,它是它默认的是一个数组长度为16的字符串。
在往上看这个向上传的父类方法就更加简单明了:
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
直接创建长度为16的字符型数组。
第二种创建方法:
StringBuffer stringBuffer = new StringBuffer(5);
直接给定要创建的字符长度。
底层源码:
public StringBuffer(int capacity) {
super(capacity);
}
第三种创建方法:
StringBuffer stringBuffer = new StringBuffer(“Xin”);
在创建的时候,初始给定初始化的一个字符串。
那么在这个时候就要考虑它的底层保存字符串的数组要创建几位呢? 看源码,源码会告诉我们一切的。
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
源码当中明确的告诉了我们,在这种情况下创建的字符串,底层数组长度是初始字符串的长度加16.
小小的总结一下:
在创建StringBuffer或StringBuilder字符串的时候如果没有给长度,默认为16的长度,给长度的话,就是给定的长度,给的是字符串的话,默认初始长度就是字符串的长度加16
添加功能
public StringBuffer append(String str)
代码演示一下,这还要一个点考虑一下:
public class Demo01 {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("Xin_"); //stringBuffer.length = 4
stringBuffer.append("Chen_"); //stringBuffer.length = 9
stringBuffer.append("Yu_"); //stringBuffer.length = 12
stringBuffer.append("Chen_"); //stringBuffer.length = 17
stringBuffer.append("Xi"); //stringBuffer.length = 19
System.out.println(stringBuffer);
}
}
在不停的添加下去,当数组的长度到达16的时候,要考虑一下扩容的问题,如何扩容?
老规矩,看源代码,一切都在代码里:
照着这个步骤一步一步的点进去,到达最后的源代码:
MAX_ARRAY_SIZE的长度为:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
所以最后是 0x7fffffff-8
总的来说,每次底层数组扩容为上次的2倍加2的长度。
删除功能
第一种删除字符串中指定位置的字符
public StringBuffer deleteCharAt(int index)
这个指定删除字符串当中明确的第几个索引。只删除一个元素
注意这里是索引的位置,不是第几个元素。
public class Demo01 {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("Xin_Chen_Yu_Chen_Xi");
System.out.println(stringBuffer);
stringBuffer.deleteCharAt(4);
System.out.println(stringBuffer);
}
}
源码其实也不难,就不重视的看了,在这主要看一经过删除操作,它的字符串长度是如何减少的。
删除字符串当中指定区间的字符串
public StringBuffer delete(int start,int end)
牢记在Java当中大多情况下,数组下标索引都是左闭右开的。
在下面的代码当中,我删除的就是"Chen_"这5个字符
public class Demo01 {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("Xin_Chen_Yu_Chen_Xi");
System.out.println(stringBuffer);
stringBuffer.delete(4,9);
System.out.println(stringBuffer);
}
}
和上面一样。看一眼源码,如何处理删除后的字符串的长度:
它两的底层都调用的是native本地反方,交由计算机系统完成。
替换功能
就是将字符串中其中某一段的字符串替换为其他的字符串。
public StringBuffer replace(int start,int end,String str)
public class Demo01 {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("Xin_Chen_Yu_Chen_Xi");
System.out.println(stringBuffer);
stringBuffer.replace(4,8,"Gu_Chen");
System.out.println(stringBuffer);
}
}
这里也都是数组的下标索引,所以就是从0开始计数的。
替换这也同上面的一样,也需要考虑一下底层字符串的长度问题,先看源码:
反转功能
这个功能就是将原字符串玩去哪反转过来,倒序保存起来。
public class Demo01 {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("Xin_Chen_Yu_Chen_Xi");
System.out.println(stringBuffer);
stringBuffer.reverse();
System.out.println(stringBuffer);
}
}
也来看一下它的源代码
最后总结一下
String的字符串是不可变的,StringBuffer和StringBuilder是可变的
String:是字符常量,适用于少量的字符串操作的情况。
StringBuilder:线程不安全的,适用于单线程下在字符缓冲区进行大量操作的情况 。
StringBuffer:线程不安全的,适用多线程下在字符缓冲区进行大量操作的情况。
在这几条总计出来的说法当中,在这再详细说明一下为什么StringBuffer和StringBuilder是可变的。
在文章上面,我展示了许多的源码,仔细观察一下,Buffer和Builder修饰的方法,它最后返回的都是this,返回了,自己,就是无论什么操作,它最终都返回的是自己。
但是有些小伙伴还是有个疑问,当它底层扩容超过它的最大值的时候,它的数组不就要换了吗?
这点确实是对的,但是在这我们要理清一个概念,StringBuffer引用的是底层数组名字的地址,而数组在需要换一个更大的时候,这个时候是数组的引用变化了,就是指向数组的地址变了,但是指向StringBuffer的地址是不变的。
我下来使用一个debug动图来展示一下,相信能更好的理解一下。
主要看控制台里的StringBuffer和value的值。我们发现StringBuffer值地址自始至终都没有改变,而value的值在到达16的时候,需要扩容的时候,就变了。这正印证了我上面所说的。
本篇博客也就到此为止了,感谢你的阅读。如果要是还是想不清楚,特别是最后的总结部分,欢迎在评论区讨论,我看到话,会第一时间共同讨论,解决问题。
这也是我重读javase基础的一个系列,比起当时初学的时候,现在看问题多了个高度,理解什么也相对轻松一点全面一些。学习起来更加偏向阅读源码来看,所以多为大家分享看源码。
来源:https://blog.csdn.net/weixin_45970271/article/details/124675457
猜你喜欢
- java中实现list或set转map的方法在开发中我们有时需要将list或set转换为map(比如对象属性中的唯一键作为map的key,对
- 框架的概述JDBC存在的问题:我们要想研究mybatis就必须知道jdbc所存在的问题,那我那么我们首先来复习一下jdbc操作数据库的大致流
- 1、简单又有效的方法是使用PreparedStatement采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX(如:s
- 1.前面讲解的都是通过直接读取配置文件,进行的实例化ApplicationContextAbstractApplicationContext
- 什么是OKHttp一般在Java平台上,我们会使用Apache HttpClient作为Http客户端,用于发送 HTTP 请求,并对响应进
- 首先说一下最近自己遇到的一个坑:@Transactionalservice A(){try{insert();serviceB.update
- 网络唤醒实现了对网络的集中管理,即在任何时刻,网管中心的IT管理人员可以经由网络远程唤醒一台处于休眠或关机状态的计算机。使用这一功能,IT管
- 一种可以设置滑动动画的控件,只显示一行布局,在布局文件中的ViewFlipper控件中顺序写好每一行的布局(1).MainActivity.
- 文章来源:互联网 作者:ggg82/CSDN现在许多用户界面都使用工具栏制作菜单条,小弟最近对此感兴趣,便从网上求助,可是得到的帮助大多是B
- 如果您要显示敏感数据,例如。钱包金额,或者只是当登录表单显示插入的密码清晰时(想想眼睛图标..),当您不在应用程序中时,您必须隐藏敏感数据。
- java中的线程状态🥇在操作系统层面,一个线程就两个状态:就绪和阻塞状态.但是java中为了在线程阻塞时能够更快速的知晓一个线程阻塞的原因,
- MyBatis Generator简介MyBatis Generator(MBG)是MyBatis MyBatis 和iBATIS的代码生成
- 1. 对象的创建对象创建的主要流程:1.类加载检查虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引
- MongoDB的基本使用添加依赖<dependency>
- 本文实例为大家分享了C#实现简易计算器功能的具体代码,供大家参考,具体内容如下实现页面布局和数值初始化using System;using
- 本文实例展示了WinForm项目开发中NPOI用法,对于C#初学者有一定的借鉴价值。具体实例如下:private void ExportMe
- 前言碎语今天博主安利一个国产开源的无服务器容器云平台,关注它已经有一年多了,虽然其迭代到现在很多功能还是一直处于测试验证中,但是其设计理念以
- 串口通讯是一种计算机常用的数据传输方式。程序运行如下:首先,检查计算机的串口,并获取所有串口信息。private void CheckPor
- 前端模板框架为Bootstrap,系统分为前台和后台。后台主要为管理员角色,功能有:商品类型管理、商品管理、订单管理、会员管理、管理员管理等
- 前言回想写过的图书管理系统、租房系统、电影院卖票系统都是基于原生的JavaSE、OOP,没有用到任何框架,在层与层的关系中一个类要想获得与其