在Java中,String是一个常量,一旦创建其值后不能更改但可以共享。
如果我们把多个字符串进行连接(拼接)操作,就会开辟很多空间,从而造成了大量内存空间的浪费。
为了解决这个问题,我们需要用到StringBuffer类和StringBuilder类。
这两个类可牛逼了,它们都是可变长度的字符串类,在字符串的拼接处理上大大提高了效率。
共同点:底层数据结构都是char类型的数组,都是可变的字符串。
不同点:StringBuffer
线程同步的安全性高,但多线程操作时效率低。StringBuilder
线程不同步,进行多线程操作时,安全性低,但效率高。
因为这两个类的用法都类似,所以我就以StringBuffer类为例来讲解。
StringBuffer类的部分源码:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
从源码中可以看出,StringBuffer
是一个用final
修饰的最终类,继承了父类AbstractStringBuilder
类,实现了Serializable
接口和CharSequence
接口,说明具备了两种能力。
StringBuffer类的部分源码:
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
重点讲下这两个构造方法:
构造方法:StringBuffer()
方法描述:构造一个没有字符的字符串缓冲区,初始容量为16。
构造方法:StringBuffer(String str)
方法描述:构造一个初始化为指定字符串内容的字符串缓冲区,初始容量为str.length() + 16。
实例:
package cn.tkr.demo;
public class MyStringBuffer {
public static void main(String[] args) {
StringBuffer sb1 = new StringBuffer();
System.out.println("sb1字符缓冲区的容量:" + sb1.capacity()); //获取当前StringBuffer的容量
StringBuffer sb2 = new StringBuffer("LoveJava");
System.out.println("sb2字符缓冲区的容量:" + sb2.capacity()); //获取当前StringBuffer的容量
}
}
运行结果:
sb1字符缓冲区的容量:16
sb2字符缓冲区的容量:24
实例分析:调用无参数构造函数,sb1字符缓冲区的初始容量为16,调用有参数构造函数,初始容量为字符串的长度(8) + 16,所以初始容量为24。
返回值类型 | 方法名称 | 描述 |
---|---|---|
int | length() | 获取当前StringBuffer中的字符的个数 |
int | capacity() | 获取当前StringBuffer的容量 |
int | indexOf(String str) | 查找某个字符串str在StringBuffer中的索引位置,找到返回索引,找不到返回-1 |
StringBuffer | append(…) | 将指定的参数的字符串追加到序列中 |
StringBuffer | delete(int start, int end) | 从start的位置开始删除字符,一直删除到索引为end的位置(包含start,不包含end) |
void | setCharAt(int index,char c) | 使用字符c替换指定的索引index位置上的字符 |
注: append(...)
中的...
代表各种类型的参数,如append(int i)、append(char c)、append(String str)等
扩容原理:
StringBuffer
的底层数组结构用的是char
类型的数组。
所以,当我们使用StringBuffer
对象的append(...)
方法追加数据时,如果char类型数组的长度无法容纳我们追加的数据,StringBuffer
就会进行扩容。
扩容时会用到Arrays
类中的copyOf(...)
方法,每次扩容的容量大小是原来的容量的2倍加2。
注: copyOf(...)
中的...
代表各种类型的参数,如
copyOf(int[] original, int newLength)、copyOf(char[] original, int newLength)等
实例源码分析:
假设我现在调用了append(String str)方法,追加了一个字符串(char类型数组的长度无法容纳的字符串)。
append(String str)方法源码:
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
方法中通过super.append(str)
调用了父类的append(String str)
方法。
父类的append(String str)方法源码:
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;
}
重点来了,这里的ensureCapacityInternal(count + len)
就是一个扩容相关的方法,变量count
是一个全局变量,并没有实际的值,变量len
是我们追加进来的字符串的长度。
也就是说,我们追加进来的字符串的长度会传递给ensureCapacityInternal(int minimumCapacity)
方法。
再来看看ensureCapacityInternal(int minimumCapacity)方法的源码:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
其中,minimumCapacity
指我们追加进来的字符串的长度,value
是一个全局的char类型的数组名
。
也就是说,value.length
指数组的长度,那如果(minimumCapacity - value.length > 0)
这个条件成立,也就意味着,char
类型数组的长度无法容纳我们追加的字符串的长度。
这时,就需要使用Arrays
类中的copyOf(char[] original, int newLength)
方法进行扩容。
方法:
copyOf(char[] original, int newLength)
描述:复制指定的数组,截断或使用相应的默认值进行填充
该方法的第一个参数是源数组名,所以要传递的是value
。
第二个参数是新数组长度,新数组长度的值是通过newCapacity(int minCapacity)
方法来计算并返回的值。
newCapacity(int minCapacity)方法的源码:
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
这个方法会返回一个新的容量大小(即新数组长度),每次扩容的容量大小是原来的容量的2倍加2。
以上是我分享给大家的关于StringBuffer类的一些总结。如果觉得还不错的话,就送我一个赞吧!如果本文对你有用的话,也欢迎收藏哦!