s.substring(0,2).intern()=="ab" intern方法在常量池中构建了一个值为“ab"的String对象,"ab"语句不会再去构建一个新的String对象,而是返回已经存在的String对象。所以结果是true。
只有显式使用双引号构造字符串对象、使用String对象的intern()方法 这两种方法会在常量池中创建String对象,其他方法都是在堆区创建对象。每次在常量池创建String对象前都会检查是否存在相同的String对象,如果是则会直接返回该对象的引用,而不会重新创建一个对象。
关于intern方法还有一个问题需要讲一下,在不同jdk版本所执行的具体逻辑是不同的。在jdk6以前,方法区是存放在永生代内存区域中,与堆区是分割开的,那么当往常量池中创建对象时,就需要进行深拷贝,也就是把一个对象完整地复制一遍并创建新的对象,如下图:
永生代有一个很严重的缺点:容易发生OOM 。永生代是有内存上限的,且很小,当程序大量调用intern方法时很容易就发生OOM。在JDK7时将常量池迁移出了永生代,改在堆区中实现,jdk8以后使用了本地空间实现。jdk7以后常量池的实现使得在常量池中创建对象可以进行浅拷贝,也就是不需要把整个对象复制过去,而只需要复制对象的引用即可,避免重复创建对象,如下图:
观察这个代码:
String s = new String(new char[]{'a'}); s.intern(); System.out.println(s=="a");在jdk6以前创建的是两个不同的对象,输出为false;而jdk7以后常量池中并不会创建新的对象,引用的是同个对象,所以输出是true。
jdk6之前使用intern创建对象使用的深拷贝,而在jdk7之后使用的是浅拷贝,得以重复利用堆区中的String对象。
通过上面的分析,String真正重复利用字符串是在使用双引号直接创建字符串时。使用intern方法虽然可以返回常量池中的字符串引用,但是本身已经需要堆区中的一个String对象。因而我们可以得出结论:
尽量使用双引号显式构建字符串;如果一个字符串需要频繁被重复利用,可以调用intern方法将他存放到常量池中。
字符串拼接字符串操作最多的莫过于字符串拼接了,由于String对象的不可变性,如果每次拼接都需要创建新的字符串对象就太影响性能了。因此,官方推出了两个类: StringBuffer、StringBuilder 。这两个类可以在不创建新的String对象的前提下拼装字符串、修改字符串。如下代码:
StringBuilder stringBuilder = new StringBuilder("abc"); stringBuilder.append("p") .append(new char[]{'q'}) .deleteCharAt(2) .insert(2,"abc"); String s = stringBuilder.toString();拼接、插入、删除都可以很快速地完成。因此,使用StringBuilder进行修改、拼接等操作来初始化字符串是更加高效率的做法。StringBuffer和StringBuilder的接口一致,但StringBuffer对操作方法都加上了synchronize关键字,保证线程安全的同时,也付出了对应的性能代价。单线程环境下更加建议使用StringBuilder。
拼接、修改等操作来初始化字符串时使用StringBuilder和StringBuffer可以提高性能;单线程环境下使用StringBuilder更加合适。
一般情况下,我们会使用+来连接字符串。+在java经过了运算符重载,可以用来拼接字符串。编译器也对+进行了一系列的优化。观察下面的代码:
String s1 = "ab"+"cd"+"fg"; String s2 = "hello"+s1; Object object = new Object(); String s3 = s2 + object;
对于s1字符串而言,编译器会把"ab"+"cd"+"fg"直接优化成"abcdefg" ,与String s1 = "abcdefg"; 是等价的。这种优化也就减少了拼接时产生的消耗。甚至比使用StringBuilder更加高效。
s2的拼接编译器会自动创建一个StringBuilder来构建字符串。也就相当于以下代码:
StringBuilder sb = new StringBuilder(); sb.append("hello"); sb.append(s1); String s2 = sb.toString();那么这是不是意味着我们可以不需要显式使用StringBuilder了,反正编译器都会帮助我们优化?当然不是,观察下边的代码:
String s = "a"; for(int i=0;i<=100;i++){ s+=i; }这里有100次循环,则会创建100个StringBuilder对象,这显然是一个非常错误的做法。这时候就需要我们来显示创建StringBuilder对象了:
StringBuilder sb = new StringBuilder("a"); for(int i=0;i<=100;i++){ sb.append(i); } String s = sb.toString();只需要构建一个StringBuilder对象,性能就极大地提高了。