NSString *strS = @"123456789";
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithFormat:@"1"];
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithUTF8String:"1234567"];
strS = [NSString stringWithUTF8String:"abcdabc"];//7
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithUTF8String:"abcdabcd"];//8
strS = [NSString stringWithUTF8String:"teeeeeeet"];//9
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithUTF8String:"eeeeeeeeee"];//10
strS = [NSString stringWithUTF8String:"eeeeeeeeeee"];//11
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithUTF8String:"eeeeeeeeeeee"];//12
NSLog(@"%@ : %p",strS.class,strS);
上面這段代碼輸出為:
__NSCFConstantString : 0x10c843820
NSTaggedPointerString : 0xa000000000000311
NSTaggedPointerString : 0xa636261646362617
NSTaggedPointerString : 0xa040000000000049
NSTaggedPointerString : 0xa00000000000000b
__NSCFString : 0x6000000315c0
我們知道oc存儲主要分成數(shù)據(jù)區(qū)、堆區(qū)和棧區(qū),
-
__NSCFConstantString
顯然是常量字符串,地址0x10c843820
自然就是存儲在數(shù)據(jù)區(qū)。 -
__NSCFString
表示為oc對象,NSString
就是封裝的CFString
,0x6000000315c0
地址顯示這個(gè)字符串對象存儲在堆中。 -
NSTaggedPointerString
這個(gè)類表示這是字符串的一種指針Tagged Pointer,0xa636261646362617
這個(gè)地址為什么如此與眾不同呢,接下來我們就簡單介紹這鐘字符串的存儲指針。
在蘋果推出了 采用64位架構(gòu)的A7雙核處理器 iphone 5s的時(shí)候,為了節(jié)省內(nèi)存和提高執(zhí)行效率,蘋果提出了Tagged Pointer的概念。先看看原有的對象為什么會(huì)浪費(fèi)內(nèi)存。假設(shè)要存儲一個(gè) NSNumber 對象,其值是一個(gè)整數(shù)。正常情況下,如果這個(gè)整數(shù)只是一個(gè) NSInteger 的普通變量,那么它所占用的內(nèi)存是與CPU的位數(shù)有關(guān),在32位CPU下占4個(gè)字節(jié),在64位CPU下是占8個(gè)字節(jié)的。而指針類型的大小通常也是與CPU位數(shù)相關(guān),一個(gè)指針?biāo)加玫膬?nèi)存在32位CPU下為4個(gè)字節(jié),在64位CPU下也是8個(gè)字節(jié)。所以一個(gè)普通的iOS程序,如果沒有Tagged Pointer對象,從32位機(jī)器遷移到64位機(jī)器中后,雖然邏輯沒有任何變化,但這種NSNumber、NSDate一類的對象所占用的內(nèi)存會(huì)翻倍。
感興趣的朋友可以去嘗試下,這里只列出了NSString采用這種技術(shù)的結(jié)果。
對于以前的@""
符號創(chuàng)建的字符串還是常量字符串,這個(gè)沒有改變,但是采用stringWithFormat
等NSString方法的創(chuàng)建字符串對象則有了區(qū)別。
在蘋果的64位OC實(shí)現(xiàn)中,若對象指針的二進(jìn)制第一位是1,則該指針為Tagged Pointer。
例如0xa000000000000311
其中a的2進(jìn)制為1010
,第一位1
表示這是Tagged Pointer,010
表示這是一個(gè)NSTaggedPointerString類;這個(gè)地址最后一位表示字符串的數(shù)目,這里是0001
表示有1位字符串;其中真正用來存儲的位數(shù)只有中間的14位16進(jìn)制。這個(gè)地址本身其實(shí)就存儲了字符串的值,可以說是存儲在&strS內(nèi)存中值,只是偽裝成了地址,它不需要存儲在數(shù)據(jù)區(qū),也不需要申請堆空間。
NSTaggedPointerString的存儲有三種編碼方式:ASCII碼,六位編碼,五位編碼。
ASCII碼
我們發(fā)現(xiàn)NSTaggedPointerString存儲內(nèi)容除去第一位和最后一位,其實(shí)只有中間的14位16進(jìn)制字符,再看ascll碼由8位二進(jìn)制組成,所以這里(14*4) / 8=7,用8位的ascll碼的話最多可以存儲7個(gè)字符。字符串?dāng)?shù)目0~7之間
[NSString stringWithFormat:@"1"]
輸出的地址 0xa000000000000311
,其中31
的2進(jìn)制是0011 0001
,在ascll碼表里查找發(fā)現(xiàn)正是對應(yīng)著“1”;
[NSString stringWithUTF8String:"abcdabc"] 這里輸出的地址是0xa636261646362617 可以發(fā)現(xiàn)在使用ascll編碼時(shí),字符串對應(yīng)的編碼是從右向左存儲的
六位編碼:
NSTaggedPointerString 采用六位二進(jìn)制編碼,(14*4)/6=9.333…,可以看出最多存儲9位字符。字符數(shù)目8~9
五位編碼:
采用五位二進(jìn)制編碼,(14*4)/5 = 11.2,可以看出這種編碼最多存儲11位字符。字符數(shù)目在10~11
NSTaggedPointerString 存儲編碼中的六位和五位編碼都是根據(jù)通常代碼中字母使用頻率來排序的,但并不是一成不變的,apple會(huì)持續(xù)更新并統(tǒng)計(jì)字母使用頻率,系統(tǒng)每次升級都可能不一樣,當(dāng)前第一位是字母e,之后是i,l,o,t…;這兩種編碼是從左向右的;根據(jù)編碼位數(shù)我們顯然也能推測出并不是所有字符都可以進(jìn)行ascll或者六位五位編碼的,當(dāng)出現(xiàn)這樣不能編碼的時(shí)候,系統(tǒng)也就不會(huì)使用NSTaggedPointerString類。