常用的Java内部的字符串拼接方式大概有四种
- 使用运算符“+”
- 推荐使用场景:简单的字符串拼接
- 使用String类的concat方法
- 推荐使用场景:简单的字符串拼接
- StringBuilder类的append方法
- 推荐使用场景:不需要保证线程安全的场景,效率最高
- StringBuffer类的append方法
- 推荐使用场景:需要保证线程安全的场景
字符拼接效率:StringBuilder.append > StringBuffer.append > String.concat > 运算符“+”
运算符“+”拼接字符串实现
- 本质:通过初始化一个新的StringBuilder对象,并且调用它的append方法来拼接多个字符串
分析如下:
/*
通过反编译下面的代码的字节码(javap -verbose YourClassName),可以了解到使用运算符“+”来拼接字符串的本质,是通过初始化一个新的StringBuilder对象,并且调用它的append方法,等价于:
String str = "";
str = (new StringBuilder()).append(str).append("a").toString();
str = (new StringBuilder()).append(str).append("b").toString();
str = (new StringBuilder()).append(str).append("c").append("d").toString();
*/
String str = "";
str = str + "a";
str = str + "b";
str = str + "c" + "d";
优点:
- 写法简洁,在简单的字符串拼接场景推荐直接使用
- 写法简洁,在简单的字符串拼接场景推荐直接使用
缺点:
本质:初始化一个新的String类来存储拼接后的字符串(因为String类是以字符数组常量的方式存储它的值,因此拼接后的字符串就必须要初始化新的String来存储)
源码分析如下
/**
* String类的concat方法源码分析:
* 首先,每个String类存储字符串的方式是:以字符数组char[]的形式,存储在常量value中
* 这里,我们假设原来的字符串存储常量为value1,待拼接字符串存储常量为value2
* 在调用concat方法时,会根据旧字符数组(value1)和待拼接字符串(str)的长度之和创建一个新的char数组(假设为newChar1),并且将旧的字符数组(value1)拷贝到新的字符数组中(假设为newChar1)
* 然后,再将待拼接字符串的存储常量(str的value2值)拷贝到字符数组newChar1后
* 最后,根据拼接后的字符数组newChar1,初始化一个新的字符串返回
*/
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
优点:
- 相对来说,写法也比较简洁,同样适用于简单的字符串拼接场景
- 在简单的字符串拼接场景中,性能比使用运算符“+”号稍好一点
- 相对来说,写法也比较简洁,同样适用于简单的字符串拼接场景
- 缺点:
- 每调用一次concat方法都会创建一个新的String对象;
- 当在循环中使用时,占用更多内存的同时,效率还非常低;
- 注意:
- 当每次循环调用concat的次数较低时,效率比使用运算符“+”号稍好
- 但当,每次循环调用concat的次数非常多时,效率不如使用运算符“+”号
```java // 可以尝试使用以下代码来校验运算符+号和调用concat的来比较其效率 long t1 = System.currentTimeMillis(); String str1 = “”; for (int i = 0; i < 10000; i++) { str1 = str1 + “a” + “b” + “c” + “d” + “e” + “f”; } long t2 = System.currentTimeMillis(); System.out.println(“使用加号拼接字符串效率: “ + (t2 - t1));
- 当每次循环调用concat的次数较低时,效率比使用运算符“+”号稍好
- 注意:
- 每调用一次concat方法都会创建一个新的String对象;
long t3 = System.currentTimeMillis(); String str2 = “”; for (int i = 0; i < 10000; i++) { str2 = str2.concat(“a”).concat(“b”).concat(“c”).concat(“e”).concat(“d”).concat(“f”); } long t4 = System.currentTimeMillis(); System.out.println(“使用concat拼接字符串效率: “ + (t4 - t3));
<a name="IpZ0M"></a>
### StringBuilder类的append方法实现
- 本质:StringBuilder类是继承了AbstractStringBuilder类,而StringBuilder的append正是调用了父类的append方法
- 源码分析如下
```java
/**
* StringBuilder类的append方法
* 它本质上是调用了父类实现的append方法,下面是父类的append具体实现
*/
public StringBuilder append(String str) {
super.append(str);
return this;
}
/**
* AbstractStringBuilder类的append方法源码分析:
* 首先,AbstractStringBuilder类有两个参数:
* - value参数:以字符数组char[]的形式存储字符串(变量)
* - count参数:存储字符串的实际长度(注意,与value参数的长度区分开)
* 其次,确定是否需要扩容旧的字符数组value的长度
* - 当拼接后的字符串长度要大于value的长度时,默认扩容长度为value长度 * 2 + 2,否则为拼接后字符串的长度
* 然后,判断是否需要扩容,则初始化一个新的字符数组(长度为第二步获取)newValue,将旧的value的值拷贝到新的字符数组newValue中,并重新为旧的value赋值(value = newValue;)
* 最后,将待拼接的字符串的字符数组值,拷贝到value参数中
*/
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;
}
/**
* AbstractStringBuilder类的扩容源码
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
/**
* 获取新字符数组扩容后长度源码分析:
* 在做扩容时,默认新的字符数组长度为:旧的字符数组的值的长度 * 2 + 2
* 这么做的理由主要目的是:减少后续的扩容次数
*/
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;
}
- 优点(其实是父类AbstractStringBuilder的优点)
- 通过校验扩容、字符数组扩容、拷贝等方法,极大地提高了字符串拼接效率
- 通过校验扩容、字符数组扩容、拷贝等方法,极大地提高了字符串拼接效率
- 缺点
- 完全是调用了父类的方法,方法本身并没有保证线程不安全
- 在简单的字符串拼接场景写法略显复杂
- 完全是调用了父类的方法,方法本身并没有保证线程不安全
StringBuffer类的append方法实现
- 实现方式:
- StringBuffer类也继承了AbstractStringBuilder类,而StringBuffer的append同样调用了父类的append方法
- 与StringBuilder的差别是,它的append方法使用了synchronized进行声明,说明它是一个线程安全的方法
- StringBuffer类也继承了AbstractStringBuilder类,而StringBuffer的append同样调用了父类的append方法
源码如下
/**
* StringBuffer类的append方法源码
*/
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
优点:
- 通过校验扩容、字符数组扩容、拷贝等方法,极大地提高了字符串拼接效率(其实是父类AbstractStringBuilder的优点)
- 保证了线程安全
- 通过校验扩容、字符数组扩容、拷贝等方法,极大地提高了字符串拼接效率(其实是父类AbstractStringBuilder的优点)
- 缺点:
- 因为方法本身保证了线程安全,因此字符拼接效率,相比于StringBuilder稍慢
- 在简单的字符串拼接场景写法略显复杂
- 因为方法本身保证了线程安全,因此字符拼接效率,相比于StringBuilder稍慢