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類的不可變性。