zl程序教程

您现在的位置是:首页 >  后端

当前栏目

什么是字符串常量池_常量池中的字符串是对象吗

对象 什么 字符串 常量 池中
2023-06-13 09:12:47 时间

大家好,又见面了,我是你们的朋友全栈君。

JDK1.8-1.9,String底层从char数组变成了byte数组,原因是部分字符仅占一个byte,而堆中含有大量的String字符串,该优化能节省较多空间。

关于常量池

常量池/Class常量池(Constant pool)

常量池,也叫 Class 常量池(常量池==Class常量池)。Java文件被编译成 Class文件,Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项就是常量池,常量池是当Class文件被Java虚拟机加载进来后存放在方法区 各种字面量 (Literal)和 符号引用 。

运行时常量池(Runtime constant pool)

运行时常量池是方法区的一部分。运行时常量池是当Class文件被加载到内存后,Java虚拟机会 将Class文件常量池里的内容转移到运行时常量池里(运行时常量池也是每个类都有一个)。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中

只存放字符串引用

字符串常量池(String pool/String table)

字符串常量池又称为:字符串池,全局字符串池,英文也叫String Pool。 在工作中,String类是我们使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间:字符串常量池。字符串常量池由String类私有的维护

堆里边的字符串常量池存放的是字符串的引用或者字符串(两者都有)

比如new String(“test”)就会先在常量池中检查是否存在,不存在则在常量池中创建,然后堆中创建其引用。

常量池和字符串常量池的版本变化

  • 在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
  • 在JDK1.7 字符串常量池、静态变量等被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说 字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
  • 在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
  • 补充:
    • 对象只能存在于堆中(虚拟机规范的定义),所以String实体只能存在于堆中,
    • 运行时常量池存放的是字面量引用
    • 使用双引号方式显式声明的字符串,则直接放入字符串常量池中(final修饰的“变量”可以直接看作双引号字面量)

StringTable为什么要调整(1.6-1.7)

  • permSize默认比较小
  • 永久代垃圾回收频率低

字符串拼接操作

  • 常量与常量的拼接结果在常量池,原理是编译器优化
  • 常量池中不会存在相同内容的常量
  • 只要其中一个是变量,结果就在堆中。变量拼接的原理是StringBuilder(final不算变量),返回String对象
  • 如果拼接的结果调用intern()方法,则注定将常量池中还没有的字符串对象放入池中,并返回此对象地址

所以建议多使用final定义字符串,并且不使用new,对于new String()声明final不会优化

示例

    public static boolean IsInPool(String str){ 
   
        return str == str.intern();
    }
    public static void main(String[] args) { 
   
        final String a1 = "a";
        final String a2 = "b";
        String a3 = a1 + a2;
        String limit1 = "ab";

        String a4 = "c";
        String a5 = "d";
        String a6 = a4 + a5;
        String limit2 = "cd";

        String b1 = new String("e");
        String b2 = new String("f");
        String b3 = b1 + b2;
        String limit3 = "ef";

        final String b4 = new String("g");
        final String b5 = new String("h");
        String b6 = b4 + b5;
        final String limit4 = "gh";

        System.out.println(IsInPool(a3));//true
        System.out.println(IsInPool(a6));//false
        System.out.println(IsInPool(b3));//false
        System.out.println(IsInPool(b6));//false

字符串的创建与常量池

String两种创建方式

  • 方式一(str值和字符串常量池中字面量地址相等)
String str = "abc"
  1. 检查字符串常量池是否存在该字符串,存在则不创建并且返回该字符串的引用
  2. 不存在则在字符串常量池中创建该字符串常量并返回其常量池中地址
  • 方式二(str值和字符串常量池中字面量地址不相等)
String str = new String("abc")
  1. 检查字符串常量池是否存在该字符串,存在则不创建,不存在则创建该字符串常量
  2. 在堆中创建该对象
  3. 返回堆中该对象的引用

普遍地

使用双引号方式显式存在的字符串,则直接放入字符串常量池中(final修饰的“变量”可以直接看作双引号字面量)

一些测试(JDK1.8)

情况一:

    public static void main(String[] args) { 
   
        String a1 = "a";
        String a2 = new String("b");
        String a3 = "c";
        String a4 = a1 + a2;
        String a5 = a1 + a3;
// String at = "a"+"c";
// String at1 = "a"+"b";

        System.out.println(IsInPool(a1));//true
        System.out.println(IsInPool("b"));//true
        System.out.println(IsInPool(a2));//false
        System.out.println(IsInPool(a3));//true

        System.out.println(IsInPool(a4));//true
        System.out.println(IsInPool("ab"));//true
        System.out.println(IsInPool(a5));//true
        System.out.println(IsInPool("ac"));//true
    }

情况二:

    public static void main(String[] args) { 
   
        String a1 = "a";
        String a2 = new String("b");
        String a3 = "c";
        String a4 = a1 + a2;
        String a5 = a1 + a3;
        String at = "a"+"c";
        String at1 = "a"+"b";

        System.out.println(IsInPool(a1));//true
        System.out.println(IsInPool("b"));//true
        System.out.println(IsInPool(a2));//false
        System.out.println(IsInPool(a3));//true

        System.out.println(IsInPool(a4));//false
        System.out.println(IsInPool("ab"));//true
        System.out.println(IsInPool(a5));//false
        System.out.println(IsInPool("ac"));//t
    }

IsInpool函数

    //使用这个函数需要在这个函数前使用双引号字面量使得字符串直接被加入常量池中
	//使用顺序应为待测字符串->字面量->IsInPool

	public static boolean IsInPool(String str){ 
   
        return str == str.intern();
    }

对于intern函数的理解

调用这个方法之后就是去看当前字符串是否在字符串常量池中已经存在引用

(1)存 在:那就直接返回该字符串在字符串常量池中所对应的地址给栈中要引用这个字符串的变量。

(2)不存在: ① jdk 1.6:先在字符串常量池中创建该字符串,地址与堆中字符串地址不相同。然后再返回刚创建的字符串在字符串常量池中所对应的地址给栈中要引用这个字符串的变量。

② jdk 1.7及以后:直接将堆中(不是字符串常量池中)该字符串的地址复制到字符串常量池中,这样字符串常量池就有了该字符串的地址引用,也可以说此时字符串常量池中的字符串只是一个对 堆中字符串对象的引用,它们两个的地址相同,然后再把这个地址返回给栈中要引用这个字符串的变量。

对测试的解释

  • 第一次两个拼接测试为true,因为intern函数将堆中字符串对象引用复制到字符串常量池中,所以二者自然相等
  • 第二次两个拼接测试为false,因为intern检查到已经存在该字符常量,且堆常量池中保存的是字符串的值,二者自然不相等

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/164773.html原文链接:https://javaforall.cn