JVM-String

91 阅读3分钟

String基本特性

  • String:字符串,使用一对“”引起来表示。
  • String声明为final的,不可被继承
  • String实现了Serializable接口:表示字符串是支持序列化的,实现了Comparable接口:表示String可以比较大小
  • String在jdk8及以前内部定义了final char[] value 用于储存字符串数据。JDK9时改为byte[]。
  • string pool是一个固定大小的HashTable,默认值大小长度是1009。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能大幅下降。
  • jdk6固定1009,jdk7默认60013,jdk8最小值1009。

String的内存分配

String类型的常量池

  • 直接使用双引号声明出来的String对象会直接储存在常量池中。
  • 如果不是双引号声明的String对象,可以使用String提供的intern()方法。
  • 在JDK6及以前,字符串常量池存放在方法区中,JDK7及以后存放在堆中。

字符串拼接操作

  • 常量与常量的拼接结果在常量池,原理是编译期优化
  • 常量池中不会存在相同内容的常量
  • 只要其中有一个是变量,结果就在堆中,相当于在堆空间中new String()。变量拼接的原理是StringBuilder
String s1 = "a";
String s2 = "b";
String s3 = s1 + s2 ;

/*等价于
StringBuilder s = new StringBuilder();
s.append("a");
s.append("b");
s.tostring();
*/

System.out.println(s3==s4)//false

  • 如果拼接结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并且返回次对象地址。

String.intern()

  • 如果不是用双引号申明的String对象。可以使用String提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
  • 也就是说,如果在任意字符串上调用String.intern方法,那么其返回结果所指向的那个类的实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是true, ("a"+"b"+"c").intern() == "abc"

创建几个对象问题

String s= new String("ab");

  • 一个对象是:new关键字在堆空间创建的
  • 另一个对象时:字符串常量池中的对象

String s = new String("a")+new String("b");

  • 对象1:new StringBuilder()
  • 对象2:new String("a")
  • 对象3:常量池中的"a"
  • 对象4:new String("b")
  • 对象5:常量池中的"b"
  • 对象6:StringBuilder中的toString(),new String(value,0,count)=>new String(["a","b"],0,1);并不会把“ab”存在字符串常量池。

intern难题

String s = new String("1"); 
s.intern(); //调用此方法前,字符串常量池中已经存在了“1”,所以intern放回该“1”地址,但无接收,s仍然指向堆中
String s2 = "1"; // s2指向常量池中
Sout(s==s2);   //jdk6:false. jdk7/8:false 
String s = new String("1")+new String("2"); // 在调用此方法后,字符串常量池中并不存在“12”,存在“1”,存在“2”,
s.intern();//所以调用这个方法时,它会在字符串常量池中生成“11”。
// jdk6:创建一个新的对象“11”,也就是新的地址
// jdk7/8:此时常量池中并没有创建“11”,而是创建一个指向堆空间中new String("11")的地址
String s2 = "12";
sout(s==s2); //jdk6:false ,jdk7/8:true