String基本特性
String字符串,使用一对“”引起来表示 String s1 = “lotus”; //字面量定义方式 String s2 = new String(“hello”) String声明为final,不可被继承 String实现了Serializable接口,表示字符串支持序列化,实现Comparable接口,表示String可以比较大小 String在JDK8以前内部定义了final char[] value用于存储字符串数据,jdk9改为byte[] String代表不可变字符序列,称不可变性 当对字符串重新赋值时,重写指定内存区域赋值,不能使用原有的value进行赋值 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中 字符串常量池不会存储相同内容的字符串 String的String Pool是一个固定大小的Hashtable,默认值大小长度1009.如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表很长,而链表长了后直接会影响当前调用String.intern的性能 使用-XX:StringTableSize可以设置StringTable的长度 在jdk6中StringTable是固定的,就是1009,所以常量池中的字符串过多会导致效率下降很快,StringTableSize设置数值没有要求 在jdk7中StringTable的长度默认60013,StringTableSize设置数值没有要求 在jdk8开始,设置StringTable的长度,1009是可设置的最小值
String内存分配
Java语言中有8种基本数据类型和一种比较特殊的类型String,这些类型为了使它们运行过程中速度更快,更节省内存,都提供了一种常量池的概念 常量池类似于一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String的常量池比较特殊,主要使用方法有两种 直接使用双引号声明出的String对象会直接存储在常量池中 不使用双引号声明的String对象,可以使用String提供的intern()方法 Java6及以前,字符串常量池存放在永久代 Java7中对字符串池的逻辑做了改变,将字符串常量池位置调整到Java堆内 所有的字符串都保存在堆中,和其他普通对象一样,这样可以让你在进行调优应用时仅需调整堆大小就可以了 字符串常量池概念原本使用得比较多,但是这个改动使我们有足够的理由重新考虑使用String.intern() Java8元空间,字符串常量池在堆中
字符串拼接操作
@Test public void test1 ( ) { String s1 = "a" + "b" + "c" ; String s2 = "abc" ; System . out. println ( s1 == s2) ; System . out. println ( s1. equals ( s2) ) ; } true true
常量池中不会存在相同内容的常量 只要其中有一个是变量,结果就在堆中,变量拼接原理是StringBuilder
@Test public void test2 ( ) { String s1 = "javaEE" ; String s2 = "hadoop" ; String s3 = "javaEEhadoop" ; String s4 = "javaEE" + "hadoop" ; String s5 = s1 + "hadoop" ; String s6 = "javaEE" + s2; String s7 = s1 + s2; System . out. println ( s3 == s4) ; System . out. println ( s3 == s5) ; System . out. println ( s3 == s6) ; System . out. println ( s3 == s7) ; System . out. println ( s5 == s6) ; System . out. println ( s5 == s7) ; System . out. println ( s6 == s7) ; String s8 = s6. intern ( ) ; System . out. println ( s3 == s8) ; } @Test public void test3 ( ) { String s1 = "a" ; String s2 = "b" ; String s3 = "ab" ; String s4 = s1 + s2; System . out. println ( s3 == s4) ; }
@Test public void test4 ( ) { final String s1 = "a" ; final String s2 = "b" ; String s3 = "ab" ; String s4 = s1 + s2; System . out. println ( s3 == s4) ; } @Test public void test5 ( ) { long start = System . currentTimeMillis ( ) ; method2 ( 100000 ) ; long end = System . currentTimeMillis ( ) ; System . out. println ( "花费时间:" + ( end - start) ) ; } private void method1 ( int highLevel) { String src = "" ; for ( int i = 0 ; i < highLevel; i++ ) { src += "a" ; } } private void method2 ( int highLevel) { StringBuilder sb = new StringBuilder ( ) ; for ( int i = 0 ; i < highLevel; i++ ) { sb. append ( "a" ) ; } }
如果拼接的结果调用intern()方法,则该去将常量池中还没有的字符串对象放入池中,并返回些对象地址
intern的使用
JDK6中,将这个字符串对象尝试放入串池 如果串池中有,则并不会放入,返回已有的串池中的对象地址 如果没有,会把此对象复制一份 ,放入串池,并返回串池中的对象地址 JDK7起,将这个字符串对象尝试放入串池 如果串池中有,则并不会放入,返回已有的串池中的对象地址 如果没有,会把此对象的引用地址复制一份 ,放入串池,并返回串池中的对象地址
public class StringIntern { public static void main ( String [ ] args) { String s = new String ( "1" ) ; s. intern ( ) ; String s2 = "1" ; System . out. println ( s == s2) ; String s3 = new String ( "1" ) + new String ( "2" ) ; s3. intern ( ) ; String s4 = "12" ; System . out. println ( s3 == s4) ; }
}
G1的String去重操作
许多Java应用做的测试得出以下结果 堆存活数据集合里面String对象占了25% 堆存活数据集合里面重复的String对象有13.5% String对象的平均长度是45 许多大规模的Java应用的瓶颈在于内存,测试表明,在这些类型的应用里,Java堆中存活的数据集合差不多25%是String对象 ,更进一步,这里面差不多一半String对象是重复的,重复的意思是说string1.equals(string2)=true,堆上存在重复的string对象必然是一种内存资源浪费 ,这个项目将在G1垃圾收集器中实现自动持续对重复的String对象进行去重,这样就避免浪费内存 实现 当垃圾收集器工作的时候,会访问堆上存活的对象,对每一个访问的对象都会检查是否是候选的要去重的String对象 如果是,把这个对象的一个引用插入到队列中等待后续的处理,一个去重的线程在后台运行,处理这个队列,处理队列的一个元素意味着从队列删除这个元素,然后尝试去重它引用的String对象 使用一个hashtable来记录所有的被String对象使用的不重复的char数组,当去重的时候,会查看这个hashtable,不看堆上是否已经存在一个一模一样的char数组 如果存在,String对象会被调整引用那个数组,释放对原来的数组的引用,最终被垃圾收集器回收 如果查看失败,char数组会被插入到hashtable,这样以后的时候就可以共享这个数组 命令行选项 UseStringDeduplication (bool):开启String去重,默认是不开启的,需要手动开启 PrintStringDeduplicationStatistics(bool):打印详细的去重统计信息 StringDeduplicationAgeThreshold(uintx):达到这个年龄的String对象被认为是去重的候选对象