導航:首頁 > 源碼編譯 > string字元串源碼解析

string字元串源碼解析

發布時間:2023-03-08 18:12:58

A. Swift進階-String源碼解析

Swift進階-類與結構體
Swift-函數派發
Swift進階-屬性
Swift進階-指針
Swift進階-內存管理
Swift進階-TargetClassMetadata和TargetStructMetadata數據結構源碼分析
Swift進階-Mirror解析
Swift進階-閉包
Swift進階-協議
Swift進階-泛型
Swift進階-String源碼解析
Swift進階-Array源碼解析

創建一個空的字元串發生了什麼?

這里並不能看出String的內存結構。那麼接下來就藉助 Swift源碼 的方式看看String在內存中到底是如何存儲的。

打開swift源碼 -> stdib里的 String.swift

最直觀地可以看到 String 是一個結構體,就是我們所說的值類型;它有一個成員變數 _StringGuts

其中最後有一個創建空字元串初始化方式 self.init(_StringGuts()) :

接下來看看這個 _StringGuts 到底是什麼東西?
同樣找到swift源碼 -> stdib里的 StringGuts.swift

_StringGuts 也是一個結構體,它有一個成員變數是 _StringObject 類型的實例;
並且在最後是通過初始化出一個 _StringObject 類型的實例來初始化 _StringGuts 的。

所以真正swift的 String 的實質就是 _StringObject 。接下來看看 _StringObject 到底是什麼玩意兒?

找到swift源碼 -> stdib里的 StringObject.swift ,可以看到 _StringObject 是一個結構體,再找到空字元串的初始化函數:

ps: 注意這里初始化時的傳參,下面會說到這幾個成員

最終找到字元串最終初始化函數,該函數是對成員的初始化賦值,那麼只要搞懂這幾個成員是代表什麼意思,那就能搞清楚字元串的底層實質了。

_StringObject 存儲著一些成員變數,文章最開始使用x/8g格式化輸出一個空字元串對象empty的時候,那我猜測:輸出的內容應該就是 _StringObject 里的_count、_variant、_discriminator、_flags。

internal var _variant: Variant 是一個枚舉值,默認是immortal 0:

internal var _discriminator: UInt8 在初始化的時候傳遞了一個Nibbles.emptyString( Nibbles 是一個枚舉類型):

0xE000_0000_0000_0000 與文章最上面截圖相對應起來了:

那接下來我們就能測試一下字元串了:

字元a的ASCII編碼是97,97的16進制是61,注意那個2的位元組位的輸出

小於等於15個字元串時,會記錄字元串的位數。
對於小字元串(小於等於15個字元串)來說,是優先直接存到內存當中,無需另外分配內存空間的。(和NSString差不多類似)

接下來看看中文字元

中文字元不是ASCII編碼,一個中文字元占據3個位元組(24位),也是我們上面通過源碼分析得出的使用了 0xA000_0000_0000_0000

所以 _StringObject.Nibbles 是一個識別器,去識別字元串是不是ASCII編碼。

對於大字元串(大於15個字元串)來說,原本的小字元串占據的15個位元組已經不足以存儲字元串了,那就會發生改變:

來看看0x8000000000000000在源碼中出現的定義是一個大原始字元串:

那剩下的 0x000000010000b860 到底是什麼東西呢?它是字元串的內存 相對地址;
那應該偏移多少呢?來看源碼里的註解

意思是0x10000b860需要加上偏移量 nativeBias 即32,32的16進制是0x20:
0x10000b860 + 0x20 = 0x10000b880

在源碼註解里找到大字元串標志位

大字元串前8位就記錄著這些標志位信息,0xd000000000000012就是大字元串前8位,拿到科學計算器里看看標志位:

所以count是0x12,轉換成10進制就是18,正好對應18個字元。

對於 String 來說,它並不支持通過下標的方式獲取字元

只能通過 String.Index 的方式來訪問

對於 Swift 來說, String 是一系列字元的集合,也就意味著 String 中的每一個元素是不等長的。那也就意味著我們在進行內存移動的時候步長是不一樣的,什麼意思?
比如我們有一個 Array 的數組(Int 類型),當我們遍歷數組中的元素的時候,因為每個元素的內存大小是一致的,所以每次的偏移量就是 8 個位元組。

但是對於字元串來說不一樣,比如我要方位 str[1] 那麼我是不是要把我這個欄位遍歷完成之後才能夠確定是的偏移量?
依次內推每一次都要重新遍歷計算偏移量,這個時候無疑增加了很多的內存消耗。這就是為什麼我們不能通過 Int 作為下標來去訪問 String 。

可以很直觀的看到 Index 的定義:

position aka encodedOffset 一個 48 bit 值,用來記錄碼位偏移量;
transcoded offset : 一個 2 bit 的值,用來記錄字元使用的碼位數量;
grapheme cache : 一個 6 bit 的值,用來記錄下一個字元的邊界;
reserved : 7 bit 的預留欄位;
scalar aligned : 一個 1 bit 的值,用來記錄標量是否已經對齊過。

String.Index 的本質就是一個64位的位域信息,這個位域信息展示的就是上面的解釋。

創建 String.Index 實際上就是通過 encodedOffset 或者 transcoded offset , encodedOffset 就是方便我們從內存中通過下標訪問到字元串。

B. 關於java中String類!!!!

ava字元串類(java.lang.String)是Java中使用最多的類,也是最為特殊的一個類,很多時候,我們對它既熟悉又陌生。
一、從根本上認識java.lang.String類和String池
首先,我建議先看看String類的源碼實現,這是從本質上認識String類的根本出發點。從中可以看到:
1、String類是final的,不可被繼承。public final class String。
2、String類是的本質是字元數組char[], 並且其值不可改變。private final char value[];
然後打開String類的API文檔,可以發現:
3、String類對象有個特殊的創建的方式,就是直接指定比如String x = "abc","abc"就表示一個字元串對象。而x是"abc"對象的地址,也叫
做"abc"對象的引用。
4、String對象可以通過「+」串聯。串聯後會生成新的字元串。也可以通過concat()來串聯,這個後面會講述。
6、Java運行時會維護一個String Pool(String池),JavaDoc翻譯很模糊「字元串緩沖區」。String池用來存放運行時中產生的各種字元串,
並且池中的字元串的內容不重復。而一般對象不存在這個緩沖池,並且創建的對象僅僅存在於方法的堆棧區。
5、創建字元串的方式很多,歸納起來有三類:
其一,使用new關鍵字創建字元串,比如String s1 = new String("abc");
其二,直接指定。比如String s2 = "abc";
其三,使用串聯生成新的字元串。比如String s3 = "ab" + "c";
二、String對象的創建
String對象的創建也很講究,關鍵是要明白其原理。
原理1:當使用任何方式來創建一個字元串對象s時,Java運行時(運行中JVM)會拿著這個X在String池中找是否存在內容相同的字元串對象,
如果不存在,則在池中創建一個字元串s,否則,不在池中添加。
原理2:Java中,只要使用new關鍵字來創建對象,則一定會(在堆區或棧區)創建一個新的對象。
原理3:使用直接指定或者使用純字元串串聯來創建String對象,則僅僅會檢查維護String池中的字元串,池中沒有就在池中創建一個,有則罷
了!但絕不會在堆棧區再去創建該String對象。
原理4:使用包含變數的表達式來創建String對象,則不僅會檢查維護String池,而且還會在堆棧區創建一個String對象。
另外,String的intern()方法是一個本地方法,定義為public native String intern(); intern()方法的價值在於讓開發者能將注意力集中到
String池上。當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字元串(該對象由 equals(Object) 方法確定),則返回池
中的字元串。否則,將此 String 對象添加到池中,並且返回此 String 對象的引用。
三、不可變類
不可改變的字元串具有一個很大的優點:編譯器可以把字元串設置為共享。
不可變類String有一個重要的優點-它們不會被共享引用。
是這樣的,JAVA為了提高效率,所以對於String類型進行了特別的處理---為string類型提供了串池
定義一個string類型的變數有兩種方式:
string name= "tom ";
string name =new string( "tom ")
使用第一種方式的時候,就使用了串池,
使用第二中方式的時候,就是一種普通的聲明對象的方式
如果你使用了第一種方式,那麼當你在聲明一個內容也是 "tom "的string時,它將使用串池裡原來的那個內存,而不會重新分配內存,也就是說,string saname= "tom ",將會指向同一塊內存
另外關於string類型是不可改變的問題:
string類型是不可改變的,也就是說,當你想改變一個string對象的時候,比如name= "madding "
那麼虛擬機不會改變原來的對象,而是生成一個新的string對象,然後讓name去指向它,如果原來的那個 "tom "沒有任何對象去引用它,虛擬機的垃圾回收機制將接收它。
據說這樣可以提高效率!!!

C. String類源碼筆記(一):成員變數和構造器

String類表示字元串,所有類似"abc"形式的字元串(或魔法字元串)都被看作是這個類的實例。String是不可變的,當一個字元串在常量池中被創建時,他的值就不會被改變。

所在路徑:javalangString.java

為了保證String類是一個不可變類,String類的成員變數多為私有和不可變的。

其中serialPersistentFields在序列化時使用:

JDK8的String類一共有16個構造器,其中兩個是@Deprecated,一個是私有構造器,剩下的13個是可以調用的。

無參構造器直接將""的value賦值給當前類的value。""的value是一個空的char[],其length為0。調用使用了無參構造器的String對象的isEmpty()方法會得到true,調用length()方法會得到0,判斷其==null會得到false。

入參為String對象時,構造器會對其進行直接取值。

入參為字元串數組時,構造器調用的是Arrays.Of()方法。

其中Arrays.Of()方法是為了將入參的字元串序列深拷貝到this.valuie中,他的源碼為:

其中System.array()方法的源碼為:

這個方法支持直接傳入想要生成的String的母串,通過偏移量和有效長度找出需要賦值給this.value的部分,然後調用Arrays.OfRange()方法進行深拷貝。

當需要將一個Unicode編碼序列轉換為String時,可以使用以下構造器:

當需要將一個bytes[]轉換為String時,可以使用以下構造器:

此外,還有一些將上述構造器進一步封裝的構造器,其本質都是簡化入參。另外,String類的構造器同樣支持傳入StringBuffer和StringBuilder。如果傳入的是StringBuffer,構造器會為其加鎖。如果傳入的是StringBuilder則不會加鎖。

事實上,StringBuffer和StringBuilder的toString()方法調用的也是String類的構造器,他們最終的底層實現都是Arrays.Of()。

最後,String類還提供了一個保護類型的構造方法。該方法相比入參為char[]的構造器多了一個share參數,這個參數並沒有實際作用,只是用來和其他構造器進行區分。當String類內部調用該構造器時:

該構造器不能對外暴露的原因是需要保持String類的不可變性。

閱讀全文

與string字元串源碼解析相關的資料

熱點內容
怎麼顯示android的APP 瀏覽:121
c編譯器怎麼刪除空格 瀏覽:695
php自動釋放內存 瀏覽:219
golang編譯庫 瀏覽:794
oracle數據字元串加密 瀏覽:603
研究生去上海當程序員 瀏覽:90
u8電腦伺服器連接失敗怎麼解決 瀏覽:569
bat腳本創建日期命名文件夾 瀏覽:104
將圖片轉換為pdf格式 瀏覽:980
java中形參 瀏覽:83
枚舉類型編譯器 瀏覽:519
oraclejava包 瀏覽:568
手機定位手機怎麼定位安卓 瀏覽:523
在哪個app買歐萊雅最便宜 瀏覽:495
程序員吃零食好嗎 瀏覽:261
php工程師主要做什麼 瀏覽:356
tvp保存到哪個文件夾 瀏覽:197
怎麼把空調裡面的壓縮機拆卸掉 瀏覽:943
linux4k對齊 瀏覽:968
單片機與開關電源 瀏覽:276