文章目录
- 12. String的特性
-
- 12.1 String的基本特性
-
- 12.1.1 StringTable
- 12.2 String的内存分配
- 12.3 字符串的拼接
- 12.4 intern()的使用
- 12.5 new String()
12. String的特性
12.1 String的基本特性
-
String声明为final的,不可被继承
String s1 = "ILOVEYOU";
//字面量的定义方式String s2 = new String("ILOVEYOU");
-
String实现了serializable接口:表示字符串是支持序列化的(跨进程传输数据)。实现了Comparable接口:表示string可以比较大小
-
String在jdk8及以前内部定义了final char[] value用于存储字符串数据。idk9时改为byte[]
-
String:代表不可变的字符序列。不可变性
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用string的replace ()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
-
通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
字符串常量池中是不会存储相同内容的字符串的
12.1.1 StringTable
- string的string Pool是一个固定大小的Hashtable,默认值大小长度是1009。如果放进string Pool的string非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用string.intern时性能会大幅下降。
- 使用
-XX:stringTablesize=?
可设置stringTable的长度 - 在jdk6中stringTable是固定的,就是1009的长度,所以如果吊量池中的字符串过多就会导致效率下降很快。StringTablesize设置没有要求。
- 在jdk7中,stringTable的长度默认值是60013。
- jdk8开始,1009是StringTable可设置的最小值。
12.2 String的内存分配
- jdk6及以前字符串常量池存放在永久代中。jdk7将字符串常量池的位置调整到java堆中。jdk8字符串常量池在堆中。
12.3 字符串的拼接
-
常量与常量的拼接结果在常量池,原理是编译期优化
String s1 = "a" +"b" +"c";//等同于“abc”,在编译期间就赋值为“abc” String s2 = "abc"; System.out.println(s1 == s2);//true System.out.println(s1.equals(s2));//true
-
常量池中不会存在相同内容的常量。
-
只要其中有一个是变量,结果就在堆中。变量拼接的原理是stringBuilder
-
如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。
String s1 = "javaEE"; String s2 = "hadoop";String s3 = "javaEEhadoop"; String s4 = "javaEE" + "hadoop";//编译器优化 //如果拼接符号的前后出现了变量,则相当于在推空间中new String() String s5 = s1 + "hadoop"; String s6 = "javaEE" + s2; //jdk5.0之后,执行细节:首先new StringBuilder,然后分别append(s1)、append(s2),最后toString()生成一个string对象. String s7 = s1 + s2;System.out.println(s3 == s4);//true System.out.println(s3 == s5);//false System.out.println(s3 == s6);//false System.out.println(s3 == s7);//false System.out.println(s5 == s6);//false System.out.println(s5 == s7);//false System.out.println(s6 == s7);//false //进行校验,如果s6的值存在于字符串常量池中,则将该地址返回给s8,如果没有,则在常量池中创建字符串。 String s8 = s6.intern(); System.out.println(s3 == s8);//true---------------------------------------------------------------------------------------- //字符串拼接不一定使用StringBuilder,也有可能是编译器优化 final String s1 = "a";//常量 final String s2 = "b";//常量 String s3 = "ab"; String s4 = s1 + s2; System.out.println(s3 == s4);//true----------------------------------------------------------------------------------------- 当多次进行字符串拼接的时候,StringBuilder的append()的效率远高于+操作。 StringBuilder的append的方式自始至终只创建了一个StringBuilder对象。而String的拼接创建了多个StringBuilder和String的对象,内存占用比较大,如果GC,也会花费时间。 优化空间:如果基本确定字符串的长度,新建对象的时候就可以指定(底层数组)容量,避免后续多次新建StringBuilder。即StringBuilder sb = new StringBuilder(capacity);
12.4 intern()的使用
- 如果不是用双引号声明的string对象,可以使用string提供的intern方法: intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
比如:String myInfo =new String ("I love you" ).intern();
- 确保:字符串常量池中是不会存储相同内容的字符串的
通过以下方式保证变量s指向的是字符串常量池中的数据
String s = "I Love com";
//字面量的定义方式String s = ......intern();
//调用intern()方法
12.5 new String()
new String("ab");
会创建几个对象?两个,通过字节码指令ldc:一个对象是new关键字在堆中创建,一个对象是字符串常量池中的
String str = new String("a") + new String("b");
会创建几个对象?
对象1:new StringBuilder()
对象2:new String(“a”);
对象3:字符串常量池中的"a"
对象4:new String(“b”)
对象5:字符串常量池中的"b"
深入刨析:StringBuilder的toString();
对象6:toString()会new String(“ab”);
在字符串常量池中没有"ab"
String s1 = new String("1");//s指向队中的"1",此时常量池中也有"1" s.intern(); String s2 = "1";//字面量定义"1",指向常量池中已有的"1" System.out.println(s1 == s2);//jdk6:false jdk7/8:falseString s3 = new String("1") + new String("1");//s3指向的是堆中的new String("11"),此时常量池中没有"11" s3.intern();//在字符串常量池中开辟空间,存放的是上述堆中new String("11")的地址 String s4 = "11";//s4指向常量池中的"11",实际指向堆中的"11"的地址 System.out.println(s3 == s4);//jdk6:false jdk7/8:true
-
总结string的intern()的使用:
jdk1.6中,将这个字符串对象尝试放入串池。-
如果串池中有,则并不会放入。返回已有的串池中的对象的地址
-
如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址
Jdk1.7起,将这个字符串对象尝试放入串池。
- 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
- 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址
-
例题:
String s = new String("a") + new String("b");
String s2 = s.intern();
System.out.println(s2 == "ab");
System.out.println(s == "ab");解释为什么在jdk7/8中都是true?:
String s = new String("a") + new String("b");//
String s2 = s.intern();//字符串常量池中存放的是堆中new String("ab")的地址
System.out.println(s2 == "ab");//jdk6:true jdk7/8:true --> 与"ab"相比时,发现堆中有指向堆中的"ab",所以等号左右都指向堆中的"ab"
System.out.println(s == "ab");//jdk6:false jdk7/8:true