最近在研究String,对String用“+"拼接的过程很好奇,于是有了下面的描述。。。
本文先对两个字符串拼接进行了研究,然后再对两个字符串变量拼接进行,最后小结了一下,一起来看吧!!!
片段1:两个字符串拼接
public class Test { public static void main(String args[]) { String str = "hello" + "world"; } }
用javap查看class文件结构
从文件结构中我们可以看出,"hello" + "world"直接被编译成了"helloworld",并且将这个常量加载到操作数栈,再存储到局部变量表,最后return结束。
public class Test { public static void main(String args[]) { String str1 = "hello" + "world"; String str2 = "helloworld;
System.out.print(str.equals(str));
}
}
所以,上述代码执行equals的结果是true,String str = "hello" + "world" 和 String str = "helloworld"的效果是一样的。
延伸:上面讲述的是直接把字符串赋值给变量,那new String()是一个怎样的过程呢?
public class Test { public static void main(String args[]) { String str = new String("helloworld"); } }
用javap查看class文件结构
从文件结构中我们可以看出,首先不管如何会先new一个String,然后复制栈顶值且将该值压入栈顶,初始化String,再存储到局部变量表,最后return结束。
片段2:两个变量拼接
public class Test { public static void main(String args[]) { String str = "hello"; str = str + "world"; } }
以上这段代码会发生什么呢?
我们先来先看一下class文件
首先将变量str存储局部变量表里,接着new StringBuilder(),并初始化,然后调用StringBuilder.append()方法拼接字符串,再通过StringBuilder.toString()调用new String()得到一个新的字符串变量,再将这个变量存储到局部变量表,最后return结束。
所以代码片段2的过程其实为new StringBuilder().append(str).append("world").toString()。
既然String用加号的拼接过程用到了StringBuilder的append和toString方法,不如看看这两个方法的过程吧。
// StringBuilder的append方法 public StringBuilder append(String str) { // 调用父类AbstractStringBuilder的append方法 super.append(str); return this; } public AbstractStringBuilder append(String str) { // 若字符串为空,返回"null" if (str == null) return appendNull(); int len = str.length(); // 获取数组的大小,若是新的字符串的size大于char[]的length则new一个新的char[],并把原来的char赋值到新的char[]上 ensureCapacityInternal(count + len); // 把str放入char[],底层调用本地方法System.arraycopy str.getChars(0, len, value, count); count += len; return this; } // 获取容器大小 private int newCapacity(int minCapacity) { // 若新字符串 + 已存在的字符串的大小大于char[]的大小,则新容器的大小翻倍+2 int newCapacity = (value.length << 1) + 2; if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } // 若newCapacity<Integer.MAX_VALUE - 8则char[]大小取newCapacity;若Integer.MAX_VALUE- 8<newCapacity<Integer.MAX_VALUE,取则char[]大小取Integer.MAX_VALUE;若newCapacity>Integer.MAX_VALUE,抛错。 return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)? hugeCapacity(minCapacity): newCapacity; }
// 而StringBuilder的toString()调用的是new String() public String toString() { return new String(value, 0, count); }} public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { throw new StringIndexOutOfBoundsException(count); } if (offset <= value.length) { this.value = "".value; return; } } if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } // 根据拼接后的字符串长度new一个新的char[],并把原来的char[]数组的值复制到新的char[]上,节省内存 this.value = Arrays.copyOfRange(value, offset, offset+count); } }
小结
1、两个字符串直接拼接时,编译器对其进行了优化,此时String str = "hello" + "world"和String str = "helloworld"是一样的;
2、由上述的分析可以两个字符串的拼接的底层是通过StringBuilder的append方法先将字符串放在char[]数组中,再通过toString()调用new String()新建一个合适长度的char[],再把旧的char[]复制到新的char[]上完成的,而一个小小的”+“拼接的过程经过了3次System.arraycopy,过程复杂,每次“+”都会new StringBuilder又要toString,若是在for循环中使用这种方式,可谓是呜呼哀哉,并且每次循环一次就会new StringBuilder(),若循环的次数非常多会new非常多的StringBuilder。
最后,欢迎大家留言。。。