Java 杂谈

Java String关键字学习

2019-05-25  本文已影响0人  jwfy

在学习String类之前先看看如下的代码块

public String gets1() {
    return "" + System.currentTimeMillis();
}

public String gets2() {
    return String.valueOf(System.currentTimeMillis());
}

public String gets3() {
    return new String("") + System.currentTimeMillis();
}

很显然三个方法都是输出当前时间戳string类型的数据,那么他们有什么不同呢?反编译得出三个方法的操作细节

public java.lang.String gets1();
Code:
   0: new           #2                  // class java/lang/StringBuilder
   3: dup
   4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
   7: ldc           #4                  // String
   9: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  12: invokestatic  #6                  // Method java/lang/System.currentTimeMillis:()J
  15: invokevirtual #7                  // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
  18: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  21: areturn'
  
public java.lang.String gets2();
Code:
   0: invokestatic  #6                  // Method java/lang/System.currentTimeMillis:()J
   3: invokestatic  #9                  // Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6: areturn

public java.lang.String gets3();
Code:
   0: new           #2                  // class java/lang/StringBuilder
   3: dup
   4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
   7: new           #10                 // class java/lang/String
  10: dup
  11: ldc           #4                  // String
  13: invokespecial #11                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
  16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  19: invokestatic  #6                  // Method java/lang/System.currentTimeMillis:()J
  22: invokevirtual #7                  // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
  25: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  28: areturn

很明显上述三个方法中方法2是最优的,那么这是为什么呢?接下来就深入学习一下String 类。在学习之前先了解下JVM的组成结构

1、创建字符串

创建字符串一般就两种方法

第一种方法是在栈中创建一个String类型的引用s1,然后在常量池中寻找,如果常量池中存在hello的字符串数据,则直接把s1指向常量池中hello的地址;否则会在常量池中创建hello这个字符串数据,然后把s1指向新创建的hello地址。

第二种方法是在栈中创建一个String类型的引用s2,然后在常量池中寻找,如果常量池中存在该数据,则在堆中复制拷贝该数据,然后把s2指向堆中新建的地址;否则会创建一个字符串存放在常量池中,然后进行拷贝一份到堆中,s2指向堆的地址。

通过new创建的string一定会在堆中创建一份数据,同时常量池肯定有一个值的备份操作;而单独的字符串则是直接指向常量池,所以一般还是使用字符串更好些,具体可看如下的图解

创建字符串

2、字符串 + 操作

System.out.println(s1 == s2);  // true
System.out.println(s2 == s3);  // false
System.out.println(s1 == s3);  // false

代码中"hello" + "world"在编译期已经知道了数据情况(使用javac编译查看class文件会发现s1和s2是一致的),JVM会自动优化使得s1和s2是一样的,s3由于有new操作,所以需要StringBuilder的append完成,具体可看如下图解。

字符串 + 操作

3、字符串变量 + 操作

System.out.println(s3 == s4);  // true

这里的样例和上一个样例存在一些差别,这里的s4是由s1 + s2获得的,在编译期无法感知到其实际值,在运行期时会利用StringBuilder的append剩下一个新的String对象,所以s3指向的是常量池,而s4指向的堆,两者自然是不一样的。

s4 = new StringBuilder()).append(s1).append(s2).toString()

4、带final的字符串变量 + 操作

System.out.println(s3 == s4);  // true
System.out.println(s3 == s5); // false

javac编译之后的class文件如下

String var1 = "world";
String var2 = "helloworld";
String var3 = "helloworld";
String var4 = "hello" + var1;
System.out.println(var2 == var3);
System.out.println(var2 == var4);

添加了final关键字修饰的变量在编译期会被对应的字符串直接替换掉,相当于字符串数据,而包含了字符串变量的+操作则依旧是使用了StringBuilder

5、intern() 方法

String.intern方法是一个native方法,获取的是当前字符串在常量池的数据,如果常量池存在该数据则直接返回,如果不存在则把该数据添加到常量池中后返回,所以有String s1 = "abc" 和 String s2 = s1.intern() 中的s1 == s2String s1="world" 和 String s2=new String("world") 中 s1.intern() == s2.intern()

总结

String 本身是final类型的类,在日常使用中需要频繁的做字符串合并操作时,尽可能的使用StringBuilder(如需要考虑线程安全则使用StringBuffer),降低无谓的字符串创建操作,在保证安全的情况下,提高效率!

上一篇下一篇

猜你喜欢

热点阅读