String Table
不可变性
String 在jdk8中 是 char value[];jdk9中式byte[] 更加节约内存空间。String 代表不可变的字符序列,对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
/** * String代表不可变的字符序列,拥有不可变性。对字符串重新赋值的时候,需要重新指定内存 */ public class A2 { String str = new String("good"); char[] ch = {'t', 'e', 's', 't'}; public void change(String str, char ch[]) { System.out.println("======================"); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(str))); str = "test ok"; // 这不会改变实例变量的值 //this.str = "test ok"; // 这样就真正修改了实例变量 ch[0] = 'b'; System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(str))); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(str))); System.out.println("======================"); } public static void main(String[] args) { A2 ex = new A2(); ex.change(ex.str, ex.ch); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(ex.str))); System.out.println(ex.str);//good System.out.println(ex.ch);//best } }
字符串常量池
通过 new String("str") 的方式创建对象,它是存在堆中的。而我们通过字面量的方式给一个字符串赋值,它是放在字符串常量池中的。
public static void main(String[] args) { String s1 = "wulei"; // wulei 是字符串常量池,它是放在堆中的 String s2 = "wulei"; // 字符串常量池中是没有相同的字符串的,这两个wulei其实是同一个 System.out.println(s1==s2); // 判断内存地址 true System.out.println(s1.equals(s2)); // 比较数据的值,这个不用看肯定是true System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s1))); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s2))); }
内存分布
public static void main(String[] args) { int i = 1; Object obj = new Object(); A3 mem = new A3(); mem.foo(obj); } private void foo(Object param) { String str = param.toString(); System.out.println(str); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(str))); }
创建对象
/** * 创建了几个对象 */ public class A4 { public static void main(String[] args) { // 一个对象是:new关键字在堆空间创建的 // 另一个对象是:字符串常量池中的对象"ab"。 字节码指令:ldc String s1 = new String("ab"); // 对象1: new String("a") // 对象2: 常量池中的"a" // 对象3: new String("b") // 对象4: 常量池中的"b" // 对象5: + 等于 new StringBuilder() // 对象6: 最后返回字符串,等于 StringBuilder.toString() /* 如下的s1 + s2 的执行细节:(变量s是我临时定义的) ① StringBuilder s = new StringBuilder(); ② s.append("a") ③ s.append("b") ④ s.toString() --> 约等于 new String("ab"),具体的内容为拼接的结果:javaEEhadoop 在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer */ String s2 = new String("a") + new String("b"); } }
字符串拼接
public static void main(String[] args) { /* 编译期优化 String s4 = "javaEE" + "hadoop"; 在编译时会被替换为 String s4 = "javaEEhadoop"; */ String s1 = "javaEE"; String s2 = "hadoop"; String s3 = "javaEEhadoop"; String s4 = "javaEE" + "hadoop"; System.out.println(s3 == s4);//true System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s3))); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s4))); System.out.println("====================="); /* 如下的s1 + s2 的执行细节:(变量s是我临时定义的) ① StringBuilder s = new StringBuilder(); ② s.append("a") ③ s.append("b") ④ s.toString() --> 约等于 new String("ab"),具体的内容为拼接的结果:javaEEhadoop */ String s5 = s1 + "hadoop"; String s6 = s1 + s2; System.out.println(s3 == s5);//false System.out.println(s3 == s6);//false System.out.println(s5 == s6);//false System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s5))); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s6))); System.out.println("====================="); /* 如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。 */ final String a1 = "a"; final String a2 = "b"; String a3 = "ab"; String a4 = a1 + a2; System.out.println(a3 == a4);//true }
intern()
public static void main(String[] args) { String s1 = "javaEEhadoop"; String s2 = new String("javaEEhadoop"); System.out.println(s1==s2);//false /* intern(): 首先,会判断堆中是否有字符串String("javaEEhadoop"), 有的话,就不会在字符串常量池创建 "javaEEhadoop"了, 而是在串池里面存指向String("javaEEhadoop")的地址,并返回此对象的地址。 然后,会判断串池中是否已经有"javaEEhadoop"了,有的话就直接指向之前的"javaEEhadoop",并返回此对象的地址。 最后,以上要是都没有,就会在串池中创建一个"javaEEhadoop",并返回此对象的地址。 扩展: String a = new String("ss")+new String("bb"); a.intern(); // 串池里面保存了new String("ssbb")引用 String b = "ssbb"; // 由于串池有引用了,就不会创建了,此时b==a。要是没有执行a.intern(),则会新创建对象 */ String s3 = s2.intern(); System.out.println(s1 == s3);//true System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s1))); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s2))); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s3))); System.out.println("====================="); String s4 = new String("javaEE"); String s5 = s4.intern(); System.out.println(s4 == s5);//false System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s4))); System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(s5))); System.out.println("====================="); String b = new String("a") + new String("b");//new String("ab") System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(b))); //在上b一行代码执行完以后,字符串常量池中并没有"ab" String b2 = b.intern();//jdk6中:在串池中创建一个字符串"ab"b //jdk8中:串池中没有创建字符串"ab",而是创建一个引用,指向 System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(b))); //由于b.intern()的时候, 已经在串池指向堆的new String("ab")了,所以这里的"ab"不会创建新对象,而是之前的地址 String x = "ab";//指向b2 System.out.println(b2 == "ab");//true System.out.println(b == "ab"); //true System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(b2))); System.out.println(b == x); //true System.out.println(String.class.getName()+"@"+Integer.toHexString(System.identityHashCode(x))); System.out.println("====================="); String c1 = new String("cd");//执行完以后,会在字符串常量池中会生成"cd",堆里也一个new String("cd") String c2 = c1.intern();//指向串池 String c3 = "cd"; //指向串池的c2 System.out.println(c1 == c2);//false System.out.println(c3 == c2);//true System.out.println("====================="); String e1 = new String("e") + new String("f");////执行完以后,不会在字符串常量池中会生成"ef" String e2 = e1.intern();//值 String e3 = "ef";//指向e2 System.out.println(e1 == e2);//true System.out.println(e3 == e2);//true System.out.println("====================="); String e11 = new String("e") + new String("f");////执行完以后,不会在字符串常量池中会生成"ef" String e31 = "ef";//由于堆中有了一个new String("ef")了,没必要在字符串常量池创建"ef", 直接在字符串常量池存一个地址指向堆的new String("ef")就好了 String e21 = e1.intern();//指向e31 System.out.println(e11 == e21);//false System.out.println(e31 == e21);//true System.out.println("====================="); String g = new String("1"); String g3 = g.intern();//调用此方法之前,字符串常量池中已经存在了"1", String g2 = "1";//指向g3 System.out.println(g == g2);//false System.out.println(g == g3);//false System.out.println(g2 == g3);//true System.out.println("====================="); String h3 = new String("3") + new String("3");//new String("33") //执行完上一行代码以后,字符串常量池中,是否存在"33"呢?答案:不存在!! String h4 = "33";//在字符串常量池中生成对象"33" String h5 = h3.intern();//指向h4 System.out.println(h3 == h4);//false System.out.println(h5 == h4);//true }
。