欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于Java里面的字符串拼接,你了解多少?

程序员文章站 2022-06-21 09:06:24
...

# 关于Java里面的字符串拼接,你了解多少?

###  前言

字符串拼接是我们日常开发中很常见的操作,虽然常见,但要是使用不当的的话,很有可能让你的程序处理效率降低一大半,所以我们有必要来重新了解一下Java里面的字符串操作。


### 基础知识回顾
(1)关于字符串字面量

```
String text="我是攻城师1";
```

上面的这行代码是我们最常见的声明方式,它会创建一个对象,并放在字符串常量池里面,关于常量池的内容,可以参考我之前的文章。

(2)关于new String

```
String text2=new String("我是攻城师2")
```

上面的这段代码是通过new的方式来创建字符串对象的,这里总共会产生2个对象,第一个是new创建的在堆内存里,第二个是字面量创建的在常量池里面。此外text2变量是在线程的栈里面,它只引用了堆里面的对象实例,这一点需要注意很多新手分不清楚他们之间的联系。

(3)关于String不可变性
Java的里面String类的有关操作,都会生成新的String实例,所以在大量修改操作时,应该使用可变的StringBuffer或者StringBuilder。


### 字符串拼接的几种方法


(1)使用 + 号

(2)使用字符串的concat方法

(3)StringBuffer的append方法

(4)StringBuilder的append方法

上面的四种就是Java里面所有的关于字符串拼接的方法,问题来了四种方法之间有什么区别?


* StringBuffer是线程安全的,StringBuilder是线程不安全的,大部分时候应该首选StringBuilder方法,它的字符串拼接效率最高,因为不会创建很多的String实例。
* +号与concat方法的:
首先+号是可以和各种类型拼接字符串的,而concat的参数必须是字符串类型,其次concat方法拼接null值是会抛空指针的,代码如下:


```
//DIFFERENCE 1
	String a = null;
	String b = "foo";
	String c = a + b; // c = nullfoo

	String d = b.concat(a); // 空指针
	String e = a.concat(b); // 空指针
		
	//DIFFERENCE 2
	String x = "1" + 2; // x = 12
	String y = "1".concat(2); // 编译错误
```

最后,他们在底层处理机制上也不同:


```
String c ="a" + "b";
```

上面的语句在编译的时候会转化成下面的代码:

```
c = new StringBuilder()
		.append("a")
		.append("b")
		.toString();
```

注意上面的代码会涉及三次的new char[]数组分配和数组拷贝动作。

而同样的操作使用concat方法,则只会涉及一次的数组分组和数组拷贝:

```
String c=”a“.concat("b")
```

源码如下:

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


### 字符串拼接的性能对比

下面我们分别对上面的四种方法在jdk8的mac系统上,进行1万次的拼接动作,看下性能表现,测试代码如下:


```
        final int loop_count=10000;

        // 1 +
        String s1="s1";
        long startTime=System.nanoTime();

        for (int i = 0; i < loop_count; i++) {
            s1=s1+Integer.toString(i);
        }
        long duration=System.nanoTime()-startTime;
        System.out.println(" + cost: "+duration/1000+" 微秒 ");



        // 2 concat
        String s2="s2";
        startTime=System.nanoTime();
        for (int i = 0; i < loop_count; i++) {
            s2=s2.concat(Integer.toString(i));
        }
        duration=System.nanoTime()-startTime;
        System.out.println(" concat cost: "+duration/1000+" 微秒 ");


        // 3 stringbuffer
        StringBuffer s3=new StringBuffer("s3");
        startTime=System.nanoTime();
        for (int i = 0; i < loop_count; i++) {
            s3.append(Integer.toString(i));
        }
        duration=System.nanoTime()-startTime;
        System.out.println(" StringBuffer cost: "+duration/1000+" 微秒 ");



        // 4 stringbuilder
        StringBuilder s4=new StringBuilder("s4");
        startTime=System.nanoTime();
        for (int i = 0; i < loop_count; i++) {
            s4.append(Integer.toString(i));
        }
        duration=System.nanoTime()-startTime;
        System.out.println(" StringBuilder cost: "+duration/1000+" 微秒 ");
```


结果输出如下:

```
 + cost: 432673 微秒 
 concat cost: 49118 微秒 
 StringBuffer cost: 1011 微秒 
 StringBuilder cost: 734 微秒
```


从上面的循环1万次拼接的结果来看+操作符在循环loop中拼接字符串最耗时,前两个结果与后两个结果完全不在一个量级上,因为前面的两种方法在每次循环时候都会生成一个新的String实例返回,而后面的两个则只需要最后使用一次,至于StringBuffer比StringBuilder耗时是因为前者是同步的方法,后者则不是。


### 总结

(1)在没有线程问题的情况下大量修改字符串优先考虑StringBuilder

(2)在有线程问题的情况下大量修改字符串优先考虑StringBuffer

(3)如果仅仅是两个字符串的拼接优先考虑String类的concat方法

(4)如果一次性拼接多个字符串,可以考虑使用非循环模式下的+号拼接
或者直接使用StringBuilder对象。

(5)最后还有一个不常用的方法String.join其内部用的是StringBuilder类,所以不再多说。