jdk8及以前:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
java9及以后:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
String在jdk8及以前内部定义了private final char[] value;
用于存储字符串数据。jdk9时将value类型改进为byte[],因为String为堆空间中存储的主要数据,而大部分String对象只包含Latin-1格式字符,而Latin-1格式字符使用一个字节就能存下,使用char的话就会浪费掉1个字节的空间,所以jdk9将charp[]改为了byte[],对于大于一个字节的字符(如:中文),jvm增加了一个字符编码集的标识encoding-flag,相应的StringBuffer和StringBuilder也做了同样的改变
详情请看这里
/**
* 下面3行代码:编译后是通过StringBuilder实现的
* StringBuilder sb = new StringBuilder();
* sb.append("a");
* sb.append("b");
* String str = sb.toString(); 注意:toString()产生的字符串不会被放入到字符串常量池中
*/
String a = "a";
String b = "b";
String str = a+ b;
我们平时拼接字符串时,建议大家使用StringBuilder进行拼接。尤其是在循环中进行拼接的时候,每次循环都会创建一个StringBuilder对象及其toString()方法产生的String对象,这会占用非常多不必要的内存
jdk6中:intern()方法会尝试将该字符串放入字符串常量池中,如果字符串常量池中有该字符串,则返回常量池中该字符串的地址;如果没有,则将当前对象复制一份放入到字符串常量池中,并返回其在池中的地址
jdk7起:如果字符串常量池中有该字符串,则返回常量池中该字符串的地址;如果没有,则会把对象的引用地址复制一份放入池中,并返回其在池中的地址
通俗来讲:intern()方法确保字符串在内存中只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度
String str = "abc";
String newStr = new String("abc");
newStr == str ;// false
String str2 = "abcd";
String newStr2 = new String("abcd").intern();
newStr2 == str2 ;// true
1、String str = new String(“abc”)创建了几个对象?
答:两个,一个是new关键字在堆中创建的对象即str变量实际引用的对象;另一个是字符串"abc",它会被存放到字符串常量池中。当然这个答案不绝对,详情请看这里
2、String str = new String(“a”)+new String(“b”)会创建几个对象?
答:6个,分别是:new StringBuilder()
、 new String("a")
、 常量池中的"a" 、 new String("b")
、 常量池中的"b" 、 StringBuilder的toString()产生的对象。注:StringBuilder的toString()方法使用的是new String(“ab”),它的字符串"ab"不会被放入到字符串常量池中
3、关于intern()的面试题
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);// jdk6 : false jdk7 : false
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);// jdk6 : false jdk7 : true
}
首先,对于第一个结果来说:在s调用intern()方法前,JVM就因为new String(“1”)这条语句在字符串常量池中放入了"1",而s则是指向的堆区的new String(“1”)对象的地址,该地址对象会指向常量池中“1”这个字符串;String s2 = “1”,所以s2直接指向常量池中“1”这个字符串,所以第一个打印语句打印的false
对于第二个打印语句结果来说:String s3 = new String(“1”) + new String(“1”);这条语句编译后通过StringBuilder进行拼接后调用toString方法返回的s3,这个过程不会在字符串常量池中加入字符串"11"。而intern()在jdk6时会将当前对象复制一份放入池中,从jdk7开始会把对象的引用地址复制一份放入池中,所以在jdk7中,在将字符串"11"加入常量池时,发现堆区(字符串常量池以外)已经有了"11"这个字符串,那么为了节省空间,常量池中保存的是该堆区对象的地址,而String s4 = “11”;也就是s4指向的是常量池。所以整个过程就是s4指向常量池,常量池存储的是s3的地址,所以第二个打印语句在jdk6时返回false,在jdk7时返回true
版本 | 位置 |
---|---|
jdk6 及以前 | 方法区、永久代 |
jdk7及以后 | java堆 |