探究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方法的完整邏輯如下:
- 首先判斷是否是字符串,如果不是,直接返回false。
- 檢查是否具有相同的coder(compact_strings的值間接影響coder的一致性比較),如果不同,直接返回false。
- 在coder相同的情況下,比較內部數據是否一致,決定最終的比較結果。
補充說明:
關于utf16的比較方式,源碼中僅使用了stringlatin1.equals,但如果確定編碼規則相同,底層按字節比較的方法仍然適用。如果想要更詳細了解utf16的比較,可以進一步研究stringlatin1的實現。
對于斷點調試時觀察到的“循環運行”現象,實際上并沒有循環語句。調試過程中,可能會因為編碼比較而導致這種現象。如果發現調試時傳遞的參數是“gbk”,這可能是因為在比較過程中涉及了編碼轉換。這需要進一步查看stringlatin1的源碼以及調用棧來理解具體原因。