zoukankan      html  css  js  c++  java
  • 对Java字符串的探究

    问题的出发点

    在网上看到一道题:

    1
    
    String str = new String("abc");
    

    以上代码执行过程中生成了多少个 String 对象?

    答案写的是两个。”abc”本身是一个,而 new 又生成了一个。

    “abc”是什么

    查看这句程序的字节码,如下:

    1
    2
    3
    4
    5
    
    NEW String
        DUP
        LDC "abc"
        INVOKESPECIAL String.<init>(String) : void
        ASTORE 1
    

    指令ldc indexbyte的含义:将两字节的值从 indexbyte 索引的常量池中的项中推到方法栈上。

    指令LDC "abc"说明了”abc”并不是直接以对象存在的,而是存在于常量池的索引中。String 的构造函数调用命令实际使用的就是 String 类型作为参数,那么,栈上应该有一个 String 类型的索引。

    由此我们得出,在字节码中,ldc 命令在常量池中找到了能索引到“abc”那个 String 对象的索引值。

    常量池

    常量池是类文件(.class)文件中的一部分,记录了许多常量信息,索引的字符串信息。

    由于 Java 是动态加载的,类文件并没有包含程序运行时的内存布局,方法调用等无法直接记录出方法的物理位置,常量池通过索引的方法解决了这个问题。

    常量池中存着许多表,其中 Constant_Utf8_info 表中,记录着会被初始化为 String 对象的字符串的字面值(iteral)。 而在 String 的 java doc 中,有对 String 字面值的说明:

    All string literals in Java programs, such as “abc”, are implemented as instances of this class.

    在 Java 编译的过程中,确定下来的 String 字面值都先被优化记录在常量池中(那些双引号字符串,都是以 CONSTANT_utf8_info 的形式存储在常量池中的)。也就是说,Java 源代码文件中出现的那些诸如”abc”字符串,都已经被提前放在了常量池中。

    可以使用如下代码验证这一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    public class Program
    {
        public static void main(String[] args)
        {
           String str1 = "Hello";
           String str2 = "Hello";
           System.out.print(str1 == str2);
        }
    }
    

    输出结果是 true.说明”Hello”作为对象是被程序从同一个内存空间读取出来的。

    常量池是编译时产生的,存在于类文件中(*.class 文件)。运行时,JVM 中每个对象都拥有自己的运行时常量池(run time constant pool)。

    字符串池

    我在 String 的 java doc 中又发现了一个有趣的 method:intern() ,我翻译如下:

    当 intern 方法被调用,如果池中已经拥有一个与该 String 的字符串值相等(即 equals()调用后为 true)的 String 对象时,那么池中的那个 String 对象会被返回。否则,池中会增加这个对象,并返回当前这个 String 对象。

    其中有介绍一个字符串池的东西:字符串池(String pool),初始是空的,由类私有的控制。

    查看 java.lang.String 的源代码,发现 Intern()方法是一个 native 方法,即本地实现的方法,而不是一个 java 方法,这让我们不能直观的看到字符串池的实现细节。不过能够理解字符串池其实是类似于线程池的缓冲器,可以起到节约内存的作用。如下代码可以验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    package biaobiaoqi.thinkingInJava;
    
    public class Test {
        public static void main(String[] args){
    
            String strA1 = "ab";
            String strA2 = "c";
            String strB1 = "a";
            String strB2 = "bc" ;
            System.out.println((strA1+strA2).intern() == (strB1 + strB2).intern());
    
        }
    }
    

    输出结果为 true。

    现代的 JVM 实现里,考虑到垃圾回收(Garbage Collection)的方便,将 heap 划分为三部分: young generation 、 tenured generation(old generation)和 permanent generation( permgen )

    字符串池是为了解决字符串重复的问题,生命周期长,它存在于 permgen 中。

    总结

    编译 Java 源代码时,源文件中出现的双引号内的字符串都被收纳到常量池中,用 CONSTANT_utf8_info 项存储着。

    JVM 中,相应的类被加载运行后,常量池对应的映射到 JVM 的运行时常量池中。其中每项 CONSTANT_utf8_info(也就试记录那些字符串的)都会在常量引用解析时,自动生成相应的 internal String,记录在字符串池中。

    回过头来看看文章刚开始的那个问题。

    1
    
    String str = new String("abc");
    

    这里确实是有两个 String 对象生成了。

    new String("xxx") 创建的 String 对象会在 heap 中重新生成新的 String 对象,绕过字符串池的管辖。而如果使用String str = "xxx"则先查看字符串池 是否已经存在,存在则直接返回 PermGen 中的该 String 对象,否则生成新的 String 对象,并将它加入字符串池中。

    尽量使用String str = "abc";,而不是String str = new String("abc");用 new 的方法肯定会开辟新的 heap 空间,而前者的方法,则会通过 string interning 优化。

    参考资料

  • 相关阅读:
    SQL Server 在多个数据库中创建同一个存储过程(Create Same Stored Procedure in All Databases)
    SQL Server 多实例下的复制
    SQL Server 批量主分区备份(Multiple Jobs)
    SQL Server 批量完整备份
    SQL Server 游标运用:鼠标轨迹字符串分割
    SQL Server 批量主分区备份(One Job)
    SQL Server 即时文件初始化
    (原+转)Ubuntu中设置程序可用的cpu核心
    (原)Non-local Neural Networks
    (原)softmax loss特征为何径向放射状分布(直观理解,非公式推导。。。)
  • 原文地址:https://www.cnblogs.com/biaobiaoqi/p/3308433.html
Copyright © 2011-2022 走看看