常用的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 codeif (minimumCapacity - value.length > 0) {value = Arrays.copyOf(value,newCapacity(minimumCapacity));}}/*** 获取新字符数组扩容后长度源码分析:* 在做扩容时,默认新的字符数组长度为:旧的字符数组的值的长度 * 2 + 2* 这么做的理由主要目的是:减少后续的扩容次数*/private int newCapacity(int minCapacity) {// overflow-conscious codeint 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稍慢
