String 相关知识

156 阅读3分钟

video link : www.bilibili.com/video/BV1PJ…

1、 String的基本特性

String声明为final,不可继承

String实现了Serializable接口,支持序列化;实现了Comparable接口,可比较大小

String在java8及之前内部定义了final char[] value用于存储字符串数据;java9改为byte[]

因为对于英文字符来说,用一个byte就能存储

而在java8及之前的char,使用了两个byte,浪费了空间

java9中,使用byte加编码标记

String代表不可变的字符序列

当对字符串重新赋值、进行连接操作、replace()方法修改时,需要重新指定内存区域赋值,不能使用原有的value

通过字面量的方式(区别于new)给一个字符串赋值时,此时的字符串值声明在字符串常量池中

字符串常量池中不会存储相同内容的字符串

String pool是一个固定大小的HashTable,默认值是1009。如果String非常多,会造成严重hash冲突,从而导致链表很长,而链表长了之后直接会导致调用intern()时性能大幅下降

使用-XX:StringTableSize可设置该参数

java6中默认1009且设置没有要求

java7中默认是60013且设置没有要求

java8中默认60013且可设置的最小值为1009

2、String的内存分配

String常量池主要使用方法有两种:

  1. 直接使用双引号声明出来的String对象会直接存储在常量池中
  2. 如果不是使用双引号声明的String对象,可使用intern()

字符串常量池位于堆中

3、字符串拼接操作

  1. 常量与常量的拼接结果在常量池,编译器进行该优化
  2. 常量池中不会存在相同内容的常量
  3. 只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
  4. 如果拼接的结果调用intern(),则主动将常量池中还没有的字符串放入池中,并返回此对象地址
/**
如下代码执行细节:
StringBuilder s = new StringBuilder();
s.append("a");
s.append("b");
s.toString();
*/
String s1 = "a";
String s2 = "b";
String s3 = s1 + s2;

/**
对于如下final修饰的字符串,仍然使用编译期优化
PS: 能用final的场景,尽量用final
*/
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
String src = "";
for (int i = 0; i < 1000000; i++) {
    // 每次循环都会创建StringBuilder、String
    // 创建的对象多,进行GC时,消耗也更大
    src += "a"; 
}

StringBuilder src = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
    // 只创建一个对象
    // 改进:如果能大致确定最终字符串长度,可初始化StringBuilder容量,减少扩容次数
    src.append("a"); 
}

4、intern()的使用

jdk1.6中:

  1. 如果串池中有,则不会放入,返回已有的串池中的对象的地址(s.intern()String ss = s.intern()不同)
  2. 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址

jdk1.7起:

  1. 如果串池中有,则不会放入,返回已有的串池中的对象的地址(s.intern()String ss = s.intern()不同)
  2. 如果没有,会把此对象的引用地址复制一份,放入串池,并返回串池中的对象地址

当程序中有大量重复的字符串时,调用intern()能够节省内存

// for(1...1m): 会产生1m个String对象需要维护
String s1 = new String("test");
// for(1...1m): 调用intern()后,产生的很多String,会很快销毁无需维护
String s2 = new String("test").intern()