在深刻学习字符串类以前, 咱们先搞懂JVM是怎样处理新生字符串的. 当你知道字符串的初始化细节后, 再去写
String s = "hello"
或String s = new String("hello")
等代码时, 就能作到心中有数.java
public static void main(String[] args) {
String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1 == s2); //false
}
复制代码
String s1 = "hello";
干了什么.
String s2 = new String("hello");
的示意图
若是上面的知识理解起来没有问题的话, 下面看些难点的.面试
public static void main(String[] args) {
String s1 = new String("hello ") + new String("world");
s1.intern();
String s2 = "hello world";
System.out.println(s1 == s2); //true
}
复制代码
String s1 = new String("hello ") + new String("world");
的执行过程是这样子的:
s1.intern();
intern()
方法的详细介绍, 翻译过来的意思是: 当调用intern()
方法时, 首先会去常量池中查找是否有该字符串对应的引用, 若是有就直接返回该字符串; 若是没有, 就会在常量池中注册该字符串的引用, 而后返回该字符串.
String s2 = "hello world";
public class Main {
public static void main(String[] args) {
String s1 = "hello ";
String s2 = "world";
String s3 = s1 + s2;
String s4 = "hello world";
System.out.println(s3 == s4);
}
}
复制代码
这道压轴题是通过精心设计的, 它不但照应上面所讲的字符串常量池知识, 也引出了后面的话题.算法
String s3 = s1 + s2;
, 咱们不知道s1 + s2
在建立完新字符串"hello world"后是否会在字符串常量池进行注册. 说白了就是咱们不知道这行代码是以双引号""形式声明字符串, 仍是用new关键字建立字符串.javap -c 对应.class文件的绝对路径
, 按回车后便可看到反编译文件的代码段.C:\Users\liuyj>javap -c C:\Users\liuyj\IdeaProjects\Test\target\classes\forTest\Main.class
Compiled from "Main.java"
public class forTest.Main {
public forTest.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String world
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: ldc #8 // String hello world
27: astore 4
29: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_3
33: aload 4
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
46: return
}
复制代码
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String world
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
toString()
方法得到字符串hello world
, 并存放至s3.toString()
方法源码:@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
复制代码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
}
复制代码
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/** * The value is used for character storage. */
char[] value;
...
}
复制代码
并且经过StringBuilder和StringBuffer继承自同一个父类这点, 咱们能够推断出它俩的方法都是差很少的. 经过查看源码也发现确实如此, 只不过StringBuffer在方法上添加了synchronized
关键字, 证实它的方法绝大多数方法都是线程同步方法. 也就是说在多线程的环境下咱们应该使用StringBuffer以保证线程安全, 在单线程环境下咱们应使用StringBuilder以得到更高的效率.编程
既然如此, 咱们的比较也就落到了StringBuilder和String身上了.api
/** * 下面截取几个String类的方法 */
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
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);
}
/** * 下面截取几个StringBuilder类的方法 */
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public StringBuilder replace(int start, int end, String str) {
super.replace(start, end, str);
return this;
}
复制代码
public class Main {
public static int time = 50000;
public static void main(String[] args) {
long start = System.currentTimeMillis();
String s = "";
for(int i = 0; i < time; i++){
s += "test";
}
long end = System.currentTimeMillis();
System.out.println("String类使用时间: " + (end - start) + "毫秒");
}
}
//String类使用时间: 4781毫秒
复制代码
public class Main {
public static int time = 50000;
public static void main(String[] args) {
long start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for(int i = 0; i < time; i++){
sb.append("test");
}
long end = System.currentTimeMillis();
System.out.println("StringBuilder类使用时间: " + (end - start) + "毫秒");
}
}
//StringBuilder类使用时间: 5毫秒
复制代码
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String, 将""空字符串加载到栈顶
2: astore_1 //存放到s变量中
3: iconst_0 //把int型数0压栈
4: istore_2 //存到变量i中
5: iload_2 //把i的值压到栈顶(0)
6: getstatic #3 // Field time:I 拿到静态变量time的值, 压到栈顶
9: if_icmpge 38 // 比较栈顶两个int值, for循环中的断定, 若是i比time小就继续执行, 不然跳转
//从这里开始, 就是for循环部分
12: new #4 // class java/lang/StringBuilder
15: dup
16: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
19: aload_1
20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: ldc #7 // String test
25: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_1 //每拼接完一次, 就把新的字符串对象引用保存在第二个本地变量中
//到这里一次for循环结束
32: iinc 2, 1 //变量i加1
35: goto 5 //继续循环
38: return
复制代码
从反汇编代码中能够看到, 当用String类拼接字符串时, 每次都会生成一个StringBuilder对象, 而后调用两次append()方法把字符串拼接好, 最后经过StringBuilder的toString()方法new出一个新的字符串对象.数组
也就是说每次拼接都会new出两个对象, 并进行两次方法调用, 若是拼接的次数过多, 建立对象所带来的时延会下降系统效率, 同时会形成巨大的内存浪费. 并且当内存不够用时, 虚拟机会进行垃圾回收, 这也是一项至关耗时的操做, 会大大下降系统性能. 安全
下面是使用StringBuilder拼接字符串获得的反编译代码.bash
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: iload_2
11: getstatic #4 // Field time:I
14: if_icmpge 30
//从这里开始执行for循环内的代码
17: aload_1
18: ldc #5 // String test
20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
//到这里一次for循环结束
24: iinc 2, 1
27: goto 10
30: return
复制代码
String s = "hello " + "world";
, String类的效率会更高一点.