final
类型,不可继承,且具有不可变性;final
修饰的value[]
数组存储,在jdk8.0之前用char[]数组,jdk9.0后用byte[]
数组存储;(why?因为使用发现大部分String类型能够被一个字符存储下来,虽然汉字不行,但是大部分还是英文类型居多,这样用byte数组存储能明显节省空间);String s1 = "abc";
String s2 = "abc";
System.out.println(s1==s2);//true
StringTable
中的。在jdk6.0时,字符串常量池是放在方法区中(此时方法区称为永久代,占用虚拟机内存);jdk7.0时,字符串常量池被转移到堆中;jdk8.0后,字符串常量池仍然被保留在堆中(而永久代的概念被元空间取代,占用本地内存)。
问:为什么字符床常量池的位置需要从方法去到堆中区?
答: 因为如果StringTable放在方法区中,只有在full GC的时候才会回收空间,回收的效率并不高。实际中字符串会被大量的创建,由于回收效率低,更加容易导致方法区内存不足,而放在堆中,可以利用minor/major/full gc回收,回收效率更高。
StringTable
的一个特点是不能存放两个相同内容的字符串的,且不可更改(只能重新创建)。在jdk7.0之前,StringTable
底层是固定大小的Hashtable
,长度为1009,所以不存在相同的两个字符串。jdk7.0之后,长度不固定,默认设置为60013。当字符串常量池中存储的字符串过多时,会导致哈希冲突严重,导致链表的长度变长,从而导致在调用String.intern()时降低性能。结合上面两点可以得出,变量s1和s2指向的是字符串常量池中的同一个“abc”
,因此判断结果为true
。
String s1 = "abc";
String s2 = "abc";
s1 = "hello";
//s1 += "def";拼接/replace函数同样是需要在StringTable重新创建
System.out.println(s1 == s2);//false
System.out.println(s1);//hello
System.out.println(s2);//abc
如果给s1重新赋值“hello”
,会在字符串常量池新建一个“hello”
,然后s1的指针指向“hello”
,所以判断结果为false
。
问:String对象的创建通过字面量和new的方式两者有什么不同?
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2);//true
System.out.println(s3==s4);//false
Person p1 = new Person("Tom",12);
Person p2 = new Person("Tom",12);
System.out.println(p1.name == p2.name);//true
答:通过字面量的方式创建的对象,变量存储的指针地址是直接指向字符串常量池的;
而通过new的方式创建对象,变量存储的指针地址指向堆空间中开辟的value
数据的地址,数组的指针地址会指向字符串常量池中的“abc”
。同理,Person
的例子也是一样的,“Tom”
是字面量存储在字符串常量池中,p1指针指向堆中对象,name指向字符串常量池中的“Tom”
。而字符串常量池是共享的,字符不可重复的,所以判断结果为true
。
问:解释下列结果。
String s1 = "jvm";
String s2 = "hotspot";
String s3 = "jvm" + "hotspot";
String s4 = "jvmhotspot";
String s5 = s1 + "hotspot";
String s6 = s1 + s2;
String s7 = (s1 + s2).intern();
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//true
答:
问:底层分析字符串拼接的一边或者两边是变量的情况。
答:
String s1 = "a";
String s2 = "b";
String s3 = s1 + s2;
s1 + s2执行细节如下:
1、创建StringBuilder对象,StringBuilder s = new StringBuilder()
2、s.append(“a”)
3、s.append(“b”)
4、s.toString() //约等于new String(“ab”):因为正常new String(“ab”)会在堆中和常量池中有两个对象,而toString中的没有在常量池中生成,为什么,我也不清楚。
解析:略。
解析:函数调用,把变量str指向的地址赋给形参str,根据string的不可变性,形参变成“test ok”,需要重新创建一个字符串,所以形参str保存的地址变成“test ok”字符串存储的地址,但这不影响原来的变量str的指向地址。
而形参ch指向数组,数组可以修改,所以内容被改变。
解析:
new String("ab");
:2个对象。一个是new的String对象”ab“,一个是再字符串常量池中的“ab”。
new String("a") + new String("b");
*6个对象。**第一个是因为拼接操作new的StringBuilder对象,第二个是new的String对象"a",第三个是字符串常量池中的”a“,第四个是new的String对象"b",第五个是字符串常量池中的”b“,第六个是StringBuilder.toString方法调用时new的String对象。
从上述可以看出,原始题的字符串常量池中有”ab“字符串,而扩展题里的字符串常量池中没有。这里就是StringBuilder的toString(),为什么常量池里没有我也不知道!!!
题四
// intern一
String a = new String("1").intern();
// intern二
String b = new String("1");
b.intern();
//intern三
String d = b.intern();
String c = "1";
System.out.println(a == c);// true
System.out.println(b == c);// false,虽然b调用了intern,但是没有改变b,intern是返回地址到新变量,所以d是指向常量池的
System.out.println(d == c);// true
//说明:intern有返回值,返回值是字符串常量池的引用地址
解析:intern有返回值,调用intern的对象本身并不受影响。
题五
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
String s5 = new String("2") + new String("2");
String s6 = "22";
s5.intern();
System.out.println(s5 == s6);
}
答案:结果和jdk版本有关。主要影响就是字符串常量池位置的变化。jdk6在永久区,jdk7/8在堆。
jdk6:false+false+false;
jad7/8:false+true+false;
解析:
总结:由于jdk版本的不同,intern()方式的实现原理发生了些许的变化。
题六
String s = new String("a") + new String("b");
String s2 = s.intern();
System.out.println(s == "ab");//jdk6:false jdk7:true
System.out.println(s2 == "ab");//jdk6:true jdk7:true
解析:”ab“字面量本身首先是存储在字符串常量池中的,这点是需要先明确的。上述==比较,比较的是地址。
一个问题?题目中的”ab“是指存放在哪里的?如果内存中堆中和字符串常量池都有呢?
强记:”ab“在堆中和字符串常量池都有,那么指的在字符串常量池的”ab“,只有一个有,那就是哪一个。
jdk6中,s存的是指向堆中的地址,s2存的指向字符串常量池的地址,”ab“有两个,所以指的常量池的地址,所以false+true;
jdk7中,s存的是指向堆中的地址,s2存的也是字符串常量池中存储的是堆中”ab“的地址,”ab “只有一个,所以地址也是堆中的,所以true+true。
String–>基本数据类型: Integer.parseInt()
、Float.parseFloat
…
String s1 = "123";
int i1 = Integer.parseInt(s1);
基本数据类型–>String:String.valueOf()
int i1 = 123;
String s1 = String.valueOf(i1);
String s2 = i1 + "";
String–>StringBuffer、StringBuilser:
String s1 = "abc";
StringBuffer sb1 = StringBuffer(s1);
StringBuilder sb2 = StringBuilder(s1);
StringBuffer、StringBuilser–>String:
StringBuffer sb1 = StringBuffer("abc");
String s1 = String(sb1);
String s2 = sb1.toString();
问:三者异同?
(底层存储jdk9开始从char[]数组变成byte[]数组)
String:不可变字符序列,声明为fianl,不可扩容,效率最低
StringBuffer:可变字符序列,可扩容,继承于AbstractStringBuilder,线程安全,效率低
StringBuilder(jdk5.0):可变字符序列,可扩容,继承于AbstractStringBuilder,线程不安全,效率高
StringBuffer、StringBuilder初始化底层数组长度为:字符串长度+16
扩容机制:x2+2;