字符串拼接的几种方式

+ 号拼接

        通过+拼接是最常见的拼接方式,这个应该算是最简单的一种方式了,但是很遗憾得玩告诉你,阿里巴巴在他们的规范里面之处不建议在 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. ()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. ()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. ()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. ()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. ()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 不免一个好的选择。

你可能感兴趣的:(Java,java)