Java String類equals方法的工作機制是什么?

探究Java string類equals方法的工作機制

在學習java String類的equals方法時,我們經常會遇到一些困惑,尤其是當深入到源碼時,會發現一些不易理解的現象。今天我們將深入探討jdk18環境下string類的equals方法的內部邏輯,揭示其中的奧秘。

問題描述

在使用斷點調試時,觀察到了以下現象:

問題1:

return (anobject instanceof string astring)        && (!compact_strings || this.coder == astring.coder)        && stringlatin1.equals(value, astring.value);

這個邏輯似乎在調試過程中循環運行。而且,有時即使字符相同,如”a”.equals(“a”)時,value與astring.value的數組長度也會不同。

問題2:

立即學習Java免費學習筆記(深入)”;

  • 對于”a”.equals(new string(“a”));,調試時發現傳遞的參數如圖所示:
    [參數圖像]
  • 對于”a”.equals(“a”);,調試時發現傳遞的參數如圖所示:
    [參數圖像]

這兩個例子中,參數在傳遞到equals方法后,似乎并不總是如預期的”a”。

解答

讓我們從string類的源碼開始,逐步理解這些現象。首先,關于compact_strings的定義和說明:

    /**      * if string compaction is disabled, the bytes in {@code value} are      * always encoded in utf16.      *      * for methods with several possible implementation paths, when string      * compaction is disabled, only one code path is taken.      *      * the instance field value is generally opaque to optimizing jit      * compilers. therefore, in performance-sensitive place, an explicit      * check of the static boolean {@code compact_strings} is done first      * before checking the {@code coder} field since the static boolean      * {@code compact_strings} would be constant folded away by an      * optimizing jit compiler. the idioms for these cases are as follows.      *      * for code such as:      *      *    if (coder == latin1) { ... }      *      * can be written more optimally as      *      *    if (coder() == latin1) { ... }      *      * or:      *      *    if (compact_strings && coder == latin1) { ... }      *      * an optimizing jit compiler can fold the above conditional as:      *      *    compact_strings == true  => if (coder == latin1) { ... }      *    compact_strings == false => if (false)           { ... }      *      * @implnote      * the actual value for this field is injected by jvm. the static      * initialization block is used to set the value here to communicate      * that this static final field is not statically foldable, and to      * avoid any possible circular dependency during vm initialization.      */     static final boolean compact_strings;      static {         compact_strings = true;     }

從這段說明可以看出,如果compact_strings為false,value將始終使用utf16編碼。這個設置和coder字段密切相關。

接下來,我們看看coder的定義:

    /**      * the identifier of the encoding used to encode the bytes in      * {@code value}. the supported values in this implementation are      *      * latin1      * utf16      *      * @implnote this field is trusted by the vm, and is a subject to      * constant folding if string instance is constant. overwriting this      * field after construction will cause problems.      */     private final byte coder;

coder有兩個可能的值,分別表示latin1和utf16。我們可以找到與coder同名的方法:

    byte coder() {         return compact_strings ? coder : utf16;     }

因此,條件(!compact_strings || this.coder == astring.coder)的意義就很明確了:

如果compact_strings == false,就使用utf16編碼,繼續檢查下一個條件。如果條件不成立,就檢查coder是否相同,如果不相同,直接返回false。我們可以用手寫代碼來理解這個邏輯:

boolean flag = false; if (!compact_strings) {     flag = true; // 根據 compact_strings 的說明,這種情況下使用 utf16,忽略 coder 值 } else if (this.coder == astring.coder) {     flag = true; // 說明 coder 一致 }

然后,stringlatin1.equals(value, astring.value)條件中,內部數據value使用latin1編碼規則進行比較。value的定義如下:

    /**      * The value is used for character storage.      *      * @implNote This field is trusted by the VM, and is a subject to      * constant folding if String instance is constant. Overwriting this      * field after construction will cause problems.      *      * Additionally, it is marked with {@link Stable} to trust the contents      * of the array. No other facility in JDK provides this functionality (yet).      * {@link Stable} is safe here, because value is never null.      */     @Stable     private final byte[] value;

因此,equals方法的完整邏輯如下:

  1. 首先判斷是否是字符串,如果不是,直接返回false。
  2. 檢查是否具有相同的coder(compact_strings的值間接影響coder的一致性比較),如果不同,直接返回false。
  3. 在coder相同的情況下,比較內部數據是否一致,決定最終的比較結果。

補充說明:

關于utf16的比較方式,源碼中僅使用了stringlatin1.equals,但如果確定編碼規則相同,底層按字節比較的方法仍然適用。如果想要更詳細了解utf16的比較,可以進一步研究stringlatin1的實現。

對于斷點調試時觀察到的“循環運行”現象,實際上并沒有循環語句。調試過程中,可能會因為編碼比較而導致這種現象。如果發現調試時傳遞的參數是“gbk”,這可能是因為在比較過程中涉及了編碼轉換。這需要進一步查看stringlatin1的源碼以及調用來理解具體原因。

? 版權聲明
THE END
喜歡就支持一下吧
點贊15 分享