.NET中的字符串在內(nèi)存中的存儲(chǔ)方式
毫無(wú)疑問(wèn),字符串是我們使用頻率最高的類型。但是如果我問(wèn)大家一個(gè)問(wèn)題:“一個(gè)字符串對(duì)象在內(nèi)存中如何表示的?”,我相信絕大部分人回答不上來(lái)。我們今天就來(lái)討論這個(gè)問(wèn)題。
一、字符串對(duì)象的內(nèi)存布局
從“值類型”和“引用類型”來(lái)劃分,字符串自然屬于引用類型的范疇,所以一個(gè)字符串對(duì)象自然采用引用類型的內(nèi)存布局。我在很多文章中都介紹過(guò)引用類型實(shí)例的內(nèi)存布局(《以純二進(jìn)制的形式在內(nèi)存中繪制一個(gè)對(duì)象》 和《如何將一個(gè)實(shí)例的內(nèi)存二進(jìn)制內(nèi)容讀出來(lái)?》,總的來(lái)說(shuō)整個(gè)內(nèi)存布局分三塊:ObjHeader + TypeHandle + Payload。對(duì)于一般的引用類型實(shí)例來(lái)說(shuō),最后一部分存放的就是該實(shí)例所有字段的值,但是字符串有點(diǎn)特別,它有哪些字段呢?
說(shuō)到這里,可能有人想去反編譯一下String類型,看看它定義了那些字段。其實(shí)沒有必要,字符串這個(gè)類型有點(diǎn)特別,它的Payload部分由兩部分組成:字符串長(zhǎng)度(不是字節(jié)長(zhǎng)度)+編碼的文本,下圖揭示了字符串對(duì)象的內(nèi)存布局。那么具體采用怎樣的編碼方式呢?可能很多人會(huì)認(rèn)為是UTF-8,實(shí)在不然,它采用的是UTF-16,大部分字符通過(guò)兩個(gè)字節(jié)來(lái)表示,少數(shù)的則需要使用四個(gè)字節(jié)。至于字節(jié)序,自然是使用小端字節(jié)序。

二、以二進(jìn)制的方式創(chuàng)建一個(gè)String對(duì)象
在《以純二進(jìn)制的形式在內(nèi)存中繪制一個(gè)對(duì)象》中,我們通過(guò)構(gòu)建一個(gè)字節(jié)數(shù)組來(lái)表示創(chuàng)建的對(duì)象,現(xiàn)在我們依然可以采用類似的方式來(lái)創(chuàng)建一個(gè)真正的String對(duì)象。如下所示的AsString方法用來(lái)將用于承載字符串實(shí)例的字節(jié)數(shù)組轉(zhuǎn)換成一個(gè)String對(duì)象,至于這個(gè)字節(jié)數(shù)組的構(gòu)建,則有CreateString方法完成。CreateString方法根據(jù)指定的字符串內(nèi)容創(chuàng)建一個(gè)String對(duì)象,并利用輸出參數(shù)返回該對(duì)象映射在內(nèi)存中的字節(jié)數(shù)組。
static unsafe string CreateString(string value, out byte[] bytes)
{
var byteCount = Encoding.Unicode.GetByteCount(value);
// ObjHeader + TypeHandle + Length + Encoded string
var size = sizeof(nint) + sizeof(nint) + sizeof(int) + byteCount;
bytes = new byte[size];
// TypeHandle
BinaryPrimitives.WriteInt64LittleEndian(bytes.AsSpan(sizeof(nint)), typeof(string).TypeHandle.Value.ToInt64());
// Length
BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(sizeof(nint) * 2), value.Length);
// Encoded string
Encoding.Unicode.GetBytes(value).CopyTo(bytes, 20);
return AsString(bytes);
}
static unsafe string AsString(byte[] bytes)
{
string s = null!;
Unsafe.Write(Unsafe.AsPointer(ref s), new IntPtr(Unsafe.AsPointer(ref bytes[8])));
return s;
}由于我們需要?jiǎng)?chuàng)建一個(gè)字節(jié)數(shù)組來(lái)表示String對(duì)象,所以必須先計(jì)算出這個(gè)字節(jié)數(shù)組的長(zhǎng)度。我們?cè)谏厦嬲f(shuō)過(guò),String類型采用UTF-16/Unicode編碼方式,所以我們調(diào)用Encoding.Unicode的GetByteCont方法可以計(jì)算出指定的字符串編碼后的字節(jié)數(shù)。在此基礎(chǔ)上我們還需要加上通過(guò)一個(gè)整數(shù)(sizeof(int))表示字符串長(zhǎng)度和TypeHandle(sizeof(nint))和ObjHeader(sizeof(nint),含padding),就是整個(gè)String實(shí)例在內(nèi)存中占用的字節(jié)數(shù)。
接下來(lái)我們填充String類型的TypeHandle的值(String類型方法表地址)、字符串長(zhǎng)度和編碼后的字節(jié),最終將填充好的字節(jié)數(shù)組作為參數(shù)調(diào)用AsString方法,返回的就是我們創(chuàng)建的String對(duì)象。CreateString方法針字符串對(duì)象的創(chuàng)建可以通過(guò)如下的代碼來(lái)驗(yàn)證。
var literal = "foobar"; string s = CreateString(literal, out var bytes); Debug.Assert(literal == s);
對(duì)于上面定義的AsString方法來(lái)說(shuō),作為輸入?yún)?shù)的字節(jié)數(shù)組字符串實(shí)例的內(nèi)存片段,所以該方法針對(duì)同一個(gè)數(shù)組返回的都是同一個(gè)實(shí)例,如下的演示代碼證明了這一點(diǎn)。
var literal = "foobar"; CreateString(literal, out var bytes); var s1 = AsString(bytes); var s2 = AsString(bytes); Debug.Assert(ReferenceEquals(s1,s2));
三、字符串的“可變性”
我們都知道字符串一經(jīng)創(chuàng)建就不會(huì)改變,但是對(duì)于上面創(chuàng)建的字符串來(lái)說(shuō),由于我們都將承載字符串實(shí)例的內(nèi)存字節(jié)都拿捏住了,那還不是想怎么改就怎么改。比如在如下所示的代碼片段中,我們將同一個(gè)字符串的文本從“foo”改成了“bar”。
var literal = "foo";
var s = CreateString(literal, out var bytes);
Debug.Assert(s == "foo");
Encoding.Unicode.GetBytes("bar").CopyTo(bytes, 20);
Debug.Assert(s == "bar");到此這篇關(guān)于.NET的字符串在內(nèi)存中是如何存儲(chǔ)的嗎?的文章就介紹到這了,更多相關(guān).NET字符串內(nèi)存存儲(chǔ)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- .net core EF Core調(diào)用存儲(chǔ)過(guò)程的方式
- C# Ado.net實(shí)現(xiàn)讀取SQLServer數(shù)據(jù)庫(kù)存儲(chǔ)過(guò)程列表及參數(shù)信息示例
- .net core2.0下使用Identity改用dapper存儲(chǔ)數(shù)據(jù)(實(shí)例講解)
- ASP.NET MVC用存儲(chǔ)過(guò)程批量添加修改數(shù)據(jù)操作
- asp.net中調(diào)用存儲(chǔ)過(guò)程的方法
- asp.net中調(diào)用oracle存儲(chǔ)過(guò)程的方法
- VB.NET調(diào)用MySQL存儲(chǔ)過(guò)程并獲得返回值的方法
- .Net下二進(jìn)制形式的文件(圖片)的存儲(chǔ)與讀取詳細(xì)解析
相關(guān)文章
Entity?Framework?Core基于數(shù)據(jù)模型創(chuàng)建數(shù)據(jù)庫(kù)
這篇文章介紹了Entity?Framework?Core基于數(shù)據(jù)模型創(chuàng)建數(shù)據(jù)庫(kù)的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
Ext.Net學(xué)習(xí)筆記之button小結(jié)
Ext.Net學(xué)習(xí)筆記之button小結(jié),需要的朋友可以參考一下2013-02-02
ASP.NET Core3.X 終端中間件轉(zhuǎn)換為端點(diǎn)路由運(yùn)行詳解
這篇文章主要介紹了ASP.NET Core3.X 終端中間件轉(zhuǎn)換為端點(diǎn)路由運(yùn)行,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
ASP.NET編程中經(jīng)常用到的27個(gè)函數(shù)集
asp.net 整理的27個(gè)函數(shù)集,大家可以參考下2008-08-08
asp.net實(shí)現(xiàn)批量刪除實(shí)例
這篇文章主要介紹了asp.net實(shí)現(xiàn)批量刪除的方法,需要的朋友可以參考下2014-08-08
ashx介紹以及ashx文件與aspx文件之間的區(qū)別
這篇文章主要介紹了ashx以及ashx文件與aspx文件之間的區(qū)別。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12
[c#]asp.ent下開發(fā)中Tag的開發(fā)技巧
[c#]asp.ent下開發(fā)中Tag的開發(fā)技巧...2007-05-05

