在深入学习字符串类之前, 我们先搞懂JVM是怎样处理新生字符串的. 当你知道字符串的初始化细节后, 再去写 String s = "hello"
或 String s = new String("hello")
等代码时, 就能做到心中有数.
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身上了.
/** * 下面截取几个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 复制代码
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";
最后欢迎关注我的 免费 知识星球, 我会在星球中持续更新系统的Java后端面试题分析, 将会囊括Java基础知识到主流框架原理, 力求深入每个知识点背后的原理. 还会分享关于编程的趣味漫画.
-----点击到星球参观-----