美文网首页
String整理

String整理

作者: lesline | 来源:发表于2019-03-15 09:33 被阅读0次

    浅谈StringBuilder - 简书
    理解Java常量池 - gegewx - 博客园
    通过反编译深入理解Java String及intern - liuxiaopeng - 博客园

    深入分析String.intern和String常量的实现原理 - 简书
    深入解析String#intern -


    总结:

    原理

    常量存储内容

    • java6常量池在永久代
    • java7+java8常量池在堆中常量池中可以存储堆中的引用
    • 方法名、java等字符串在代码执行前已在常量池中存在

    intern定义:

    • 如果常量池中存在当前字符串,就会直接返回当前字符串.如果常量池中没有此字符串,会将此字符串放入常量池中后再返回,返回的是常量池中的引用
    • java7及以后 常量池中存储的是:堆中的引用
      如果堆中有,常量池中没有,如何确定堆中是有的?因为执行intern方法的对象已确定。

    对象声明

    • newString(“china”)每次执行都会在堆中建一个新的对象,但常量区就一个,返回堆中的引用

    对象数

    String str1 = “a”;//一个对象 放在常量池中(不会指向堆中的对象)
    String str1 = new String(“a”);//两个对象(无论是jdk1.6还是1.7) 一个堆中 一个常量区 返回堆中的
    String s3 = new String(“1”) + new String(“1”);//四个对象,一个常量区的1 一个堆中的11 两个堆中的匿名对象1
    String c = “a” + “b” + “c”;  //应该是一个,编译时已确定(三个对象,都在常量区中 操作会加到常量池中)
    

    String test = “test”;

    通过反编译出来的字节码可以看出字符串 “test” 在常量池中的定义方式:

    #2 = String             #14            // test
    #14 = Utf8              test
    

    在main方法字节码指令中,0 ~ 2行对应代码 String test = “test”; 由两部分组成:ldc #2 和 astore_1。

     // main方法字节码指令
     public static void main(java.lang.String[]);
       Code:
          0: ldc           #2                  // String test
          2: astore_1
          3: return
    

    1、Test类加载到虚拟机时,”test"字符串在Constant pool中使用符号引用symbol表示,当调用 ldc #2 指令时,
    如果Constant pool中索引 #2 的symbol还未解析,则调用C++底层的 StringTable::intern 方法生成char数组,
    并将引用保存在StringTable和常量池中,当下次调用 ldc #2 时,可以直接从Constant pool根据索引 #2 获取 "test" 字符串的引用,
    避免再次到StringTable中查找。
    2、astore_1指令将”test”字符串的引用保存在局部变量表中。

    总结:String test = “test”; 只在常量池中操作,跟堆中没关系。

    ldc:Push item from run-time constant pool,从常量池中加载指定项的引用到栈。
    astore_<n>:Store reference into local variable,将引用赋值给第n个局部变量
    
    ldc      #2:加载常量池中的第二项("baseStr")到栈中。
    astore_1 将引用类型或returnAddress类型值存入局部变量1
    
    指令格式: 
    astore index: 将栈顶数值(objectref)存入当前frame的局部变量数组中指定下标(index)处的变量中,栈顶数值出栈。
    astore_0  该指令的行为类似于astore指令index为0的情况。
    

    String str1 = new String(“a”);

    String str2 = new String("str");
    System.out.println(str2 == "str");//false
    

    javap -c

    Constant pool:
       #1 = Methodref          #6.#22         // java/lang/Object."<init>":()V
       #2 = Class              #23            // java/lang/String
       #3 = String             #24            // str
       #4 = Methodref          #2.#25         // java/lang/String."<init>":(Ljava/lang/String;)V
    
        Code:
           0: new           #2                  // class java/lang/String
           3: dup
           4: ldc           #3                  // String str
           6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
           9: astore_1
          10: return
    

    可以看出生成了两个对象:一个是第4行在常量池中生成的对象,一个是第6行初始化的对象。

    String str2 = new String("str")+new String("01");

            String str2 = new String("str") + new String("01");
            System.out.println(str2 == "str01");//false
            System.out.println(str2.intern() == "str01");//true
    
        @Test
        public void test1() {
            String str2 = new String("str")+new String("01");
            str2.intern();
            String str1 = "str01";
            System.out.println(str2==str1);//true
        }
        @Test
        public void test2() {
            String str1 = "str01";
            String str2 = new String("str")+new String("01");
            str2.intern();
            System.out.println(str2 == str1);//false
        }
    

    这个原因主要是从JDK 1.7后,HotSpot 将字符常量池从永久代移到了堆中,正因为如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。

    所以:test1中,因为常量池中没有“str01”这个字符串,所以会在常量池中生成一个对堆中的“str01”的引用,而在进行字面量赋值的时候,常量池中已经存在,所以直接返回该引用即可,因此str1和str2都指向堆中的字符串,返回true。
    调换位置以后,因为在进行字面量赋值(String str1 = “str01")的时候,常量池中不存在,所以str1指向的常量池中的位置,而str2指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,所以返回false。

    看以下示例:

            String s1 = new String("a") + new String("b");
            /**
             String s11 = "ab"; //存在这个前后值不一样
             是否存在 String s11 = "ab";
             不存在 存在
             true   true
             true   false
             true   false
             */
            System.out.println(s1.intern() == "ab");
            System.out.println(s1.intern() == s1);
            System.out.println(s1 == "ab");
    
            String s2 = new String("ja") + new String("va");
            System.out.println(s2.intern() == "java");//true
            System.out.println(s2.intern() == s2);//false
            System.out.println(s2 == "java");//false
    

    原因: 不存在 String s11 = "ab”;时,s1.intern()会将在常量池中查找是否存在“ab”,查找不存在,会在常量池中生成对s1对象的引用,所以s1.intern() == "ab" ,s1 == "ab”;
    存在 String s11 = "ab”;时,s11 = "ab"; 会常量直接操作常量池中生成常量,不会查找是否存在“ab”,所以生成的常量不会引用堆中(s1)对角,所以s1.intern() == "ab" ,s1 <> "ab”。


    引用

    intellij idea中使用External Tool实现javap等工具 - 简书

    相关文章

      网友评论

          本文标题:String整理

          本文链接:https://www.haomeiwen.com/subject/qwwkmqtx.html