Java String

149 阅读4分钟

STRING

FROM JVM

1、String基本特性

字符串,用一对引号表示("")

实例化的方式

// 1. 这种字面量的赋值方式声明的字符串存在于字符串常量池中, 字符串常量池中不存储相同的字符串
// 如何保证? String Pool底层是一个固定大小的 Hashtable,默认长度60013
// 也可以使用 -XX:StringTableSize设置长度,最小是1009

String s1 = "CUMT";
// 2. 
String s2 = new String("CUMT");

String类 默认为final,不能被继承

String类 实现了Serializable接口:字符串是支持序列化的

String类 实现了Comparable接口:String对象可以比较大小

String类 在JDK8及以前是用private final char[] 表示,JDK9以后是用private final byte[]表示,原因:

  1. String在堆空间中占据了很大部分,有必要对其内部结构进行优化
  2. 研究发现String大部分存储的都是拉丁字符,也就是说,一个char占据两个字节,但是其中表示的东西一多半没用上,所以改成byte一个字节,为了兼容像中文这类的语言,每一个byte数组还会多一个字符码标示,如果用中文还是用char数组来存
  3. 基于String类的StringBuffer、StringBuilder也同样做了修改

String实例 代表不可变的字符序列,不可变性,简述一下:

  • 对字符串重新赋值需要重写指定内存区域,不可以使用原有的value进行赋值
  • 对现有字符串进行连接、替换操作,都需要重新指定内存区域

2、String内存分配

String在内存中放在哪?字符串常量池

  • Java6及以前字符串常量池放在方法区永久代中

  • Java7及以后中所有常量池都保存在堆中

调整原因(Java6 ->)?

  • permSize较小,放大量字符串容易使得永久代OOM
  • 永久代回收频率低,无法释放内存

3、字符串拼接操作

  1. 常量和常量的拼接结果在常量池中,原理是编译期优化
  2. 常量池中不允许存放相同的常量
  3. 只要其中有一个是变量,结果就在堆(非常量池空间)中,变量拼接原理是``StringBuilder`
  4. 如果拼接的结果调用intern(),则主动将常量池中还没有的字符串对象放入池中,返回地址引用
public void test(){
		String s1 = "a";
  	String s2 = "b";
  	String s3 = "ab";
  	
  	/*
  	 * new StringBuilder().append("a").append("b");
  	 * toString(); ---> 约等于 new String("ab");
  	 */
  	String s4 = s1 + s2;
  
  	System.out.pringln(s3 == s4); // false
  
  ''''''------------''''''
  	final String s1 = "a";
  	final String s2 = "b";
  	
  	String s3 = "ab";
  	// final 修饰的算常量	 
  	String s4 = s1 + s2;
  	System.out.println(s3 == s4); //true
  
}

4、intern()方法

String s = "abc".intern();
String m = "mn";
String q = m.intern();

Java 6中:

判断字符串常量池中是否存在该值,如果存在则返回常量池中地址,如果不存在该值,则在常量池中复制一份儿并返回此字符串的地址

Java7及以后

判断字符串常量池中是否存在该值,如果有则返回常量池中的地址,如果没有,会把对象的引用地址复制一份儿放回串池,并返回串池中保存的引用地址

关于StringBuilder对象的toString

会创建一个String对象,但是不会去往常量池中放入

5、G1中String去重操作

去哪里的重? Heap里面的String对象,因为在字符串常量池中本身就不会有重复的,但是在字符串产量池以外的空间中,存在重复的字符串对象

String s1 = new String("abc");
String s2 = new String("abc");

目的就是让s1、s2指向同一个对象从而减少堆内存

为什么要去重?

实验表明,Java堆中大概有25%的无法被回收的空间被字符串对象占据,同时这其中有一半的字符串存在重复现象,严重消耗了内存,所以我们有必要进行优化

如何去重?

  1. 当垃圾收集器工作时,会访问堆上存活的对象,对每一个访问的对象都会检查是否是候选的要去重的String对象
  2. 如果是,就把该对象的引用插入到队列中等待后续的处理,一个去重的线程在后台运行,处理队列中的一个元素意味着从队列中删除这个元素,并尝试去去重它引用的对象
  3. 用一个hashtable记录所有被String对象使用的不重复的char[],当去重的时候会查这个table,看堆上是否存在一个一模一样的char[]
  4. 如果存在,String对象会被调整引用哪个数组,释放堆原来数组的引用,最终会被垃圾回收器回收
  5. 如果不存在,char[]会被插入到hashtable,这样就可以共享这个char[]