網站首頁 編程語言 正文
今天寫bug的時候幫同事解決了一個有趣的問題,可能很多人都會答錯。分享給大家。
問題
請看以下例子,并回答問題。
var s1 = "12"; var s2 = "12"; //序列化方式1 var o3 = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(Newtonsoft.Json.JsonConvert.SerializeObject(s1)); //序列化方式2 MemoryStream stream = new MemoryStream(); System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); bf.Serialize(stream, s1); stream.Seek(0, SeekOrigin.Begin); var o4 = bf.Deserialize(stream); //====分割線=================================================== var e1 = object.ReferenceEquals(s1, s2); var e2 = o4 == s1; var e3 = s1.Equals(o4); var e4 = o3 == o4; Console.ReadKey();
請回答分割線后e1, e2, e3, e4 值為true還是false。
人人都知道在.Net中字符串是享元模式的經典范例。字符串具有不變性。(至少在托管層,事實上可以在非托管層修改字符串的值),但你真的能回答對上面的問題么?
答案
e1 = true;
e2 = false;
e3 = true;
e4 = false;
要了解這個問題首先可以看下字符串在內存中的布局。
如何在visual studio中查看變量的內存布局
在VS中可以非常方便的查看托管或非托管變量的內存值。方法如下。
- 依次在調試模式下打開 調試 -> 窗口 -> 內存 -> 內存1(1~4均可) 打開內存對話框。
- 在地址欄中輸入變量名即可。
字符串變量在內存中的布局
在.Net中字符串是以UTF-16格式在內存中保存的。在本例中s1的內存如下。
00 00 00 00 00 00 00 00 98 d6 fc e5 fb 7f 00 00 02 00 00 00 31 00 32 00
這里可能與你拿到的結果不一樣。你可能并沒有前8位0x00
,因為我把對象頭帶上了。下面依次解釋各段含義。
-
00 00 00 00 00 00 00 00
?最開始的8比特是對象頭。其中,在64位下,高4位為0,低4位為一個不為0的數(這里由于并沒有執行lock或Gethashcode操作,所以這里為0,感興趣的自行實驗.) -
98 d6 fc e5 fb 7f 00 00
對象的MethodTable
,根據類型而不同,對象的引用指向的位置。 -
02 00 00 00
?字符串長度,這里是2。 -
31 00 32 00
?字符串數組* char
,注意都是小端模式。
拿以上s1 s2 o3 o4
分別實驗可以發現他們的內存一模一樣,其中s1 s2
直接就是同一塊內存地址,但剩下的內存地址都不一樣。
比較與解答
e1 = true;
?通過內存看合情合理,畢竟都同一塊內存了。-
e2 = false;
?這里如果用的VS的版本比較高的話,也能看出來。因為這里VS會提示:可能非有意的引用比較。
既然是引用比較,內存地址都不一樣,肯定是false了。但是如果vs版本不高的話則迷惑性就較大了,其實這里做的是
ReferenceEquals
的比較。 e3 = true;
?這里問題出在.Net代碼里。字符串類型Equals
方法被重載了。
// Determines whether two strings match. public override bool Equals([NotNullWhen(true)] object? obj) { if (object.ReferenceEquals(this, obj)) return true; if (!(obj is string str)) return false; if (this.Length != str.Length) return false; return EqualsHelper(this, str); }
EqualsHelper
方法最終則調用如下。(在.Net 6下)
// Optimized byte-based SequenceEquals. The "length" parameter for this one is declared a nuint rather than int as we also use it for types other than byte // where the length can exceed 2Gb once scaled by sizeof(T). public static unsafe bool SequenceEqual(ref byte first, ref byte second, nuint length)
由于實現過于復雜(.Net framework 4.5.2下則較簡單,直接按長度比較char,有興趣的自行查閱),這里就不貼具體實現了。我們很容易看出這里比較的目的是比較兩段內存是否相等,顯然為true
。
-
e4 = false;
這里是為了比較不同序列化方式的影響,和e2
類似,結果顯然是false
。
結論
雖然.Net中字符串是享元模式創建的,但并不能保證同一字符串在內存里只有一份。比如序列化情況等例外情況。如果讀者知道其他情況也可以告訴我,提前說聲感謝
原文鏈接:https://www.cnblogs.com/zhangchen-trunk/p/16172401.html
相關推薦
- 2022-05-05 Entity?Framework使用LINQ操作實體_實用技巧
- 2022-04-19 Windows中Python上傳文件到Liunx下的fastdfs
- 2022-10-12 浮動AppBar中的textField焦點回滾問題解決_Android
- 2022-12-07 進程狀態ps?-ef中的e、f含義講解_linux shell
- 2022-04-15 python實現請求數據包簽名_python
- 2022-11-15 python重用父類功能的兩種方式實例詳解_python
- 2022-05-11 Python實現圖書借閱管理系統_python
- 2022-04-27 python如何將多個模型的ROC曲線繪制在一張圖(含圖例)_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支