整理于
-
声明为final,不可继承
-
实现了Serializable接口:表示字符串可以支持序列号
Comparable接口 表示可以比较大小
-
在JDK1.8中使用的是char数组,但是在JDK1.9中使用的是byte的数组,因为通过研究表明,大部分对空间大多数是拉丁字符,所以说一个字节即可解决问题,除此之外,为了解决解决中文的问题,改成了btye[ ] 加上了编码的标识,节约了一些空间
-
基于以上的String buffer等类也做出了对应的修改
不可变性
不可变的序列,当数组做出修改后,不可变,
- 当对String重新赋值之后,将会重写执行内存的区域赋值,而不使用原有的
- 当进行拼接,替换等等之后都会重写赋值
在String不可变性,所以说在传递中如果传递的话,不会修改原本的数值,而是会返回一个新的。
字符串常量池
- 字符串常量池中不会存储相同的字符串,底层是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用
String.intern
时性能会大幅下降(因为要一个一个找)。 - 可以通过去设置JVM的参数来进行设置Hashtable来控制大小。
- 在jdk6中StringTable默认大小是1009的长度,在jdk7中是60013的长度。
- jdk8中如果要设置最小值,则最小的设置大小是1009.
String的内存分配
- 在Java语言当中,为8种基本类型和String都提供了常量池的概念,为了让他们可以运行的更快、更加的节省内存。
- String的常量池比较特殊,存在两种
- 直接使用双引号声明出来的
String
对象会直接存储在常量池中。 - 如果不是用双引号声明的
String
对象,可以使用String
提供的intern
方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
- 直接使用双引号声明出来的
- 常量池的存放的位置
- jdk6 字符串常量池放在永久代
- jdk7 中,Orcale做了很大的改动放在了堆中,即将字符串的字符串的常量池都存放在了Java的堆中。
- 所以所有的字符串都保存在了堆中,和其他的对象一样,这样在调优的时候调整堆的大小就行了。
- jdk8 中字符串在堆中的字符串常量池
为什么要调整位置
- permSize 默认比较小
- 永久代垃圾回收频率低
字符串拼接操作
-
常量和常量的拼接都在常量池中,编译期优化
-
常量池中不会存在相同的内容的常量
-
只要有一个是变量则结果在堆中,原理是StringBuilder
-
拼接结果是调用intern()方法,讲常量池中没有字符串的常量放进去,返回地址
- 当发生变量拼接的时候,会执行
-
- StringBuilder s = new StringBuilder ();
- s.append(“a”);
- s.append(“b”);
- s.toString() --> 约等于 new String(“ab”)
-
- 当时常量的时候,则会直接进行拼接,在编译器优化
注意:
- 在进行字符串拼接的时候,尽量使用StringBuilder来进行操作,这样会提高效率,而是还用String的字符串来进行的时候实际上是多次创建StringBuilder
- 在使用String时,内存占用了较多的StringBuilder和String,内存会占用较多的空间,GC也需要时间
- 在使用StringBuilder的时候,如果确定大小的话,最好创建的时候,直接创建一个带长度 构造,这样就可以减少扩容的次数
intern()方法
当方法被调用的时候,发现当前对象在字符串常量池中存在一个对象时,则返回字符串常量池中的对象的地址,反之,如果字符串常量池中没有的话,则会添加到字符串常量池,随后返回该添加的地址。
如果保证s指向的是字符串常量中的数据呢
- String s=“sadsadsad”;
- String s=new String(“aa”).intern();
重点
第一段代码
public class A {
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); // jdk 6 false jdk 7 false
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4); // jdk 6 false jdk 7 true
}
}
注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向。
JDK 6 解释:
- 在JDK 6 当中,字符串的常量都是保存在了Perm区,Perm区和正常的Java Heap区是分开的,所以说生产的对象的常量必定是存在于Java方法区,然后new 出来的是存在于堆区。使用 intern 后。
- 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
- 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址
JDK 7 解释:
- 在JDK7,当中,常量池将不再存储对象,而是直接存储堆中的引用,这份引用执行的可以是堆中的地址
第二段代码
public class A {
public static void main(String[] args) {
String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2); // jdk 6 false // jdk7 + false
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4); // jdk 6 false // jdk7 + false
}
}
解释:
- 在JDK 6 中是一致的,于是不再重复
- 在JDK 7 + 中,假如说常量池中,已经存在了对象,则不会进行修改。
总结string的intern ()的使用:
jdk1.6中,将这个字符串对象尝试放入串池。
- 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
- 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址
Jdk1.7起,将这个字符串对象尝试放入串池。
- 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
- 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址,
面试题
- new String(“ab”) 会创造几个对象
2个
如何证明?
字节码
- 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:new String(“ab”)
//在字符串常量池中,没有生成ab
intern()的效率
当使用intern之后可以有效的减少很多的空间。当存在当量的重复的字符串的时候