更新日志
结论:String再也不用char[] 来存储,改成用byte[]加上编码标记,节约一些空间。
@Stable
private final byte[] value;
那StringBuffer和StringBuilder是否仍然无动于衷呢?
String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will the HotSpot VM’s intrinsic string operations.
JAVA语言规范里要求完全相同的字符串字面量,应该包含同样的Unicode字符序列(包含同一份码点序列的常量),并且必须是指向同一个String类实例
public static void main(String[] args) {
System.out.println();
System.out.println("1");// 1731
System.out.println("2");//1732
System.out.println("3");
System.out.println("4");
System.out.println("5");
System.out.println("6");
System.out.println("7");
System.out.println("8");
System.out.println("9");
System.out.println("10");//1740
System.out.println("1");//1741
System.out.println("2");
System.out.println("3");//1741
System.out.println("4");
System.out.println("5");
System.out.println("6");
System.out.println("7");
System.out.println("8");
System.out.println("9");
System.out.println("10");
}
编译期完成了拼接
public void test(){
/*
*
* 0 ldc #2 <12> 常量进行拼接的时候一开始就从字符串常量池中去加载 ,在编译期就完成了拼接
2 astore_1
3 ldc #2 <12>
5 astore_2
*
* */
String a = "1" + "2";
String a1 = "12";
System.out.println(a == a1);
}
变量拼接
@Test
public void test1(){
String a = "javaee";
String b = "hadoop";
String s1 = "javaeehadoop";
String s2 = "javaee"+"hadoop";
/*
*
* 0 ldc #5
2 astore_1
3 ldc #6
5 astore_2
6 ldc #7
8 astore_3
9 ldc #7
11 astore 4
*
*
*
* */
String b1 = a + "hadoop";
/*
*
13 new #8
16 dup
17 invokespecial #9 : ()V>
20 aload_1
21 invokevirtual #10
24 ldc #6
26 invokevirtual #10
29 invokevirtual #11
32 astore 5
* */
String b2 = "javaee"+ b;
String b3 = a + b;
System.out.println(s1 == b1);
System.out.println(s2 == b2 );
System.out.println(s2 == b3 );
System.out.println(b3 == b2);
System.out.println(b3 == b1);
}
如果不是用双引号声明的String对象,可以使用String提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
也就是说,如果在任意字符串上调用String.intern(),那么其返回结果所指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是true
(“a”+“b”+“c”).intern() == “abc”;
通俗点将,intern String就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)
@Test
public void test() {
String s = new String("1");
// 看这个字节码指令已经在字符串常量池中有了一个1 此时创建了两个对象 一个在堆中 一个在字符串常量池中。此时s的地址指向的是堆空间中的地址
s.intern();// 这个就没有什么作用
String s1 = "1";// 直接指向字符创常量池 地址是字符串常量池中的地址
System.out.println(s == s1);// jdk 6 中 false
// jdk7/8 中 false
}
public static void main(String[] args) {
// new String("1") + new String("1") 具体说一共创建了6个对象
/*
* 在字符串进行拼接的时候创建了一个StringBuilder
* 之后创建了堆空间中的String 字符串常量池中的1 ,调用append方法进行添加,然后又创建了堆空间中的String 和 字符串常量池中的1 然后又进行append添加
* 最后调用了StringBuilder中的toString()方法返回一个地址,观察StringBuilder中的字节码指令
* 发现在字符串常量池中并没有创建一个11
*
* */
String s = new String("1") + new String("1");
// 此时字符串常量池中没有11 调用了intern以后会在字符串常量池中创建一个11
/*
在jdk6中 字符串常量池在永久代 所以地址指向不同
在jdk7以后,字符串常量池移到了堆空间中,我们在调用intern()时候,在字符串常量池中我们
存放的是堆空间的那个字符的地址,不会再去新创建一个字符串
*/
s.intern();
// 在jdk7以后直接指向字符串常量池,此时字符串常量池中存放的是堆空间中的地址,所以两个地址一样。
String s1 = "11";
System.out.println(s == s1);// jdk 6 中false
// jdk 7/8中true
}
总结String.intern()使用
- JDK6中,将这个字符串对象尝试放入串池中。
- 如果串池中有,则不会放入,返回已有的串池中的对象的地址。
- 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址。
- JDK7开始,将这个字符串对象尝试放入串池。
- 如果串池中有,则并不会放入。返回已有的串池中的对象的地址。
- 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址。
public static void main(String[] args) {
String s = new String("1")+new String("2");
String s1 = s.intern();
System.out.println(s=="12");
System.out.println(s1=="12");
}
以上题目在JDK6中 结果为false true
在JDK7中,结果为 true true 。JDK7开始,将这个字符串对象尝试放入串池。如果串池中有,则并不会放入。返回已有的串池中的对象的地址。如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址。
public class Test{
public void test(){
String s = new String("ab")
}
}
会创建几个对象呢?
创建了两个对象 0 和4一个对象是通过new关键字在堆间中创建 的另一个对象是,字符串常量池中ldc创建的具体看字节码指令
0 new #2 <java/lang/String>
3 dup
4 ldc #3 <Test>
6 invokespecial #4 <java/lang/String.<init> : (Ljava/lang/String;)V>
9 astore_1
10 return
String s1 = new String("Te")+ new String("st");
会创建几个对象呢? 准确来说有6个
// 创建了5个对象
/*
对象一:StringBuilder 只要有拼接操作就会首先创建一个StringBuilder
对象二:String
对象三:字符串常量池 Te
对象四:String
对象五:字符串常量池 st
深入剖析:StringBuilder到的toString():
对象6:new String("Test")
强调一 在字符串常量池中没有生成"Test",下面的字节码指令中没有ldc,没有在字符串常量池中进行创建对象
字节码指令如下:
0 new #80
3 dup
4 aload_0
5 getfield #234
8 iconst_0
9 aload_0
10 getfield #233
13 invokespecial #291 : ([CII)V>
16 areturn
*/
String s1 = new String("Te")+ new String("st");
/*
*0 new #2
3 dup
4 invokespecial #3 : ()V>
7 new #4
10 dup
11 ldc #5
13 invokespecial #6 : (Ljava/lang/String;)V>
16 invokevirtual #7
19 new #4
22 dup
23 ldc #8
25 invokespecial #6 : (Ljava/lang/String;)V>
28 invokevirtual #7
31 invokevirtual #9
34 astore_1
35 return
* */
在一般比较大型的社交网站,很多人都存储字符串,所以使用intern(),就会明显降低内存的大小。