前阵子和同事在吃饭时聊起Java的String,以为本身以前的笔记写的略显零散。故此又从新整理了一下。java
String在Java中算是一个有意思的类型,是final类型,所以不能够继承这个类、不能修改这个类。设计模式
咱们先来看一段代码:缓存
String s = "Hello"; s = s + " world!";
试问:这两行代码执行后,原始的 String 对象中的内容到底变了没有?安全
答案是没有。由于 String 被设计成不可变(immutable)类,因此它的全部对象都是不可变对象。在 这段代码中,s 原先指向一个 String 对象,内容是 "Hello",而后咱们对 s 进行了+操做。这时,s 不指向原来那个对象了, 而指向了另外一个 String 对象,内容为"Hello world!",原来那个对象还存在于内存之中,只 是 s 这个引用变量再也不指向它了。性能优化
经过上面的说明,咱们很容易导出另外一个结论,若是常常对字符串进行各类各样的修改,或 者说,不可预见的修改,那么使用 String 来表明字符串的话会引发很大的内存开销。由于 String 对象创建以后不能再改变,因此对于每个不一样的字符串,都须要一个 String 对象来 表示。这时,应该考虑使用 StringBuffer类,它容许修改,而不是每一个不一样的字符串都要生 成一个新的对象。而且,这两种类的对象转换十分容易。app
同时,咱们还能够知道,若是要使用内容相同的字符串,没必要每次都 new 一个 String。例 如咱们要在构造器中对一个名叫 s 的 String 引用变量进行初始化,把它设置为初始值,应当这样作:性能
public class Demo { private String s; ... public Demo { s = "Initial Value"; } ... //而非 s = new String("Initial Value"); }
前者每次都会调用构造器,生成新对象,性能低下且内存开销大,而且没有意义,由于 String 对象不可改变,因此对于内容相同的字符串,只要一个 String 对象来表示就能够了。也就 说,屡次调用上面的构造器建立多个对象,他们的 String 类型属性 s 都指向同一个对象。 上面的结论还基于这样一个事实:对于字符串常量,若是内容相同,Java 认为它们表明同 一个 String 对象。而用关键字 new 调用构造器,老是会建立一个新的对象,不管内容是否 相同。优化
再请你们看一段代码:ui
String s = new String("xyz");
问题:建立了几个 String Object?两者之间有什么区别?spa
一个或两个 。
在Java中,其实有不少常量池相关的概念:
相似这种常量池的思想即涉及到了一个设计模式——享元模式。顾名思义,共享元素。
也就是说:一个系统中若是有多处用到了相同的一个元素,那么咱们应该只存储一份此元素,而让全部地方都引用这一个元素
那么为何要不可变呢?
String被许多的Java类库用来当作参数。例:
倘若String不是固定不变的,将会引发各类安全隐患。
在前面提到过常量池的享元模式。这样在拷贝或者建立内容相同对象时,就没必要复制对象自己,而是只要引用它便可。这样的开销比起copy object是天差地别的。另外,也就只有不可变对象才能使用常量池,由于能够保证引用同一常量值的多个变量不产生相互影响。
一样也是因为String对象的不可变特性,因此String对象能够自身缓存HashCode。Java中String对象的哈希码被频繁地使用, 好比在hashMap 等容器中。字符串不变性保证了hash码的惟一性,所以能够放心地进行缓存。这也是一种性能优化手段,意味着没必要每次都去计算新的哈希码:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0
JAVA 平台提供了两个类:String 和 StringBuffer,它们能够储存和操做字符串,即包含多个字符的字符数据。这个 String 类提供了数值不可改变的字符串。而这个 StringBuffer 类提供 的字符串进行修改。当你知道字符数据要改变的时候你就可使用 StringBuffer。典型地, 你可使用 StringBuffers 来动态构造字符数据。另外,String 实现了 equals 方法,new String(“abc”).equals(newString(“abc”)的结果为true,而StringBuffer没有实现equals方法, 因此,new StringBuffer(“abc”).equals(newStringBuffer(“abc”)的结果为 false。
接着要举一个具体的例子来讲明,咱们要把1到100的全部数字拼起来,组成一个串。
StringBuffer sbf = new StringBuffer(); for(int i=0;i<100;i++){ sbf.append(i); }
上面的代码效率很高,由于只建立了一个 StringBuffer 对象,而下面的代码效率很低,由于 建立了101个对象。
String str = new String(); for(int i=0;i<100;i++) { str = str + i; }
在讲二者区别时,应把循环的次数搞成10000,而后用 endTime-beginTime 来比较二者执 行的时间差别。
String 覆盖了 equals 方法和 hashCode 方法,而 StringBuffer没有覆盖 equals 方法和 hashCode 方法,因此,将 StringBuffer对象存储进 Java集合类中时会出现问题。
StringBuilder不是线程安全的,可是单线程中中的性能比StringBuffer高。
String str1 = "abcd"; String str2 = new String("abcd"); System.out.println(str1==str2);//false
这两种不一样的建立方法是有差异的:
String str1 = "str"; String str2 = "ing"; String str3 = "str" + "ing"; String str4 = str1 + str2; System.out.println(str3 == str4);//false String str5 = "string"; System.out.println(str3 == str5);//true
public static final String str1 = "ab"; public static final String str2 = "cd"; public static void main(String[] args) { String s = str1 + str2; // 将两个常量用+链接对s进行初始化 String t = "abcd"; if (s == t) { System.out.println("s等于t,它们是同一个对象"); } else { System.out.println("s不等于t,它们不是同一个对象"); } }
public static final String str1; public static final String str2; static { str1 = "ab"; str2 = "cd"; } public static void main(String[] args) { // 将两个常量用+链接对s进行初始化 String s = str1 + str2; String t = "abcd"; if (s == t) { System.out.println("s等于t,它们是同一个对象"); } else { System.out.println("s不等于t,它们不是同一个对象"); } }
运行时常量池相对于Class文件常量池的另一个重要特征是具有动态性,Java语言并不要求常量必定只有编译期才能产生,也就是并不是预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。
String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,若是有则返回该字符串的引用,若是没有则添加本身的字符串进入常量池。
public static void main(String[] args) { String s1 = new String("计算机"); String s2 = s1.intern(); String s3 = "计算机"; System.out.println("s1 == s2? " + (s1 == s2)); System.out.println("s3 == s2? " + (s3 == s2)); } //s1 == s2? false //s3 == s2? true
public class Test { public static void main(String[] args) { String hello = "Hello", lo = "lo"; System.out.println((hello == "Hello") + " "); System.out.println((Other.hello == hello) + " "); System.out.println((other.Other.hello == hello) + " "); System.out.println((hello == ("Hel" + "lo")) + " "); System.out.println((hello == ("Hel" + lo)) + " "); System.out.println(hello == ("Hel" + lo).intern()); } }
class Other { static String hello = "Hello"; }
package other; public class Other { public static String hello = "Hello"; } //true true true true false true