StringTable(字符串常量池)
字符串常量池
String的基本特性
- String:字符串,使用一对 " " 引起来表示。
String s1 = "atguigu" ; // 字面量的定义方式
String s2 = new String("hello"); // new 对象的方式
- String 被声明为 final 的,不可被继承。
- String 实现了 Serializable 接口:表示字符串是支持序列化的;实现了 Comparable 接口:表示 String 可以比较大小。
- String 在 jdk8 及以前内部定义了 final char value[] 用于存储字符串数据。JDK9 时改为 byte[] 。
为什么 JDK9 改变了 String 的结构?
官方文档:http://openjdk.java.net/jeps/254
为什么改为 byte [] 存储?
- String 类的当前实现将字符存储在 char 数组中,每个字符使用两个字节(16位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且大多数字符串对象只包含拉丁字符(Latin-1),这些字符只需要一个字节的存储空间,因此这些字符串对象的内部 char 数组中有一半的空间将不会使用,产生了大量浪费;
- 之前 String 类使用 UTF-16 的 char[] 数组存储,现在改为 byte[] 数组外加一个编码标识存储。该编码表示如果你的字符是 ISO-8859-1 或者 Latin-1 ,那么只需要一个字节存储。如果你是其它字符集,比如 UTF-8 ,仍然用两个字节存储,这样 String 再也不用 char[] 来存储了,改成了 byte [] 加上编码标记,节约了一些空间,同时基于 String 的数据结构,例如StringBuffe r和 StringBuilder 也同样做了修改。
// 之前
private final char value[];
// 之后
private final byte[] value
String 的基本特性
- String:代表不可变的字符序列,简称:不可变性。
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的 value 进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。
- 当调用 String 的 replace() 方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。 - 通过字面量的方式(区别于 new )给一个字符串赋值,此时的字符串值声明在字符串常量池中。
当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的 value 进行赋值。
示例代码:重新赋值
@Test
public void test1() {
String s1 = "abc";//字面量定义的方式,"abc"存储在字符串常量池中
String s2 = "abc";
s1 = "hello";
System.out.println(s1 == s2);//判断地址:true --> false
System.out.println(s1);//
System.out.println(s2);//abc
}
输出:
false
hello
abc
字节码指令:
- 取字符串 “abc” 时,使用的是同一个符号引用:#2
- 取字符串 “hello” 时,使用的是另一个符号引用:#3
当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。
示例代码:字符串连接
@Test
public void test2() {
String s1 = "abc";
String s2 = "abc";
s2 += "def";
System.out.println(s2);//abcdef
System.out.println(s1);//abc
}
当调用 string 的 replace() 方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。
示例代码:调用 replace() 方法
@Test
public void test3() {
String s1 = "abc";
String s2 = s1.replace('a', 'm');
System.out.println(s1);//abc
System.out.println(s2);//mbc
}
一道笔试题
public class StringExer {
String str = new String("good");
char[] ch = {
't', 'e', 's', 't'};
public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
StringExer ex = new StringExer();
ex.change(ex.str, ex.ch);
System.out.println(ex.str);//输出:good
System.out.println(ex.ch);//输出:best
}
}
- str 的内容并没有变:“test ok” 位于字符串常量池中的另一个区域(地址),进行赋值操作并没有修改原来 str 指向的引用的内容。
String 的底层结构
字符串常量池是不会存储相同内容的字符串的。
- String 的 String Pool(字符串常量池)是一个固定大小的 Hashtable ,默认值大小长度是1009。如果放进 String Pool 的 String 非常多,就会造成 Hash 冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用 String.intern() 方法时性能会大幅下降。
- 使用 -XX:StringTablesize 可设置 StringTable 的长度;
- 在 JDK6 中 StringTable 是固定的,就是1009 的长度,所以如果常量池中的字符串过多就会导致效率下降很快,而 StringTablesize 设置没有要求;
- 在 JDK7 中,StringTable 的长度默认值是 60013 ,StringTablesize 设置没有要求;
- 在 JDK8 中,StringTable 的长度默认值是 60013,StringTable 可以设置的最小值为1009。
JDK8 下:
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
StringTable size of 10 is invalid; must be between 1009 and 1305843009213693951
测试不同 StringTable 长度下程序的性能,示例代码如下:
/**
* 产生10万个长度不超过10的字符串,包含a-z,A-Z
*/
public class GenerateString {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("words.txt");
for (int i = 0; i < 100000; i++) {
//1 - 10
int length = (int)(Math.random() * (10 - 1 + 1) + 1);
fw.write(getString(length) + "\n");
}
fw.close();
}
public static String getString(int length){
String str = "";
for (int i = 0; i < length; i++) {
//65 - 90, 97-122
int num = (int)(Math.random() * (90 - 65 + 1) + 65) + (int)(Math.random() * 2) * 32;
str += (char)num;
}
return str;
}
}
public class StringTest2 {
public static void main(String[] args) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("words.txt"));
long start = System.currentTimeMillis();
String data;
while((data = br.readLine()) != null){
data.intern(); //如果字符串常量池中没有对应data的字符串的话,则在常量池中生成
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));//1009:143ms 100009:47ms
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- -XX:StringTableSize=1009 :程序耗时 505ms.
- -XX:StringTableSize=100009 &#