JVM---StringTable(字符串常量池)

本文详细介绍了JVM中StringTable(字符串常量池)的概念,包括String的基本特性、内存分配、基本操作以及intern()方法的使用。文章探讨了为何JDK9将String的底层结构从char[]改为byte[],并分析了不同版本中String对象的内存分析。此外,还讨论了StringTable的垃圾回收和JDK8引入的String去重操作,以提高内存效率。
摘要由CSDN通过智能技术生成

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 [] 存储?

  1. String 类的当前实现将字符存储在 char 数组中,每个字符使用两个字节(16位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且大多数字符串对象只包含拉丁字符(Latin-1),这些字符只需要一个字节的存储空间,因此这些字符串对象的内部 char 数组中有一半的空间将不会使用,产生了大量浪费;
  2. 之前 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();
                }

            }
        }
    }
}
  1. -XX:StringTableSize=1009 :程序耗时 505ms.
  2. -XX:StringTableSize=100009 &#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值