Java中五种拼接字符串方式的解读

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

1. 使用 + 拼接字符串,其实只是Java提供的一个语法糖,那么,我们就来解一解这个语法糖,看看他的内部原理到底是如何实现的。

    这样一段代码,我们把它生成的字节码进行反编译,看看结果。

    

String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
String hollis = wechat + "," + introduce;

    反编译后的代码块

String wechat = "Hollis";
String introduce = "\u6BCF\u65E5\u66F4\u65B0Java\u76F8\u5173\u6280\u672F\u6587\u7AE0";
String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();

通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。

那么也就是说,Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append

2. String.concat方法的源代码,看一下这个方法又是如何实现的。

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);
}

 

    这段代码首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的String对象并返回。通过源码我们也可以看到,经过concat方法,其实是new了一个新的String,这也就呼应到前面我们说的字符串的不变性问题上了。

3. StringBuffer和StringBuilder

    和String类类似,StringBuilder类也封装了一个字符数组, 与String不同的是,它并不是final的,所以他是可以修改的。另外,与String不同,字符数组中不一定所有位置都已经被使用,它有一个实例变量,表示数组中已经使用的字符个数

其append源码如下

public StringBuilder append(String str) {
   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会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展。

StringBufferStringBuilder类似,最大的区别就是StringBuffer是线程安全的,看一下StringBufferappend方法。

public synchronized StringBuffer append(String str) {
   toStringCache = null;
   super.append(str);
   return this;
}

该方法使用synchronized进行声明,说明是一个线程安全的方法。而StringBuilder则不是线程安全的。

4. StringUtils.join

    通过查看StringUtils.join的源代码,我们可以发现,其实他也是通过StringBuilder来实现的。

public static String join(final double[] array, final char separator, final int startIndex, final int endIndex) {
       if (array == null) {
           return null;
       }
       final int noOfItems = endIndex - startIndex;
       if (noOfItems <= 0) {
           return EMPTY;
       }
       final StringBuilder buf = new StringBuilder(noOfItems * 16);
       for (int i = startIndex; i < endIndex; i++) {
           if (i > startIndex) {
               buf.append(separator);
           }
           buf.append(array[i]);
       }
       return buf.toString();
   }

  既然有这么多种字符串拼接的方法,那么到底哪一种效率最高呢?我们来简单对比一下。

    

long t1 = System.currentTimeMillis();
//这里是初始字符串定义
for (int i = 0; i < 50000; i++) {
   //这里是字符串拼接代码
}
long t2 = System.currentTimeMillis();
System.out.println("cost:" + (t2 - t1));
+ cost:5119
StringBuilder cost:3
StringBuffer cost:4
concat cost:3623
StringUtils.join cost:25726

从结果可以看出,用时从短到长的对比是:

    StringBuilder < StringBuffer < concat < + < StringUtils.join

StringBufferStringBuilder的基础上,做了同步处理,所以在耗时上会相对多一些,这个很好理解。

StringUtils.join也是使用了StringBuilder,并且其中还是有很多其他操作,所以耗时较长,这个也容易理解。其实StringUtils.join更擅长处理字符串数组或者列表的拼接。

那么问题来了,前面我们分析过,其实使用+拼接字符串的实现原理也是使用的StringBuilder,那为什么结果相差这么多,高达1000多倍呢?

我们再把以下代码反编译下:

    

long t1 = System.currentTimeMillis();
String str = "hollis";
for (int i = 0; i < 50000; i++) {
   String s = String.valueOf(i);
   str += s;
}
long t2 = System.currentTimeMillis();
System.out.println("+ cost:" + (t2 - t1));


// 反编译后的代码

long t1 = System.currentTimeMillis();
String str = "hollis";
for(int i = 0; i < 50000; i++)
{
   String s = String.valueOf(i);
   str = (new StringBuilder()).append(str).append(s).toString();
}

long t2 = System.currentTimeMillis();
System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());

我们可以看到,反编译后的代码,在for循环中,每次都是new了一个StringBuilder,然后再把String转成StringBuilder,再进行append

而频繁的新建对象当然要耗费很多时间了,不仅仅会耗费时间,频繁的创建对象,还会造成内存资源的浪费。

所以,阿里巴巴Java开发手册建议:循环体内,字符串的连接方式,使用StringBuilder 的 append 方法进行扩展。而不要使用+

转载于:https://my.oschina.net/hcy8888/blog/3004489

你可能感兴趣的:(Java中五种拼接字符串方式的解读)