java基础提高-字符串拼接

转载请注明出处,如有错误,麻烦指出~谢

开始先来个面试题:

使用+可以连接两个字符串(string对象),那么,是怎么样进行连接的?

当我们使用“+”来拼接两个字符串时,比如String str = str1+str2会进行如下操作:
1.调用String.valueOf(Obj),其中obj为String类型的变量,如上面提到的str1和str2,String.valueOf()的原理为:return obj == null?"null":obj.toString();
2.产生StringBuilder,调用StringBuilder(str1)的构造方法,把StringBuilder初始化,长度为str1.length+16,并且调用append(str1);
3.继续调用append(str2);
4.最后调用StringBuilder.toString()返回结果
所以最后的结论应该为:StringBuilder.append(str1).append(str2).toString()。应该注意的是StringBuffer和StringBuilder的扩容策略:
当字符串缓冲区容量不足时,原有容量将会加倍,以新的容量来申请内存空间,建立新的char数组,然后将原数组中的内容复制到这个新的数组当中。
因此,对于大对象的扩容会涉及大量的内存复制操作。所以,如果能够预先评估StringBuilder或StringBuffer的大小,将能够有效的节省这些操作,从而提高系统的性能。

这里在提及一些字符串拼接方式和性能:

1.使用+拼接两个字符串

            如果你用”+”来连接固定长度的字符串,可能性能上会稍受影响,但是如果你是在循环中来”+”多个串的话,性能将  指数倍的下降

    如下:
public void implicitUseStringBuilder(String[] values) {
		   String result = "";
	        for (int i = 0 ; i < values.length; i ++) {
		    result += values[i];
		}
			System.out.println(result);
	}
    使用javac编译,使用javap查看
public void implicitUseStringBuilder(java.lang.String[]);
	Code:
		0: ldc      #11         // String 
		2: astore_2
		3: iconst_0
		4: istore_3
		5: iload_3
		6: aload_1
		7: arraylength
		8: if_icmpge   38
		11: new      #5         // class java/lang/StringBuilder
		14: dup
		15: invokespecial #6         // Method java/lang/StringBuilder."<init>":()V
		18: aload_2
		19: invokevirtual #7         // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
		22: aload_1
		23: iload_3
		24: aaload
		25: invokevirtual #7         // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
		28: invokevirtual #8         // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
		31: astore_2
		32: iinc     3, 1
		35: goto     5
		38: getstatic   #9         // Field java/lang/System.out:Ljava/io/PrintStream;
		41: aload_2
		42: invokevirtual #10         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
		45: return
其中8: if_icmpge 38 和35: goto 5构成了一个循环。8: if_icmpge 38的意思是如果JVM操作数栈的整数对比大于等于(i < values.length的相反结果)成立,则跳到第38行(System.out)。35: goto 5则表示直接跳到第5行。
但是这里面有一个很重要的就是StringBuilder对象创建发生在循环之间,也就是意味着有多少次循环会创建多少个StringBuilder对象,这样明显不好。代码应该这样写:

public void explicitUseStringBuider(String[] values) {
	StringBuilder result = new StringBuilder();
	for (int i = 0; i < values.length; i ++) {
	    result.append(values[i]);
	}
}

2.使用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);
    }
char数组的拷贝再重新生成String,其实通过反编译知道concat()方法使用了StringBuilder,concat()的性能应该和StringBuilder的一样好,但是由于额外的创建StringBuilder和做.append(str).append(str).toString()的操作,使得concat的性能会受到一些影响

3.使用StringBuilder来拼接

源码:
                        public StringBuilder append(String str) {
				super.append(str);
				return this;
			}
			父类的append方法实现
			public AbstractStringBuilder append(String str) {
				if (str == null)
					return appendNull();
				int len = str.length(); //连接的字符串长度
				ensureCapacityInternal(count + len); //重新给value(char[])数组赋值,数组长度变为为当前字符串长度+传入的新字符串长度之和,值为当前的值
				str.getChars(0, len, value, count);//为value数组赋值,值为当前字符串+新字符串
				count += len;//标记后移
				return this;
			}
			private void ensureCapacityInternal(int minimumCapacity) {
				// overflow-conscious code
				
				if (minimumCapacity - value.length > 0) {
					value = Arrays.copyOf(value,
							newCapacity(minimumCapacity));//char[] value;
				}
			}
			public String toString() {
				// Create a copy, don't share the array
				return new String(value, 0, count);
			}



4.使用StringBuffer(线程安全)

public synchronized StringBuffer append(String str) {
			toStringCache = null;
			super.append(str);
			return this;
		}
综合以上可以发现,StringBuffer是线程安全的,有加锁开销,效率略低。StringBuilder非线程安全,不用加锁,效率更高。一般字符串相加不会有多线程操作,
所以推荐使用StringBuilder。另外,事实上我们在java里写的字符串相加的操作被编译后都是通过new一个StringBuffer或StringBuilder对象来操作的,所以不会产生大量的String对象

附上测试代码:(注意导包)

                import org.junit.Test;
		import org.slf4j.Logger;
		import org.slf4j.LoggerFactory;
		
		public class StringTest {
	
			private final Logger logger = LoggerFactory.getLogger(this.getClass());
			
			@Test
			public void testPlus() {
				String s = "";
				long ts = System.currentTimeMillis();
				for (int i = 0; i < 10000; i++) {
					s = s + String.valueOf(i);
				}
				long te = System.currentTimeMillis();
				logger.info("+ cost {} ms", te - ts);
			}
			
			@Test
			public void testConcat() {
				String s = "";
				long ts = System.currentTimeMillis();
				for (int i = 0; i < 10000; i++) {
					s = s.concat(String.valueOf(i));
				}
				long te = System.currentTimeMillis();
				logger.info("concat cost {} ms", te - ts);
			}
			@Test
			public void testStringBuffer() {
				StringBuffer sb = new StringBuffer();
				long ts = System.currentTimeMillis();
				for (int i = 0; i < 10000; i++) {
					sb.append(String.valueOf(i));
				}
				sb.toString();
				long te = System.currentTimeMillis();
				logger.info("StringBuffer cost {} ms", te - ts);
			}
			
			@Test
			public void testStringBuilder() {
				StringBuilder sb = new StringBuilder();
				long ts = System.currentTimeMillis();
				for (int i = 0; i < 10000; i++) {
					sb.append(String.valueOf(i));
				}
				sb.toString();
				long te = System.currentTimeMillis();
				logger.info("StringBuilder cost {} ms", te - ts);
			}
		}
		运行结果:
[06 13:54:27,336 INFO ] [main] StringTest - StringBuilder cost 8 ms
[06 13:54:27,729 INFO ] [main] StringTest - + cost 391 ms
[06 13:54:27,731 INFO ] [main] StringTest - StringBuffer cost 1 ms
[06 13:54:27,904 INFO ] [main] StringTest - concat cost 172 ms
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值