日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

[iOS开发]iOS中的Hash

發(fā)布時(shí)間:2023/12/8 编程问答 62 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [iOS开发]iOS中的Hash 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 前言
    • 關(guān)聯(lián)對(duì)象的底層原理
    • weak的實(shí)現(xiàn)原理
    • KVO的實(shí)現(xiàn)原理
    • iOS App簽名的原理
    • 對(duì)象引用計(jì)數(shù)存儲(chǔ)的位置
    • Runloop與線程的存儲(chǔ)關(guān)系
    • NSDictionary的原理
  • 哈希表
    • 哈希表定義
    • 哈希表優(yōu)缺點(diǎn)
    • 哈希查找步驟
    • 哈希表的存儲(chǔ)過程
    • 哈希表的實(shí)現(xiàn)
    • 負(fù)載因子 = 總鍵值對(duì)數(shù)/數(shù)組的個(gè)數(shù)
    • 哈希沖突的解決方法
      • 開散列
      • 閉散列
      • 再哈希法
      • 建立公共溢出區(qū)
      • 開閉散列二者的比較
        • 拉鏈法的優(yōu)點(diǎn):
        • 拉鏈法的缺點(diǎn):
        • 線性探測(cè)法的缺點(diǎn)
  • NSDictionary

前言

天天聽安卓的同學(xué)整Hash,猛的發(fā)現(xiàn)iOS也有很多底層原理也是Hash來實(shí)現(xiàn)的,好好學(xué)一下。

iOS中也用到了很多Hash表。

關(guān)聯(lián)對(duì)象的底層原理

[iOS開發(fā)]Category、Extension和關(guān)聯(lián)對(duì)象

關(guān)聯(lián)對(duì)象采用的是HashMap嵌套HashMap的結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)的,簡(jiǎn)單來說就是根據(jù)對(duì)象從第一個(gè)HashMap中取出存儲(chǔ)所有關(guān)聯(lián)對(duì)象的第二個(gè)HashMap,然后根據(jù)屬性名從第二個(gè)HashMap中取出屬性對(duì)應(yīng)的值和策略。

問題:為什么關(guān)聯(lián)對(duì)象沒有weak屬性?

weak的實(shí)現(xiàn)原理

weak底層原理

weak采用的是一個(gè)全局的HashMap嵌套數(shù)組的結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)的。銷毀對(duì)象(weak指針指向的對(duì)象)的時(shí)候,根據(jù)對(duì)象從HashMap中找到存放所有指向該對(duì)象的weak指針的數(shù)組,然后將數(shù)組中的所有元素(weak指針)都置為nil

weak最大的特點(diǎn)就是在對(duì)象銷毀的時(shí)候,自動(dòng)置nil,減少訪問野指針的風(fēng)險(xiǎn),這也是設(shè)計(jì)weak的初衷。weak指針置nil的基本步驟:

  • 對(duì)象dealloc的時(shí)候,從全局的HashMap中,根據(jù)一個(gè)唯一代碼對(duì)象的值作為key,找到存儲(chǔ)所有指向該對(duì)象的weak指針的數(shù)組
  • 將數(shù)組中的所有元素都置nil

蘋果對(duì)于weak的實(shí)現(xiàn)其實(shí)類似于通知的實(shí)現(xiàn),指明誰(weak指針)要監(jiān)聽誰(賦值對(duì)象)什么事件(dealloc操作)執(zhí)行什么操作(置nil)

KVO的實(shí)現(xiàn)原理

GNUstep KVC/KVO探索(二):KVO的內(nèi)部實(shí)現(xiàn)

iOS App簽名的原理

一致性Hash算法 + 非對(duì)稱加密算法

iOS App 簽名的原理

對(duì)象引用計(jì)數(shù)存儲(chǔ)的位置

if 對(duì)象支持TaggedPointer {return 直接將對(duì)象的指針值作為引用計(jì)數(shù)返回 } else if 設(shè)備是64位環(huán)境 && Objective-C2.0 {return 對(duì)象isa指針的一部分空間(bits_extra_rc) } else {return hash表 }

Runloop與線程的存儲(chǔ)關(guān)系

線程和Runloop之間是一一對(duì)應(yīng)的關(guān)系,其關(guān)系是保存在一個(gè)全局的Dictionary里。

線程在剛創(chuàng)建是沒有RunLoop,如果我們不主動(dòng)獲取,那他就一直都不會(huì)有。RunLoop的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop的銷毀時(shí)發(fā)生在線程結(jié)束時(shí)。我們只能在一個(gè)線程內(nèi)部獲取其RunLoop。

NSDictionary的原理

學(xué)完Hash之后我們來學(xué)習(xí)具體的實(shí)現(xiàn)

哈希表

前面說了那么多的底層原理都與Hash表有關(guān),我們來詳細(xì)學(xué)一下哈希表

哈希表定義

哈希表(hash tabl,也叫散列表),是根據(jù)鍵(key)直接訪問在內(nèi)存存儲(chǔ)位置的數(shù)據(jù)結(jié)構(gòu)。哈希表本質(zhì)是一個(gè)數(shù)組,數(shù)組中的每一個(gè)元素成為一個(gè)箱子,箱子中存放的是鍵值對(duì)。根據(jù)下標(biāo)index從數(shù)組中取value。關(guān)鍵是如何獲取index,這就需要一個(gè)固定的函數(shù)(哈希函數(shù)),將key轉(zhuǎn)換成index。不論哈希函數(shù)設(shè)計(jì)的如何完美,都可能出現(xiàn)不同的key經(jīng)過hash處理后得到相同的hash值,這時(shí)候我們就需要處理哈希沖突。

哈希表優(yōu)缺點(diǎn)

優(yōu)點(diǎn):把數(shù)據(jù)的存儲(chǔ)和查找消耗的時(shí)間大大降低,幾乎可以看成是常數(shù)時(shí)間;而代價(jià)僅僅是消耗比較多的內(nèi)存。然而在當(dāng)前可利用內(nèi)存越來越多的情況下,用空間換時(shí)間的做法是值得的。另外,編碼比較容易也是它的特點(diǎn)之一。

缺點(diǎn):哈希表通常是基于數(shù)組,數(shù)組創(chuàng)建后難于擴(kuò)展。也沒有一種簡(jiǎn)便的方法可以以任何一種順序來遍歷哈希表。

所以,如果不需要有序遍歷數(shù)據(jù),并且可以提前預(yù)測(cè)數(shù)據(jù)量的大小,那么哈希表在速度和易用性方面是無與倫比的。

哈希查找步驟

  • 使用哈希函數(shù)將被查找的鍵轉(zhuǎn)換為數(shù)組的索引,理想情況下(hash函數(shù)設(shè)計(jì)合理)不同的鍵映射的數(shù)組下標(biāo)也不同,所有的查找時(shí)間復(fù)雜度為O(1)。但是實(shí)際情況下不是這樣的,所以哈希查找的第二部就是處理哈希沖突。
  • 處理哈希沖突有很多方法,比如拉鏈法、線性探測(cè)法…
  • 哈希表的存儲(chǔ)過程

  • 使用hash函數(shù)根據(jù)key得到哈希值h
  • 如果箱子的個(gè)數(shù)為n,那么值應(yīng)該存放在底(h%n)個(gè)箱子中。h%n的值范圍為[0,n-1]。
  • 如果該箱子非空(已經(jīng)存放了一個(gè)值)即不同的key得到了相同的h產(chǎn)生了哈希沖突,此時(shí)需要使用拉鏈法或者開放定址線性探測(cè)法解決沖突
  • 哈希表的實(shí)現(xiàn)

    哈希表的底層實(shí)際上是基于數(shù)組來存儲(chǔ)的,當(dāng)插入鍵值對(duì)時(shí),并不是直接插入該數(shù)組中,而是通過對(duì)鍵進(jìn)行Hash運(yùn)算得到Hash值,然后和數(shù)組容量取模,得到在數(shù)組中的位置后再插入。取值時(shí),先對(duì)指定的鍵求Hash值,再和容量取模得到底層數(shù)組中對(duì)應(yīng)的位置,如果指定的鍵值與存貯的鍵相匹配,則返回該鍵值對(duì),如果不匹配,則表示哈希表中沒有對(duì)應(yīng)的鍵值對(duì)。這樣做的好處是在查找、插入、刪除等操作可以做到 O ( 1 ) O(1) O(1),最壞的情況是 O ( n ) O(n) O(n),當(dāng)然這種是最極端的情況,極少遇到。

    負(fù)載因子 = 總鍵值對(duì)數(shù)/數(shù)組的個(gè)數(shù)

    負(fù)載因子是哈希表的一個(gè)重要屬性,用來衡量哈希表的空/滿成都,一定程度也可以體現(xiàn)查詢的效率。負(fù)載因子越大,意味著哈希表越滿,越容易導(dǎo)致沖突,性能也就越低。所以當(dāng)負(fù)載因子大于某個(gè)常數(shù)(一般是0.75)時(shí),哈希表將自動(dòng)擴(kuò)容。哈希表擴(kuò)容是,一般會(huì)創(chuàng)建兩倍于原來的數(shù)組長(zhǎng)度。這個(gè)過程被稱為重哈希(rehash)。

    哈希表擴(kuò)容在數(shù)組比較多的時(shí)候需要重新哈希并移動(dòng)數(shù)據(jù),性能影響較大。

    哈希表擴(kuò)容雖然能夠使負(fù)載因子降低,但并不總能有效提高哈希表的查詢性能。比如哈希函數(shù)設(shè)計(jì)的不合理,導(dǎo)致所有的key計(jì)算出的哈希值都相同,那么即使擴(kuò)容他們的位置還是在同一條鏈表上,變成了線性表,性能也極低,查詢時(shí)候的時(shí)間復(fù)雜度就變成了O(n)。

    哈希沖突的解決方法

    大類分為四種 開散列、閉散列、再哈希法、建立公共溢出區(qū)

    開散列

    開散列也叫鏈地址法,也叫拉鏈法、哈希桶。

    簡(jiǎn)單來說就是 數(shù)組 + 鏈表。將鍵通過hash函數(shù)映射為大小為M的數(shù)組的下標(biāo)索引,數(shù)組的每一個(gè)元素指向一個(gè)鏈表,鏈表中的每一個(gè)節(jié)點(diǎn)都存儲(chǔ)著hash出來的索引值為結(jié)點(diǎn)下標(biāo)的鍵值對(duì)。

    JAVA 8解決哈希沖突采用的就是拉鏈法。在處理哈希函數(shù)設(shè)計(jì)不合理導(dǎo)致鏈表很長(zhǎng)時(shí)(鏈表長(zhǎng)度超過8切換為紅黑樹,小于6重新退化為鏈表)。將鏈表切換為紅黑樹能夠保證插入和查找的效率,缺點(diǎn)是當(dāng)哈希表比較大時(shí),哈希表擴(kuò)容會(huì)導(dǎo)致瞬時(shí)效率降低。

    Redis解決哈希沖突采用的也是拉鏈法。通過增量式擴(kuò)容解決了java 8中的瞬時(shí)擴(kuò)容導(dǎo)致的瞬時(shí)效率降低的缺點(diǎn),同時(shí)拉鏈法的實(shí)現(xiàn)方式(新插入的鍵值對(duì)放在鏈表頭部)帶來了兩個(gè)好處:
    一、 頭插法可以節(jié)省插入耗時(shí)。如果插到尾部,則需要時(shí)間復(fù)雜度為O(n)的操作找到鏈表的尾部,或者需要額外的內(nèi)存地址來保存尾部鏈表的位置。
    二、頭插法可以節(jié)省查找耗時(shí)。最新插入的數(shù)據(jù)往往可能頻繁的被查詢。

    閉散列

    閉散列也叫開放地址法、線性探測(cè)法。

    當(dāng)發(fā)生哈希沖突時(shí),且哈希表未滿,可以通過探測(cè)的方法吧key存放在沖突位置的下一個(gè)位置中。

    方法一:線性探測(cè)
    從發(fā)生沖突的位置開始,一次向后探測(cè),直到尋找到下一個(gè)空位置為止。當(dāng)然這種方法比較簡(jiǎn)單,也有很大的缺陷,就是容易造成數(shù)據(jù)的堆積,使得關(guān)鍵字需要多次比較,這樣以來就導(dǎo)致搜索效率低

    方法二:二次探測(cè)

    方法三:偽隨機(jī)探測(cè)
    建立一個(gè)偽隨機(jī)數(shù)發(fā)生器(如 i = (i + p) % m),生成一個(gè)偽隨機(jī)序列,并給定一個(gè)隨機(jī)數(shù)做起點(diǎn),每次加上這個(gè)偽隨機(jī)數(shù),++就可以了。

    再哈希法

    再哈希法其實(shí)很簡(jiǎn)單,就是再使用哈希函數(shù)去散列一個(gè)輸入的時(shí)候,輸出是同一個(gè)位置就再次哈希,直至不發(fā)生沖突位置

    缺點(diǎn):每次沖突都要重新哈希,計(jì)算時(shí)間增加。

    建立公共溢出區(qū)

    這種方法的基本思想是:將哈希表分為基本表和溢出表兩部分,凡是和基本表發(fā)生沖突的元素,一律填入溢出表。

    開閉散列二者的比較

    拉鏈法的優(yōu)點(diǎn):

    與線性探測(cè)法相比,拉鏈法有如下幾個(gè)優(yōu)點(diǎn):

    • 處理沖突簡(jiǎn)單,且無堆積現(xiàn)象,即非同義詞絕不會(huì)發(fā)生沖突,因此平均查找長(zhǎng)度較短;
    • 由于拉鏈法中各鏈表上的結(jié)點(diǎn)空間是動(dòng)態(tài)申請(qǐng)的,所以更適合于造表錢無法確定表長(zhǎng)的情況
    • 線性探測(cè)法為減少?zèng)_突,要求裝填因子較小,故當(dāng)結(jié)點(diǎn)規(guī)模較大時(shí)會(huì)浪費(fèi)很多空間。二拉鏈法中可取a>=1,且結(jié)點(diǎn)較大時(shí),拉鏈法中增加的指針域可忽略不計(jì),因此節(jié)省空間
    • 在用拉鏈法構(gòu)造的散列表中,刪除結(jié)點(diǎn)的操作易于實(shí)現(xiàn)。只要簡(jiǎn)單地刪去鏈表上相應(yīng)的結(jié)點(diǎn)即可。而對(duì)開放定址線性探測(cè)發(fā)構(gòu)造的散列表,刪除結(jié)點(diǎn)不能簡(jiǎn)單地將被刪結(jié) 點(diǎn)的空間置為空,否則將截?cái)嘣谒筇钊松⒘斜淼耐x詞結(jié)點(diǎn)的查找路徑。這是因?yàn)楦鞣N開放定址線性探測(cè)發(fā)中,空地址單元(即開放地址)都是查找失敗的條件。因此在用開放定址線性探測(cè)發(fā)處理沖突的散列表上執(zhí)行刪除操作,只能在被刪結(jié)點(diǎn)上做刪除標(biāo)記,而不能真正刪除結(jié)點(diǎn)。

    拉鏈法的缺點(diǎn):

    指針需要額外的空間,故當(dāng)結(jié)點(diǎn)規(guī)模較小時(shí),開放定址線性探測(cè)法較為節(jié)省空間,而若將節(jié)省的指針空間用來擴(kuò)大散列表的規(guī)模,可使裝填因子變小,這又減少了開放定址線性探測(cè)法中的沖突,從而提高平均查找速度。

    線性探測(cè)法的缺點(diǎn)

  • 容易產(chǎn)生堆積問題
  • 不適于大規(guī)模的數(shù)據(jù)存儲(chǔ)
  • 散列函數(shù)的設(shè)計(jì)對(duì)沖突會(huì)有很大的影響
  • 插入時(shí)可能會(huì)出現(xiàn)多次沖突的現(xiàn)象,刪除的元素是多個(gè)沖突元素中的一個(gè),需要對(duì)后面的元素作處理,實(shí)現(xiàn)較復(fù)雜
  • 結(jié)點(diǎn)規(guī)模很大時(shí)會(huì)浪費(fèi)很多空間
  • NSDictionary

    總結(jié)

    以上是生活随笔為你收集整理的[iOS开发]iOS中的Hash的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。