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 第一个例子

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

java 复制代码
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 第二个例子

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

java 复制代码
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():删除方法

java 复制代码
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():插入方法,在指定位置添加的方法,可以添加多种类型的值
java 复制代码
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):替换方法
java 复制代码
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():反转方法
java 复制代码
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):设置某个位置为指定的字符
java 复制代码
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):设置新的长度
java 复制代码
public class test {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder("abcdefg");
        sb1.setLength(3);
        System.out.println(sb1); // abc
    }
}

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

相关推荐
松树戈几秒前
Java常用异步方式总结
java·开发语言
weisian1511 分钟前
Java常用工具算法-3--加密算法2--非对称加密算法(RSA常用,ECC,DSA)
java·开发语言·算法
背影疾风1 分钟前
C++学习之路:指针基础
c++·学习
Uncertainty!!2 分钟前
python函数装饰器
开发语言·python·装饰器
x-cmd2 分钟前
[250331] Paozhu 发布 1.9.0:C++ Web 框架,比肩脚本语言 | DeaDBeeF 播放器发布 1.10.0
android·linux·开发语言·c++·web·音乐播放器·脚本语言
小李同学_LHY14 分钟前
三.微服务架构中的精妙设计:服务注册/服务发现-Eureka
java·spring boot·spring·springcloud
myloveasuka28 分钟前
[Linux]从硬件到软件理解操作系统
linux·开发语言·c++
bst@微胖子38 分钟前
Flutter项目之登录注册功能实现
开发语言·javascript·flutter
非ban必选38 分钟前
spring-ai-alibaba第四章阿里dashscope集成百度翻译tool
java·人工智能·spring
非ban必选44 分钟前
spring-ai-alibaba第五章阿里dashscope集成mcp远程天气查询tools
java·后端·spring