+ 号拼接
通过+
拼接是最常见的拼接方式,这个应该算是最简单的一种方式了,但是很遗憾得玩告诉你,阿里巴巴在他们的规范里面之处不建议在 for 循环里面使用 “+” 进行字符串的拼接。这里的不建议,其实就是不允许的意思,只是人家说的比较委婉而已。事实上,现在还在拿 “+” 来做拼接的应该是比较少了吧。
阿里开发者社区的时候看到一篇文章《为什么阿里巴巴不建议在for循环中使用”+”进行字符串拼接》,里面提到这个 拼接符号 “+” 不是一个运算符重载,Java也并不支持这个所谓的运算符重载。作者提出这是 Java 的一个语法糖。
运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
测试
public static void main(String[] args) {
String str1 = "hello";
String str2 = "world";
String result = str1 + str2;
}
字节码文件
L0
LINENUMBER 12 L0
LDC "hello"
ASTORE 1
L1
LINENUMBER 13 L1
LDC "world"
ASTORE 2
L2
LINENUMBER 14 L2
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 3
从字节码文件可以看出:String result = str1 + str2
被JDK编译器优化成了
String result = new StringBuilder().append(str1).append(str2);
若将其拆分
String s = "hello";
s += "world";
查看字节码文件
L5
LINENUMBER 15 L5
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 5
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 5
L6
LINENUMBER 16 L6
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 5
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 3
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 5
L7
LINENUMBER 17 L7
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 5
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 4
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 5
可以看到,每次拼接都会创建一个StringBuilder
String.concat(String str)
concat方法是String给我们提供的拼接字符串的方法,内部实现就是 将字符数组扩容后形成一个新的字符数组 buf , 再将参数 str 加进去。最后再将这个字符数组转成字符串。
public static void main(String[] args) {
String str1 = "hello";
String str2 = "world";
String result = str1.concat(str2);
}
源码描述如下:
将指定的字符串连接到该字符串的末尾。如果参数字符串的长度为0,则返回此String对象。否则,返回一个String对象,该对象表示一个字符序列,该字符序列是此String对象表示的字符序列与参数字符串表示的字符序列的串联。
源码
public String concat(String str) {
int otherLen = str.length(); // 获取参数字符串长度
if (otherLen == 0) {
return this; // 参数长度为0,返回自身
}
int len = value.length; //获取自身长度
char buf[] = Arrays.copyOf(value, len + otherLen); // 得到一个包含当前字符序列,长度为 // 两者之和的字符数组
str.getChars(buf, len); // 从当前字符序列长度开始,将参数的字符序列写入buf字符数组
return new String(buf, true); // 创建新的String对象并返回。
}
StringBuffer / StringBuilder
这两个应该可以说是一家的孪生兄弟了。做字符串拼接都是 append() 方法:
StringBuilder 里面的 append(String str) 的方法如下: 其实和 concat 方法差不多是吧。
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
// 这是 super.append()
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;
}
StringBuffer 里面的 append(String str)的方法如下:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
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;
}
实现的 append 方法是一样的,唯一的不同就是 StringBuffer 是一个线程安全的拼接
测试
public static void main(String[] args) {
String str1 = "hello";
String str2 = "world";
String result = new StringBuilder().append(str1).append(str2).toString();
}
查看字节码文件
L0
LINENUMBER 13 L0
LDC "hello"
ASTORE 1
L1
LINENUMBER 14 L1
LDC "world"
ASTORE 2
L2
LINENUMBER 15 L2
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 3
可以看出,与 +
号拼接一致。
String 字符串拼效率对比
先知道一点,String在Java中是不可变对象,因此每次拼接都是生成新的String对象,为了解决频繁的内存开辟消耗资源,才有了StringBuilder类。在+拼接过程中,JDK默认优化成为StringBuilder以提高运行效率。
但是这里又出现了一个问题,当且仅有两个字符串拼接生成一个新的字符串,这个默认优化的优势就体现不出来了。因为本来只需要三份String空间,默认优化StringBuilder的情况下,还需要一份StringBuilder的空间,多开辟了一份空间,肯定会对性能有所影响,
现在问题来了,以上的这么多方法都好用,怎么选?
不涉及循环的,就是那种很简单的那种拼接,就用 + ,简单方便 ;
非循环体中的字符串拼接,若只是两个字符串拼接,推荐使用
concat
。涉及到循环的,比如说 for 的,可以考虑使用 StringBuilder , 要求线程安全的就选择 StringBuffer ;
有 List 这种的,StringJoiner 不免一个好的选择。