Java.Lang.String是Java语言自带的字符串实现,它并非java的基本类型,但却和几乎每一个java程序都密切相关的一个基础java类。html
string类内部实际实现存储的字符数组在定义时用关键字final修饰,意味着这个属性是一个常量,在初始化以后就不能再被修改。这也同时代表全部对String对象的修改操做(包括append,substring,concat,replace,trim等),在具体实现中返回的都是一个全新的string对象副本。总结来讲,Java中的String具备不变性、不可继承性。java
这里会涉及到三个概念:虚拟机栈、Java堆和运行时常量池,在个人第一篇文章中都有描述。根据JDK源码中的规范,String类的使用方式有以下几种:c++
String str1 = new String("abc"); String str2 = "abc"; String str3 = "ab" + new String("c");
在具体应用中,这里的几种String对象的建立方式是基本没有区别的。但实质上这里有一些微小的差别:第一个字符串的建立方式str1指向的对象被分配到了Java堆中,且建立的时机是在程序运行时。str2指向的字符串对象在编译期就已经肯定,存放在运行时常量池中。str3的建立是一个比较复杂的过程,java虚拟机会从新组织对应的字节码,具体的过程在下文会分析。数组
在学习的过程当中,我也参考了不少前辈的文章,其中包括这篇《Java内存分配和String类型的深度解析》,文中做者在String的定义方法中提出了针对性的几点疑问,结合我自身的思考,我来尝试解答一下。安全
二者都是一个String类型的实例化对象,即便经过方法equals比较返回结果为true,二者在本质上都不会是同一个对象。app
一个最主要的区别就是内存中的位置不一样,固然大部分的常量池中的字符串常量是在编译器肯定的,除非很明确的调用string对象的intern方法返回(或建立)一个运行时常量池中的string对象,全部经过new操做符建立的string对象都会被分配到Java堆中。函数
虽说字符串常量”abc”是在编译器被肯定的字符串常量,被存放在运行时常量池,可是这个常量字符串仍是一个String类型的对象(这一点确实有别于c/c++语言中的常量的概念,也看出在java的哲学中万物都是对象),若是是一个标准的java对象,它能够调用String的方法。性能
在分析问题的过程当中,经过查看上文中提到的三行java代码对应的字节码(方法javap -c {具体要查看的*.class文件}),来确认个人本身的猜测,在这个过程当中也能够看出java编译器对源代码的处置,具体的字节码分析以下:学习
//0 ~ 9 对应的是第一行的java语句:String str1 = "abc"; 0: new #21 // class java/lang/String 3: dup /* **装载一个常量字符串 ,符号#23表明的字符串对象就是 “abc”, **常量字符串在程序运行以前就已经被建立 */ 4: ldc #23 // String abc /*str1不指向常量字符串“abc”, **而是将这个常量字符串做为构造函数的实参传入 **在java堆中从新建立了一个全新的对象 */ 6: invokespecial #25 // Method java/lang/String."<init>":(Ljava/lang/String;)V 9: astore_1 //从这里开始到12 都是第二行的Java代码: String str2 = "abc"; /* ** 直接调用运行时常量池中的对象 */ 10: ldc #23 // String abc 12: astore_2 //今后处开始到最后对应第三行的Java代码:String str3 = "ab" + new String("c"); /* **对于这种new 对象与 常量字符串相结合的方式, **JAVA编译器在处理过程当中建立了一个StringBuilder对象用于处理异构字符串的拼接工做 ** "ab"对应另外一个常量字符串 “c”则运行时动态建立的String对象 **/ 13: new #28 // class java/lang/StringBuffer 16: dup 17: ldc #30 // String ab 19: invokespecial #32 // Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V 22: new #21 // class java/lang/String 25: dup 26: ldc #33 // String c 28: invokespecial #25 // Method java/lang/String."<init>":(Ljava/lang/String;)V 31: invokevirtual #35 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 34: invokevirtual #39 // Method java/lang/StringBuffer.toString:()Ljava/lang/String; 37: astore_3 38: return
由此能够看出,在处理不一样方式构建的字符串拼接时,java编译器为咱们付出了额外的一些代价,在咱们的代码中,尽量少出现相似第三行那样的代码。若是是肯定的字符串常量,也尽量写成”ab” + “c”这样的形式,在编译器优化时会在常量池中找到现成的对象对象,会在性能上有很大的提高。 优化
同时咱们可以看到编译器默认用的字符串拼接器是StringBuffuer类,一般状况下咱们若是须要对几个动态的string对象作拼接用的都是StringBuilder类。StringBuffer与StringBuilder最本质的区别是:前者是线程安全的。那么若是只是明确的单线程环境下,在效率上编译器自补足的代码又会有更多性能上的欠缺。