String StringBuffer StringBuilder
相信在面试过程中很多面试官都会问到String,StringBuffer,StringBuilder的区别。但是对于它的机制,原理会头晕。这就要拿出JVM的工作原理了。
大家都知道String是由“字符”组成的串,在程序中使用的频率很高,String是java中的一个类,但又是一个特殊的类。具体特殊在哪?
1、 String类对象的创建方式有2种:
方式一:String str = new String("hello world"); //利用构造器创建。这是java类中很普遍的
方式二:String str = "hello world"; //这个类似于基本类型赋值,但是它又不是基本类型。
2、java class文件结构和常量池
Java程序要运行,首先需要编译器把源文件编译成字节码文件(即.class文件),然后由JVM来解释执行。
class文件是8bit的二进制流。文件中最开始的4个字节组成的叫magic(魔数),其作用在于分辨是否为class文件。紧接着是version,contant pool(常量池),Access rights ,Implemented interfaces,fields等等。
package com.wireless.xuwei;
public class StringDemo {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println("hello world!");
}
}
此时上述代码中的"hello world!"字符串字面值被编译之后,可以清楚的看到存放在了class常量池中的字符串常量表中。如图所示:
3、 JVM运行class文件的原理
源码被编译成class文件之后,JVM就要运行这个class文件。它首先会先用类装载器ClassLoader来加载class文件。(我们在开发过程中导入的jar包通常都是把class打成jar包,如果该包中的class的方法@hide,需要用到反射机制,此时也是通过类加载器加载这个class文件作为Class类的一个实例,再通过这个实例去获取它的属性,构造器,类方法或者是成员方法等)。当JVM把class文件加载完成后,会创建许多内存数据结构来存放class文件中的字节数据。如class文件中的属性,常量,类方法,方法中的二进制指令序列等信息。当然在运行的时候,需要为方法创建栈帧等。这些数据都会被JVM放到“方法区”,“堆”,“栈”。
4、拘留字符串对象
在java源码中的每一个字面值字符串(如String str = "java"),其中"java"就是字面值字符串,它会在编译成class文件时,形成标志号为8(CONSTANT_String_info)的常量表。当JVM加载class 文件时,会为对应的常量池建立一个内存数据结构,并存放在方法区中。同时JVM又会为字符串字面值"java"在堆中创建一个新的String对象,该对象叫拘留字符串对象。同时然后把常量表中的入口地址转为堆中挽留字符串对象的地址。
package com.wireless.xuwei;
public class StringDemo {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println("hello world!");
String s1 = "a"; //s1指向的是拘留对象地址
String s2 = "a";
System.out.println(s1 == s2); //true 由此可见,源码中所有相同字面值的字符串
//都只能建立一个唯一的拘留字符串对象。
String str = new String("a");//在堆中分配一个String类对象的空间。
//str指向的是该对象的地址
String str2 = new String("a");
System.out.println(str == str2);//false 因为二者都是指对String类对象的地址值。但地址值不一样
String s3 = s1 + s2;
String aa = "aa";
System.out.println(aa == s3);//false 因为s1是指向拘留对象的址,在进行"+"
//运行时,JVM会在堆中创建一个StringBuilder的类,内容为挽留对象的内容。然后再
//调用append()把s2指向的挽留对象值追加进来,然后StringBuilder会调用toString()
//会在堆中创建一个String类的对象,并将该String类的对象地址值给了s3
System.out.println(aa == "a" + "a");//true 因为在java中字面值的运行都是在编译期间就执行了。
//即aa指向的是一个拘留对象引用值,内容为"aa",而"a"+"a"在编译期间就成为了"aa"
//此时JVM发现堆中已经存在内容一样的挽留对象,则不再创建新的对象,即二者即为同一个。
}
}
5、 StringBuffer StringBuilder
StringBuffer它是一个线程安全的,可调用内部的一些方法(如appen())来达到改变字符串。
StringBuilder是一个非线程安全的。但是它的性能要超过StringBuffer,二者的使用几乎一样。
6、 总结
一) 在编译阶段就能确定的,即只有字面量字符串进行“+”时,选用String性能最好;如果字符串是从其他来 的,如String str = "a"; String str2 = str + "bc";它实质上就执行上述的JVM的顺序了。即先创建一个 StringBuilder类的对象。内容为拘留对象的内容"a",再执行append(),把另外一个挽留对象的内 容“bc”也加进来,再调用toString()方法再在堆中创建一个新的String类的对象。
二) 如果在进行“+”运算时,字符串内容是来自其他的String,"+"操作过多时会造成内存溢出的。因为 每”+“一次就创建一个新的对象,过多了JVM会来不及回收而造成内存溢出。
三)在没有线程安全要求的情况下,应选择StringBuilder,性能远强于StringBuffer