【JVM之内存与垃圾回收篇】StringTable

StringTable String的基本特性

String:字符串,使用一对 "" 引起来表示

String s1 = "Nemo"; // 字面量的定义方式

String s2 = new String("Nemo");

String 声明为 final 的,不可被继承

String 实现了 Serializable 接口:表示字符串是支持序列化的。
实现了 Comparable 接口:表示 string 可以比较大小

String 在 jdk8 及以前内部定义了 private final char[] value 用于存储字符串数据。
JDK9 时改为 byte[]

为什么JDK9改变了结构

官方说明:

String 类的当前实现将字符存储在 char 数组中,每个字符使用两个字节(16 位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且,大多数字符串对象只包含拉丁字符。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部 char 数组中有一半的空间将不会使用。

我们建议改变字符串的内部表示 class 从 utf - 16 字符数组到字节数组 + 一个 encoding-flag 字段。新的 String 类将根据字符串的内容存储编码为 ISO-8859-1/Latin-1(每个字符一个字节)或 UTF-16(每个字符两个字节)的字符。编码标志将指示使用哪种编码。

结论:String 再也不用 char[] 来存储了,改成了 byte[] 加上编码标记,节约了一些空间

// 之前 private final char value[]; // 之后 private final byte[] value

同时基于 String 的数据结构,例如 StringBuffer 和 StringBuilder 也同样做了修改

String的不可变性

String:代表不可变的字符序列。简称:不可变性。

当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的 value 进行赋值。

当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。

当调用 String 的 replace() 方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。

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

代码

/** * String的不可变性 * * @author: Nemo */ public class StringTest1 { public static void test1() { // 字面量定义的方式,“abc”存储在字符串常量池中 String s1 = "abc"; String s2 = "abc"; System.out.println(s1 == s2); s1 = "hello"; System.out.println(s1 == s2); System.out.println(s1); System.out.println(s2); System.out.println("----------------"); } public static void test2() { String s1 = "abc"; String s2 = "abc"; // 只要进行了修改,就会重新创建一个对象,这就是不可变性 s2 += "def"; System.out.println(s1); System.out.println(s2); System.out.println("----------------"); } public static void test3() { String s1 = "abc"; String s2 = s1.replace('a', 'm'); System.out.println(s1); System.out.println(s2); } public static void main(String[] args) { test1(); test2(); test3(); } }

运行结果

true false hello abc ---------------- abc abcdef ---------------- abc mbc 面试题 /** * 面试题 * * @author: Nemo */ 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); System.out.println(ex.ch); } }

输出结果

good best 注意

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

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 info="atguigu.com";

如果不是用双引号声明的 string 对象,可以使用 string 提供的 intern() 方法。

Java 6 及以前,字符串常量池存放在永久代

Java 7 中 oracle 的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到 Java 堆内

所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。

字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在 Java 7 中使用 String.intern()。

Java8 元空间,字符串常量在堆

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgjgfw.html