ホームページ  >  記事  >  Java  >  Java String で intern メソッドを使用する方法

Java String で intern メソッドを使用する方法

WBOY
WBOY転載
2023-05-05 17:28:131146ブラウズ

    定数プールの概要

    JAVA 言語には 8 つの基本型と特殊な型 String があります。実行を高速化し、より多くのメモリを節約するために、これらの型には定数プールの概念が (メソッド領域に) 提供されています。定数プールは、JAVA システム レベルで提供されるキャッシュに似ています。 8 つの基本タイプの定数プールはすべてシステムによって調整されますが、文字列タイプの定数プールは特別です。

    文字列定数プールを使用するには、主に 2 つの方法があります。

    二重引用符を使用して直接宣言された文字列オブジェクトは、定数プールに直接保存されます。

    String オブジェクトが二重引用符で宣言されていない場合は、String によって提供される intern メソッドを使用して、それを定数プールに入れることができます。

    Intern メソッドの紹介 (JDK7)

    プロトタイプ: publicネイティブ String intern();

    説明:

    文字列定数プールから現在の文字をクエリします。文字列が存在するかどうか (等しいかどうかで判断)。

    • 存在する場合は、定数プール内の文字列参照を返します。

    • 存在しない場合は、String オブジェクト参照を定数プールに保存し、String オブジェクト参照を返します。

    戻り値: すべては、String 変数に対応する文字列定数プールへの参照を返します。

    package com.example;
     
    public class Demo {
        public static void main(String argv[]) {
            String s = "test";
            System.out.println(s == s.intern());
        }
    }

    JDK6以前: falseを出力

    JDK7以降: trueを出力

    原則(JDK6およびJDK7)

    定数プール内の文字列の起源

    JDK6 以前の String.intern() を呼び出す

    • 定数に文字列がある場合プールにある場合、定数が返されます。 プール内のこの文字列への参照

    • 定数プールに参照がない場合は、オブジェクトのコピーをコピーして、定数プールに置きます。 (永続的な世代); 戻り値は、定数プール (永続的な世代) 内の対応する文字列インスタンスへの参照です。

    JDK7 以降は String.intern() を呼び出します。

    • 定数プールに文字列がある場合は、その文字列への参照を返します。 constant pool

    • 定数プールがない場合は、参照をコピーして定数プール (ヒープ) に置きます (JDK1.7 では String 定数プールを Perm 領域からJava ヒープ領域)

    ルーチン テスト

    ルーチン 1:

    package org.example.a;
     
    public class Demo {
        public static void main(String argv[]) {
            String s1 = new String("1");
            s1.intern();
            String s2 = "1";
            System.out.println(s1 == s2);
     
            String s3 = new String("1") + new String("1");
            s3.intern();
            String s4 = "11";
            System.out.println(s3 == s4);
        }
    }

    結果

    jdk6: false false
    jdk7: false true
    jdk8:false true

    ルーチン 2:

    package org.example.a;
     
    public class Demo {
        public static void main(String argv[]) {
            String s1 = new String("1");
            s1.intern();
            String s2 = "1";
            System.out.println(s1 == s2);
     
            String s3 = new String("1") + new String("1");
            String s4 = "11";
            s3.intern();
            System.out.println(s3 == s4);
        }
    }

    上記のコードの 2 番目の部分にスワップがあります。

    結果

    jdk6:false false
    jdk7:false false
    jdk8:false false

    ルーチン分析

    下の図では、緑色の線は String オブジェクトを指すコンテンツを表します。赤い線はアドレスを示しています。

    jdk1.6

    ルーチン 1 とルーチン 2 の分析

    Java String で intern メソッドを使用する方法

    上図に示すように。まず jdk6 の状況について説明します. jdk6 では、定数プールが Perm 領域に配置され、Perm 領域が通常の JAVA ヒープ領域から完全に分離されているため、上記の出力はすべて false になります。前述したように、文字列が引用符を使用して宣言されている場合、その文字列は文字列定数プールに直接生成され、新しい String オブジェクトは JAVA ヒープ領域に配置されます。したがって、JAVA Heap領域のオブジェクトアドレスと文字列定数プールのオブジェクトアドレスを比較すると明らかに異なり、String.internメソッドを呼び出しても関係がありません。

    jdk1.7

    Jdk6 以前のバージョンでは、文字列の定数プールはヒープの Perm 領域に配置されます。Perm 領域は、主に静的領域に格納されます。クラス情報、定数プール、メソッドフラグメントなどのデフォルトのサイズはわずか 4m ですが、定数プールで大量のインターンが使用されると、java.lang.OutOfMemoryError:PermGen スペース エラーが発生します。 jdk7 バージョンでは、文字列定数プールが Perm 領域から通常の Java ヒープ領域に移動されました。移動が必要な主な理由は、Perm エリアが小さすぎるためですが、もちろん、ニュースによると、jdk8 は Perm エリアを直接キャンセルし、新しいメタ エリアを確立しました。 JDK 開発者は、Perm 領域が現在の JAVA 開発にはもはや適していないと考えているはずです。文字列定数プールが JAVA ヒープ領域に移動されましたが、なぜ上記のような結果が出力されるのか説明してください。

    ルーチン 1 の分析

    Java String で intern メソッドを使用する方法

    ##1.String s1 = new String("1");

    分析: このコード行は 2 つのオブジェクト (定数プールの「1」と JavaHeap の文字列オブジェクト) を生成します。 s.intern(); この文は、s1 オブジェクトが定数プールに検索に行き、「1」がすでに定数プールにあることが判明したことを意味します。

    このとき、s1 は Java ヒープ内の文字列オブジェクトを指します。

    2.String s2 = "1";

    分析: このコード行は、定数プール内の「1」オブジェクトを指す s2 への参照を生成します。その結果、s1 と s2 の参照アドレスが異なります。

    3.String s3 = 新しい文字列("1") 新しい文字列("1");

    分析:这行代码生成了2个对象(字符串常量池中的“1” 和 Java Heap中的 s3 引用指向的对象“11”(中间还有2个匿名的new String("1")我们不讨论它)。
    此时s3 是Java Heap中的字符串对象的引用,对象内容是”11″,此时常量池中是没有 “11”对象的。

    4.s3.intern();

    分析:这行代码将 s3中的"11"字符串放入String 常量池中,因为此时常量池中不存在"11"字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个"11"的对象,关键点是 jdk7 中常量池不在Perm区域,而是在堆中了。常量池中不需再存储一份对象了,可以直接存储堆中的引用。这份引用指向s3引用的对象。 也就是说引用地址是相同的。

    此时,s3是Java Heap中的字符串对象的引用,对象内容是”11″,此时常量池中是有 “11”对象,它保存的就是s3引用地址。

    5.String s4 = "11"; 

    这行代码”11″是显式声明的,因此会直接去常量池中创建,创建时发现已经有这个对象了。

    此时:s4 == 常量池的“11”对象引用 == s3引用对象的引用

    例程2的分析

    Java String で intern メソッドを使用する方法

    String s1 = new String("1");

    s1.intern();

    String s2 = "1";

    分析:s1.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("1");的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s1 和 s2 的引用地址是不会相等的。

    String s3 = new String("1") + new String("1");

    分析:这行代码生成了2个对象(字符串常量池中的“1” 和 Java Heap中的 s3 引用指向的对象“11”(中间还有2个匿名的new String("1")我们不讨论它)。

    此时s3 是Java Heap中的字符串对象的引用,对象内容是”11″,此时常量池中是没有 “11”对象的。

    String s4 = "11";

    分析:声明 s4 的时候常量池中是不存在“11”对象的,执行完后,s4是常量池里“11“对象的引用。

    s3.intern(); 

    分析:此时常量池中“11”对象已经存在了,不会有任何操作,s3仍然是堆中String对象的引用。因此 s3 != s4

    应用实例

    package org.example.a;
     
    import java.util.Random;
     
    public class Demo {
        static final  int MAX = 1000 * 10000;
        static final String[] arr = new String[MAX];
        public static void main(String argv[]) {
            Integer[] DB_DATA = new Integer[10];
            Random random = new Random(10 * 10000);
            for(int i = 0; i < DB_DATA.length; i++){
                DB_DATA[i] = random.nextInt();
            }
     
            long t = System.currentTimeMillis();
            for(int i = 0; i < MAX; i++){
                //arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));
                arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
            }
     
            System.out.println((System.currentTimeMillis() -t) + "ms");
            System.gc();
        }
    }

    上述代码是一个演示代码,其中有两条语句不一样,一条是使用 intern,一条是未使用 intern。

    运行的参数是:-Xmx2g -Xms2g -Xmn1500M

    不用intern

    2160ms

    Java String で intern メソッドを使用する方法

    使用intern

    826ms

    Java String で intern メソッドを使用する方法

    通过上述结果,我们发现不使用 intern 的代码生成了1000w 个字符串,占用了大约640m 空间。 使用了 intern 的代码生成了1345个字符串,占用总空间 133k 左右。其实通过观察程序中只是用到了10个字符串,所以准确计算后应该是正好相差100w 倍。虽然例子有些极端,但确实能准确反应出 intern 使用后产生的巨大空间节省。

    细心的同学会发现使用了 intern 方法后时间上有了一些增长。这是因为程序中每次都是用了 new String 后, 然后又进行 intern 操作的耗时时间,这一点如果在内存空间充足的情况下确实是无法避免的,但我们平时使用时,内存空间肯定不是无限大的,不使用 intern占用空间导致 jvm 垃圾回收的时间是要远远大于这点时间的。 毕竟这里使用了1000w次intern 才多出来1秒钟多的时间。

    以上がJava String で intern メソッドを使用する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。