谈谈Java字符串拼接

1."+"操作符和append方法

在我们初学String的时候,无数人跟我们说过

尽量不要使用 “+” 拼接字符串,效率不好,应该使用append,你自己循环拼接测一测时间就知道了 。

我们不妨来循环一百万次,看一看它们的区别。

// 1.for 循环中使用”+”号操作符。
long startTime1 = System.currentTimeMillis();
String a = "";
for (int i = 0; i < 1000000; i++) {
        a += "对";
}
long endTime1 = System.currentTimeMillis();
System.out.println(endTime1 - startTime1);

// 2.for 循环中使用 append。
long startTime2 = System.currentTimeMillis();
StringBuilder b = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
        b.append("对");
}
long endTime2 = System.currentTimeMillis();
System.out.println(endTime2 - startTime2);

这两段代码分别会耗时多长时间呢?在我的笔记本上测试出的结果是:
1)第一段代码耗时 146611 毫秒
2)第二段代码耗时 20 毫秒

可以看到确实差距巨大,这是为什么呢?
我们再看一下这段代码:

String a = "数码";
String b = "产品";
System.out.println(a + b);

反编译之后,第三行是这样的:

System.out.println((new StringBuilder(String.valueOf(a))).append(b).toString());

原来JVM在编译时会把“+”号操作符替换成 StringBuilder 的 append 方法。

现在我们终于明白一开始的那两段代码,耗时差距为什么那么大了。

  • 第一段代码,每次循环都会new一个StringBuilder,循环中 “+” 拼接被断成了十万条语句,那自然就会new十万次,创建了大量的 StringBuilder 对象。
  • 第二段代码至始至终只有一个 StringBuilder 对象。

至于StringBuffer,它和StringBuilder一样,只不过StringBuffer是线程安全的,因为它的 append 方法比 StringBuilder 多了一个关键字 synchronized。

2.concat 方法

String a = "中文";
String b = "Chinese";
System.out.println(a.concat(b));

这看上去和append方法好像没什么区别,那就只能看他们各自的源码了。
####1. append方法的源码:

@Override
public StringBuilder append(CharSequence s) {
      super.append(s);
      return this;
}

这看不出什么,继续深入看父类AbstractStringBuilder 的 append 方法:

public AbstractStringBuilder append(String str) {
      if (str == null) {
            return appendNull();
      }
      int len = str.length();
      ensureCapacityInternal(count + len);
      putStringAt(count, str);
      count += len;
      return this;
}

首先,判断拼接的字符串是否为null,如果是null,返回appendNull()方法。
那么appendNull()方法是怎么实现的呢:

private AbstractStringBuilder appendNull() {
    ensureCapacityInternal(count + 4);
    int count = this.count;
    byte[] val = this.value;
    if (isLatin1()) {
        val[count++] = 'n';
        val[count++] = 'u';
        val[count++] = 'l';
        val[count++] = 'l';
    } else {
            count = StringUTF16.putCharsAt(val, count, 'n', 'u', 'l', 'l');
    }
    this.count = count;
    return this;
}

原来appendNull的作用就是返回字符串“null”。
再看 ensureCapacityInternal是用来干什么的:

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    int oldCapacity = value.length >> coder;
    if (minimumCapacity - oldCapacity > 0) {
        value = Arrays.copyOf(value, newCapacity(minimumCapacity) << coder);
    }
}

原来, ensureCapacityInternal用来判断拼接后的字符数组长度是否超过当前值,如果超过,进行扩容并复制。然后将拼接的字符串复制到目标数组 value 中。

####2. concat方法的源码:

public String concat(String str) {
    int olen = str.length();
    if (olen == 0) {
        return this;
    }
    if (coder() == str.coder()) {
        byte[] val = this.value;
        byte[] oval = str.value;
        int len = val.length + oval.length;
        byte[] buf = Arrays.copyOf(val, len);
        System.arraycopy(oval, 0, buf, val.length, oval.length);
        return new String(buf, coder);
    }
    int len = length();
    byte[] buf = StringUTF16.newBytesFor(len + olen);
    getBytes(buf, 0, UTF16);
    str.getBytes(buf, len, UTF16);
    return new String(buf, UTF16);
}

1)获取待拼接的字符串长度

int olen = str.length();

如果待拼接的字符串的长度为 0,那么直接返回拼接前的字符串

if (olen == 0) {
return this;
}

2)如果两者的编码相同,直接通过System.arraycopy进行拷贝并返回新的 String 对象

if (coder() == str.coder()) {
byte[] val = this.value;
byte[] oval = str.value;
int len = val.length + oval.length;
byte[] buf = Arrays.copyOf(val, len);
System.arraycopy(oval, 0, buf, val.length, oval.length);
return new String(buf, coder);
}

3) 如果编码不同,则使用 UTF16 编码分别将二者的值拷贝到字节数组上,并返回新的 String 对象

int len = length();
byte[] buf = StringUTF16.newBytesFor(len + olen);
getBytes(buf, 0, UTF16);
str.getBytes(buf, len, UTF16);
return new String(buf, UTF16);

####通过源码分析,我们可以得出以下结论:
1)如果拼接的字符串是 null,concat 时候就会抛出 NullPointerException,“+”号操作符会当做是“null”字符串来处理。

2)如果拼接的字符串是一个空字符串 “”,那么 concat 的效率要更高一点。毕竟不需要 new StringBuilder对象。

3)如果拼接的字符串非常多,concat 的效率就会下降,因为创建的字符串对象越多,开销就越大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值