Java字符串拼接
字符串拼接
在Java中,String是⼀个不可变类,所以String对象⼀旦在堆中被创建出来就不能修改。
1package java.lang;
2//import ...
3public final class String
4implements java.io.Serializable, Comparable<String>, CharSequence {
5/** The value is used for character storage. */
6private final char value[];
7}
Java字符串其实是基于字符数组实现的,该数组被关键字final标注,⼀经赋值就不可修改。
既然字符串是不可变的,那么字符串拼接⼜是怎么回事呢?
其实所谓的字符串拼接,都是重新⽣成了⼀个新的字符串(JDK7开始,substring() 操作也是重新⽣成⼀个新的字符串)。下⾯⼀段字符串拼接代码:
1String s = "hello ";
2s = s.concat("world!");
其实⽣成了⼀个新字符串,s最终保存的是⼀个新字符串的引⽤,如下图所⽰:
Java
+ 语法糖
在Java中,拼接字符串最简单的⽅式就是直接使⽤符号+来拼接,如:
1public class Main2 {
2public static void main(String[] args) {
3String s1 = "hello " + "world " + "!";
4String s2 = "xzy ";
5String s3 = s2 + s1;
6}
7
8private void concat(String s1) {
9String s2 = "xzy" + s1;
10}
11}
这⾥要特别说明⼀点,有⼈把Java中使⽤+拼接字符串的功能理解为运算符重载。其实并不是,Java是不⽀持运算符重载的,这其实只是Java提供的⼀个语法糖。
编译,反编译上⾯的代码:
1public class Main2 {
2public Main2() {
3}
4
5public static void main(String[] var0) {
6String var1 = "hello world !";
7String var2 = "xzy ";
8(new StringBuilder()).append(var2).append(var1).toString();
9}
10
java重载11private void concat(String var1) {
12(new StringBuilder()).append("xzy").append(var1).toString();
13}
14}
通过查看反编译后的代码,我们发现,使⽤ + 进⾏字符串拼接,最终是通过StringBuilder,创建⼀个新的String对象。
除了使⽤+拼接字符串之外,还可以使⽤String类中的⽅法concat⽅法来拼接字符串,如:
1public static void main(String[] args) {
2String s1 = "hello " + "world " + "!";
3String s2 = "xzy ";
4String s3 = s2.concat(s1);
5}
concat⽅法的源码如下:
1public final class String
2implements java.io.Serializable, Comparable<String>, CharSequence {
3
4/** The value is used for character storage. */
5private final char value[];
6
7/**
8* Concatenates the specified string to the end of this string.
9* <p>
10* If the length of the argument string is {@code 0}, then this
11* {@code String} object is returned. Otherwise, a
12* {@code String} object is returned that represents a character
13* sequence that is the concatenation of the character sequence
14* represented by this {@code String} object and the character
15* sequence represented by the argument string.<p>
16* Examples:
17* <blockquote><pre>
18* "cares".concat("s") returns "caress"
19* "to".concat("get").concat("her") returns "together"
20* </pre></blockquote>
21*
22* @param str the {@code String} that is concatenated to the end
23* of this {@code String}.
24* @return a string that represents the concatenation of this object's
25* characters followed by the string argument's characters.
26*/
27public String concat(String str) {
28int otherLen = str.length();
29if (otherLen == 0) {
30return this;
31}
32int len = value.length;
33char buf[] = pyOf(value, len + otherLen);
35return new String(buf, true);
36}
37
38/**
39* Copy characters from this string into dst starting at dstBegin.
40* This method doesn't perform any range checking.
41*/
42void getChars(char dst[], int dstBegin) {
43System.arraycopy(value, 0, dst, dstBegin, value.length);
44}
45}
1//创建⼀个长度为newLength的字符数组,然后将original字符数组中的字符拷贝过去。
2public static char[] copyOf(char[] original, int newLength) {
3char[] copy = new char[newLength];
4System.arraycopy(original, 0, copy, 0,
5Math.min(original.length, newLength));
6return copy;
7}
从上⾯的源码看出,使⽤a.concat(b)拼接字符串a b,创建了⼀个长度为a.length + b.length的字符数组,a和b先后被拷贝进字符数组,最后使⽤这个字符数组创建了⼀个新的String对象。
StringBuffer 和
关于字符串,Java中除了定义了⼀个可以⽤来定义字符串常量的String类以外,还提供了可以⽤来定义字符串变量的StringBuffer类、StringBuilder,它的对象是可以扩充和修改的,如:
1public static void main(String[] args) {
2StringBuffer stringBuffer = new StringBuffer();
3String s1 = "hello " + "world " + "!";
4String s2 = "xzy ";
5String s3;
6stringBuffer.append(s1).append(s2);
7s3 = String();
8}
1public static void main(String[] args) {
2StringBuilder stringBuilder = new StringBuilder();
3String s1 = "hello " + "world " + "!";
4String s2 = "xzy ";
5String s3;
6stringBuilder.append(s1).append(s2);
7s3 = String();
8}
接下来看看StringBuffer和StringBuilder的实现原理。
StringBuffer和StringBuilder都继承⾃AbstractStringBuilder,下⾯是AbstractStringBuilder的部分源码:
1abstract class AbstractStringBuilder implements Appendable, CharSequence {
2/**
3* The value is used for character storage.
4*/
5char[] value;
6
7/**
8* The count is the number of characters used.
9*/
10int count;
11}
与String类似,AbstractStringBuilder也封装了⼀个字符数组,不同的是,这个字符数组没有使⽤final关键字修改,也就是所,这个字符数组是可以修改的。还要⼀个差异就是,这个字符数组不⼀定所有位置都要被占满,AbstractStringBuilder中有⼀个count变量同来记录字符数组中存在的字符个数。
试着看看StringBuffer、StringBuilder、AbstractStringBuilder中append⽅法的源码:
1public final class StringBuffer
2extends AbstractStringBuilder
3implements java.io.Serializable, CharSequence{
4
5/**
6* A cache of the last value returned by toString. Cleared
7* whenever the StringBuffer is modified.
8*/
9private transient char[] toStringCache;
10
11@Override
12public synchronized StringBuffer append(String str) {
13toStringCache = null;
14super.append(str);
15return this;
16}
17}
1public final class StringBuilder
2extends AbstractStringBuilder
3implements java.io.Serializable, CharSequence{
4
5@Override
6public StringBuilder append(String str) {
7super.append(str);
8return this;
9}
10}
1abstract class AbstractStringBuilder implements Appendable, CharSequence {
2/**
3* The value is used for character storage.
4*/
5char[] value;
6
7/**
8* The count is the number of characters used.
9*/
10int count;
11
12/**
13* Appends the specified string to this character sequence.
14* <p>
15* The characters of the {@code String} argument are appended, in
16* order, increasing the length of this sequence by the length of the
17* argument. If {@code str} is {@code null}, then the four
18* characters {@code "null"} are appended.
19* <p>
20* Let <i>n</i> be the length of this character sequence just prior to
21* execution of the {@code append} method. Then the character at
22* index <i>k</i> in the new character sequence is equal to the character
23* at index <i>k</i> in the old character sequence, if <i>k</i> is less
24* than <i>n</i>; otherwise, it is equal to the character at index
25* <i>k-n</i> in the argument {@code str}.
26*
27* @param str a string.
28* @return a reference to this object.
29*/
30public AbstractStringBuilder append(String str) {
31if (str == null)
32return appendNull();
33int len = str.length();
34ensureCapacityInternal(count + len);
35//拷贝字符到内部的字符数组中,如果字符数组长度不够,进⾏扩展。
37count += len;
38return this;
39}
40}
可以观察到⼀个⽐较明显的差异,StringBuffer类的append⽅法使⽤synchronized关键字修饰,说明StringBuffer的append⽅法是线程安全的,为了实现线程安全,StringBuffer牺牲了部分性能。
既然有这么多种字符串拼接的⽅法,那么到底哪⼀种效率最⾼呢?我们来简单对⽐⼀下。
1long t1 = System.currentTimeMillis();
2//这⾥是初始字符串定义
3for (int i = 0; i < 50000; i++) {
4//这⾥是字符串拼接代码
5}
6long t2 = System.currentTimeMillis();
7System.out.println("cost:" + (t2 - t1));
1public class Main2 {
2public static void main(String[] args) {
3test1();
4test2();
5test3();
6test4();
7}
8
9public static void test1() {
10long t1 = System.currentTimeMillis();
11String str = "";
12for (int i = 0; i < 50000; i++) {
13String s = String.valueOf(i);
14str += s;
15}
16long t2 = System.currentTimeMillis();
17System.out.println("+ cost:" + (t2 - t1));
18}
19
20public static void test2() {
21long t1 = System.currentTimeMillis();
22String str = "";
23for (int i = 0; i < 50000; i++) {
24String s = String.valueOf(i);
25str = at("hello");
26}
27long t2 = System.currentTimeMillis();
28System.out.println("concat cost:" + (t2 - t1));
29}
30
31public static void test3() {
32long t1 = System.currentTimeMillis();
33String str;
34StringBuffer stringBuffer = new StringBuffer();
35for (int i = 0; i < 50000; i++) {
36String s = String.valueOf(i);
37stringBuffer.append(s);
38}
39str = String();
40long t2 = System.currentTimeMillis();
41System.out.println("stringBuffer cost:" + (t2 - t1));
42}
43
44public static void test4() {
45long t1 = System.currentTimeMillis();
46String str;
47StringBuilder stringBuilder = new StringBuilder();
48for (int i = 0; i < 50000; i++) {
49String s = String.valueOf(i);
50stringBuilder.append(s);
51}
52str = String();
53long t2 = System.currentTimeMillis();
54System.out.println("stringBuilder cost:" + (t2 - t1));
55}
56}
我们使⽤形如以上形式的代码,分别测试下五种字符串拼接代码的运⾏时间。得到结果如下:
1+ cost:7135
2concat cost:1759
3stringBuffer cost:5
4stringBuilder cost:5
从结果可以看出,⽤时从短到长的对⽐是:
StringBuilder < StringBuffer < concat < +
那么问题来了,前⾯我们分析过,其实使⽤+拼接字符串的实现原理也是使⽤的StringBuilder,那为什么结果相差这么多,⾼达1000多倍呢?反编译上⾯的代码:
1/*
2* Decompiled with CFR 0.149.
3*/
4package com.learn.java;
5
6public class Main2 {
7public static void main(String[] arrstring) {
12}
13
14public static void test1() {
15long l = System.currentTimeMillis();
16String string = "";
17for (int i = 0; i < 50000; ++i) {
18String string2 = String.valueOf(i);
19string = new StringBuilder().append(string).append(string2).toString();
20}
21long l2 = System.currentTimeMillis();
22System.out.println(new StringBuilder().append("+ cost:").append(l2 - l).toString());
23}
24
25public static void test2() {
26long l = System.currentTimeMillis();
27String string = "";
28for (int i = 0; i < 50000; ++i) {
29String string2 = String.valueOf(i);
30string = at("hello");
31}
32long l2 = System.currentTimeMillis();
33System.out.println(new StringBuilder().append("concat cost:").append(l2 - l).toString());
34}
35

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。