当前位置: 首页 > news >正文

Java学习------源码解析之StringBuilder

1. 介绍

在这里插入图片描述
String中还有两个常用的类,StringBuffer和StringBuilder。这两个类都是专门为频繁进行拼接字符串而准备的。最先出现的是StringBuffer,之后到jdk1.5的时候才有了StringBuilder。

2. StringBuilder解析

在这里插入图片描述
从这张继承结构图可以看出:

  • StringBuilder继承了AbstractStringBuilder
  • StringBuilder实现了Comparable接口,这说明他是可比较的
  • StringBuilder实现了CharSequence接口,这说明他是一个可变的字符序列
  • StringBuilder实现了Appendable接口,这说明他具有可追加字符序列的能力
  • StringBuilder实现了Serializable接口,这说明他可以被序列化

在这里插入图片描述
在这里插入图片描述

StringBuilder继承了AbstractStringBuilder,而AbstractStringBuilder的底层是一个byte数组,并且前面没有加final进行修饰,因此他是可变的,可以进行扩容,而String类就是因为这个byte数组加了final关键字进行修饰,因此String是不可变的。那么既然StringBuilder可以进行扩容,在具体进行扩容操作又是如何实现的呢?接下来就要继续从源码进行查看。
StringBuilder有一个无参构造方法,默认初始化的容量是16.
在这里插入图片描述

2.1 第一个例子

接下来我们写一个具体的程序来看一下,在扩容的时候是如何进行操作的。

public class test {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();

        sb.append("Hello World");
        sb.append("123456");
        sb.append("3.1415926");

        System.out.println(sb);
    }
}

然后开始debug进行查看。首先是进入到Stringbuilder的这个append()方法
在这里插入图片描述
然后下一步会进入父类的append()方法。
在这里插入图片描述
这个方法就是确保他的容量,这里有一个count参数,代表最小容量,这个参数是指当前这个对象里面真实存储的元素的个数,现在是0,意味着这个对象创建出来之后,里面暂时还没有传入任何值。
在这里插入图片描述
进入到这个方法之后,这里有一个oldCapacity变量,因为初始化容量是16,因此这里这个值也是16,然后下一步判断这个最小容量minimumCapacity减去这个老容量oldCapacity是否大于0,此时可以看到值大于0为false,因此不会进去if中的扩容操作。
在这里插入图片描述
然后再看下一次的append()操作
在这里插入图片描述
同样的走到这一步,这个时候可以看见,最小容量minimumCapacity减去这个老容量oldCapacity大于0了,说明这里会进行扩容的操作,就会进入到newCapacity()这个方法。
在这里插入图片描述
到这一步可以看到此时的oldLength就是初始化的长度为16,newLength是预期的长度,也就是这个字符串拼接进去之后的长度,为17,增长就为17 - 16 = 1,接下来就要进入到ArraysSupport.newLength()这个方法中查看新长度为多少。
在这里插入图片描述
进入到方法之后,然后这里使用oldLength,也就是16,去加上Math.max(minGrowth, prefGrowth),在最小增长和预期增长之间的最大值,这里minGrowth为1,prefGrowth为18,因此最大值就是18,然后就是16 + 18 = 34。
在这里插入图片描述
然后回到newCapacity()方法,也可以看到length为34.
在这里插入图片描述
继续往下走,发现此时value值为34
在这里插入图片描述
当我们不指定初始容量时,会给一个默认的初始容量为16,从上面可以看到进行第一次扩容之后,新的容量为34.

2.2 第二个例子

我们在写一个程序进行测试,这次拼接的字符串稍微大一点

public class test {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();

        sb.append("3.1415926535897932384626433832795028841971");

        System.out.println(sb);
    }
}

还是一样先进入append()方法
在这里插入图片描述
再进入父类的append()方法,可以看到现在长度为42
在这里插入图片描述
然后进入到ensureCapacityInternal()方法,这次可以看到最小容量为42,旧容量为16,42 - 16 > 0,因此要进行扩容。
在这里插入图片描述
接着进入到newCapacity()方法,这个时候的增长growth就是42 - 16 = 26了,然后下一步进入ArraysSupport.newLength()方法
在这里插入图片描述
进去之后oldLength不变,还是16,但是这次的Math.max(minGrowth, prefGrowth),minGrowth为26,prefGrowth为18,那么最大值就是26,所以prefLength的值为42。后续步骤就和之前一样了。
在这里插入图片描述当我们不指定长度,并且添加的字符串长度过长时,扩容的时候,就直接使用当前这个字符串的长度。
如果这个时候,再添加一个字符串,比如sb.append(“helloworld123”);这个时候又会是什么样的呢?可以看到这个时候oldLength就是我们第一次拼接的长度为42,新长度为55,增长长度为 55 - 42 = 13,继续往下走
在这里插入图片描述
这个时候可以看到oldLength为42,Math.max(minGrowth, prefGrowth)方法取的最大值,最大值现在为44,因此扩容后的长度就是42 + 44 = 86.
在这里插入图片描述
那么扩容的测试到这里就结束了。我们可以看到,第一次初始化容量为16时,扩容容量为34,为16 * 2 + 2,这次再进行二次拼接时一开始为42,扩容容量为86,为42 * 2 + 2 ,可以明显看出每次扩容是原来容量的2倍再加2.原因就是因为这段代码,从上面调试来看,扩容时都是oldLength + (oldLength + 2),即oldLength * 2 + 2.
在这里插入图片描述

3. 扩容总结

  1. StringBuilder的扩容策略是从当前容量开始,每次扩容为原来的2倍再加2
  2. 如果一开始拼接的字符串的长度就超过了默认初始容量16时,那么StringBuilder扩容的容量就直接为一开始的字符串长度
  3. 如果我们想要进行优化,那么就要在创建StringBuilder时,预估一下我们要拼接的字符串的长度,然后给定一个合适的初始化容量,从而减少底层的扩容操作。

4. 构造方法和常用方法

4.1 构造方法

方法描述
StringBuilder()构造一个字符串生成器,其中不包含任何字符,初始容量为16个字符
StringBuilder(int capacity)构造一个字符串生成器,其中不包含任何字符,并且具有由容量参数指定的初始容量
StringBuilder(String str)构造初始化为指定字符串内容的字符串生成器

用法如下:
在这里插入图片描述

4.2 常用方法

  1. append(),追加方法,这个里面可以传多种不同类型的值
    在这里插入图片描述

  2. delete():删除方法
    在这里插入图片描述

public class test {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder("abcdefg");
        /**
         * delete(int start, int end)
         * 删除从指定位置开始,到指定位置结束的字符
         * 左闭右开:[start, end]
         */
        sb1.delete(0,3);
        System.out.println(sb1); //defg

        /**
         * deleteCharAt(int index)
         * 删除指定位置的字符
         */
        StringBuilder sb2 = new StringBuilder("abcdefg");
        sb2.deleteCharAt(5); // abcdeg
        System.out.println(sb2);
    }
}
  1. insert():插入方法,在指定位置添加的方法,可以添加多种类型的值
    在这里插入图片描述
public class test {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder("abcdefg");
        /**
         * insert(int offset, String str)
         * 在指定位置上添加一个字符串
         */
        sb1.insert(3, "HELLO"); // abcHELLOdefg
        System.out.println(sb1);
    }
}
  1. replace(int start, int end, String str):替换方法
public class test {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder("abcdefg");
        /**
         * replace(int start, int end, String str)
         * 从指定位置开始到结束,替换成新的字符串
         * 同样也是左闭右开
         */
        sb1.replace(1, 3, "ABC"); // aABCdefg
        System.out.println(sb1);
    }
}
  1. reverse():反转方法
public class test {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder("abcdefg");
        sb1.reverse();
        System.out.println(sb1); // gfedcba
    }
}
  1. setCharAt(int index, char ch):设置某个位置为指定的字符
public class test {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder("abcdefg");
        sb1.setCharAt(0, 'A');
        System.out.println(sb1); // Abcdefg
    }
}
  1. setLength(int newLength):设置新的长度
public class test {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder("abcdefg");
        sb1.setLength(3);
        System.out.println(sb1); // abc
    }
}

剩下的一些方法,在用到的时候看一下即可,都不是很难。

http://www.dtcms.com/a/98612.html

相关文章:

  • C++笔记-string(中)
  • Keil编译生成的axf文件的介绍
  • 38.C++哈希3(哈希表底层模拟实现 - 开散列拉链法和哈希桶)
  • 异常与捕获
  • Android7 Input(二)Linux 驱动层输入事件管理
  • Protobuf 的快速使用(二)
  • SVTAV1函数分析-svt_av1_cost_coeffs_txb
  • (二)创建实例
  • 人工智能之数学基础:实对称矩阵
  • AI大模型最新发布[update@202503]
  • [Vue2]v-model用于表单
  • fio磁盘测试工具使用笔记
  • Appium中元素定位的注意点
  • springboot-Spring Boot DevTools工具的使用
  • VSCODE npm: 因为在此系统上禁止运行脚本。有关详细信息,请参阅 ...
  • 浏览器 ➔ 服务器or服务器 ➔ 浏览器:
  • 第二卷:海盐城血战(37-72回)正反人物群像
  • 第一篇:系统分析师首篇
  • DFS飞机降落
  • 《HelloGitHub》第 108 期
  • AUTOSAR_StbM_详解
  • 浅谈Thread类及常见方法与线程的状态(多线程编程篇2)
  • fetch`的语法规则及常见用法
  • Document对象的常用属性和方法
  • 蓝桥杯[每日一题] 真题:管道(java版)
  • tryhackme——Windows Local Persistence
  • std::reference_wrapper 和 std::function的详细介绍
  • MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part13
  • 【QT】QT样式设计
  • openwrt24.10.0版本上安装istoreOS的屏幕监控插件