哈希表(HashTable)

本質(zhì)

哈希表的底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組剩愧,數(shù)組中每一個(gè)元素存放的是鍵值對(duì)篮灼。

structure of hashtable
核心原理

f(key) = index

將key傳入,通過(guò)某個(gè)算法f幽纷,計(jì)算出索引缘滥,如果索引沖突,再通過(guò)某個(gè)辦法對(duì)索引進(jìn)行+1或-1再算一遍厦画,直到算到不沖突為止。

負(fù)載因子 = 總鍵值對(duì)數(shù) / 哈希表總長(zhǎng)度

負(fù)載因子用來(lái)衡量用來(lái)衡量哈希表的空滿(mǎn)程度,一般鞭衩,當(dāng)負(fù)載因子為0.75~1的值就會(huì)進(jìn)行自動(dòng)擴(kuò)容学搜。

存取過(guò)程(以iOS方法緩存列表底層實(shí)現(xiàn)為例)

example:Class內(nèi)部結(jié)構(gòu)中有個(gè)方法緩存列表,當(dāng)objc_msgSend進(jìn)行到搜索方法緩存列表的步驟時(shí)论衍,為使查找更高效瑞佩,緩存列表的底層就是通過(guò)哈希表實(shí)現(xiàn)對(duì)調(diào)用過(guò)的方法的緩存。
1.存
(1)根據(jù)key計(jì)算出索引值
(2)如果該索引中沒(méi)有元素坯台,就存入鍵值對(duì)
(3)如果該索引中有元素炬丸,索引沖突,就對(duì)索引進(jìn)行+1或-1進(jìn)行存儲(chǔ)
(4)如果當(dāng)前已存在的所有索引都有元素蜒蕾,就進(jìn)行哈希表的擴(kuò)容
(5)設(shè)置新空間是舊空間的2倍
(6)重新分配內(nèi)存
(7)重新設(shè)置掩碼_mask = newCapacity-1
(8)會(huì)將舊內(nèi)存釋放掉稠炬,清空緩存

  • objc_cache.mm源碼解析
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
    cacheUpdateLock.assertLocked();
    // Never cache before +initialize is done
    if (!cls->isInitialized()) return;
    // Make sure the entry wasn't added to the cache by some other thread 
    // before we grabbed the cacheUpdateLock.
    if (cache_getImp(cls, sel)) return;
    cache_t *cache = getCache(cls);
    cache_key_t key = getKey(sel);
    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = cache->occupied() + 1;
    mask_t capacity = cache->capacity();
    if (cache->isConstantEmptyCache()) {
        // Cache is read-only. Replace it.
        cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
    }
    else if (newOccupied <= capacity / 4 * 3) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        // Cache is too full. Expand it.
        cache->expand();
    }
    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the 
    // minimum size is 4 and we resized at 3/4 full.
    bucket_t *bucket = cache->find(key, receiver);
    if (bucket->key() == 0) cache->incrementOccupied();
    //設(shè)置bucket中的key和imp
    bucket->set(key, imp);
}
//擴(kuò)容
void cache_t::expand()
{
    cacheUpdateLock.assertLocked();
    
    uint32_t oldCapacity = capacity();
    //新的空間是舊的空間的2倍
    uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
    if ((uint32_t)(mask_t)newCapacity != newCapacity) {
        // mask overflow - can't grow further
        // fixme this wastes one bit of mask
        newCapacity = oldCapacity;
    }
    //重新分配內(nèi)存
    reallocate(oldCapacity, newCapacity);
}

void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
    bool freeOld = canBeFreed();
    bucket_t *oldBuckets = buckets();
    bucket_t *newBuckets = allocateBuckets(newCapacity);
    // Cache's old contents are not propagated. 
    // This is thought to save cache memory at the cost of extra cache fills.
    // fixme re-measure this
    assert(newCapacity > 0);
    assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
    //會(huì)重新設(shè)置最新的_mask = newCapacity - 1;
    setBucketsAndMask(newBuckets, newCapacity - 1);
    
    if (freeOld) {
        //會(huì)將舊的釋放掉,清空緩存
        cache_collect_free(oldBuckets, oldCapacity);
        cache_collect(false);
    }
}

2.取
(1)通過(guò)key得到索引
(2)do-while循環(huán)咪啡,如果散列表元素的key恰好等于按位與掩碼(&_mask)取出的索引首启,就直接返回
(3)如果不是,就將索引-1繼續(xù)查找

  • objc_cache.mm源碼解析
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);
    //返回buckets散列表
    bucket_t *b = buckets();
    mask_t m = mask();
    //得到索引
    mask_t begin = cache_hash(k, m);
    mask_t i = begin;
    do {
        //如果buket_t的索引的恰好等于通過(guò)&_mask取出的索引撤摸,就直接返回
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
    } while ((i = cache_next(i, m)) != begin);
    //如果不是毅桃,直接i-1,如果減到0還不行准夷,就直接是_mask(相當(dāng)于再?gòu)淖詈笠晃焕^續(xù)減)
    
    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}

static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
{
    //按位與mask得到索引
    return (mask_t)(key & mask);
}

#if __arm__  ||  __x86_64__  ||  __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
    //arm64架構(gòu)钥飞,索引-1
    return i ? i-1 : mask;
}
總結(jié)

Objective-C中的實(shí)現(xiàn),優(yōu)缺點(diǎn)并存衫嵌,但相對(duì)于實(shí)際情況而言读宙,還是優(yōu)點(diǎn)大于缺點(diǎn)。因?yàn)閷?duì)于方法緩存列表楔绞,一般不會(huì)有大量的數(shù)據(jù)结闸,擴(kuò)容或許是少數(shù)情況。此時(shí)酒朵,你可能會(huì)有疑問(wèn)膀估,當(dāng)數(shù)據(jù)量少的時(shí)候,豈不是犧牲了內(nèi)存耻讽?然而察纯,哈希表就是采用了空間換時(shí)間,犧牲內(nèi)存空間针肥,換取存取效率的方法饼记。所以,整體符合需求即可慰枕。
優(yōu)點(diǎn):
整體而言具则,提升了存取效率,時(shí)間復(fù)雜度為O(1)具帮,無(wú)需遍歷博肋。
缺點(diǎn):
當(dāng)哈希表比較大時(shí)低斋,如果擴(kuò)容會(huì)導(dǎo)致瞬間效率降低。

不同編程語(yǔ)言匪凡,哈希表的實(shí)現(xiàn)也各有特點(diǎn)膊畴,但是本質(zhì)和原理不變。

例如:
對(duì)于大量的數(shù)據(jù)或者數(shù)據(jù)庫(kù)中的存取病游,Java和Redis也有自己的優(yōu)化方法唇跨。
1.Java中,當(dāng)哈希函數(shù)不合理導(dǎo)致鏈表過(guò)長(zhǎng)時(shí)衬衬,會(huì)使用紅黑樹(shù)來(lái)保證插入和查找的效率买猖。
2.Redis 使用增量式擴(kuò)容方法優(yōu)化了這個(gè)缺點(diǎn),同時(shí)還有拉鏈法的實(shí)現(xiàn)(放在鏈表頭部頭插滋尉,因?yàn)樾虏迦氲恼{(diào)用概率會(huì)高)玉控。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市狮惜,隨后出現(xiàn)的幾起案子高诺,更是在濱河造成了極大的恐慌,老刑警劉巖讽挟,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丸冕,居然都是意外死亡耽梅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)胖烛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)眼姐,“玉大人,你說(shuō)我怎么就攤上這事佩番≈谄欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵趟畏,是天一觀的道長(zhǎng)贡歧。 經(jīng)常有香客問(wèn)我,道長(zhǎng)赋秀,這世上最難降的妖魔是什么利朵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮猎莲,結(jié)果婚禮上绍弟,老公的妹妹穿的比我還像新娘。我一直安慰自己著洼,他們只是感情好樟遣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布而叼。 她就那樣靜靜地躺著,像睡著了一般豹悬。 火紅的嫁衣襯著肌膚如雪葵陵。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天屿衅,我揣著相機(jī)與錄音埃难,去河邊找鬼。 笑死涤久,一個(gè)胖子當(dāng)著我的面吹牛涡尘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播响迂,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼考抄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蔗彤?” 一聲冷哼從身側(cè)響起川梅,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎然遏,沒(méi)想到半個(gè)月后贫途,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡待侵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年丢早,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秧倾。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怨酝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出那先,到底是詐尸還是另有隱情农猬,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布售淡,位于F島的核電站斤葱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏揖闸。R本人自食惡果不足惜苦掘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望楔壤。 院中可真熱鬧鹤啡,春花似錦、人聲如沸蹲嚣。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至抖部,卻和暖如春说贝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背慎颗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工乡恕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俯萎。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓傲宜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親夫啊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子函卒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • --- layout: post title: "如果有人問(wèn)你關(guān)系型數(shù)據(jù)庫(kù)的原理,叫他看這篇文章(轉(zhuǎn))" date...
    藍(lán)墜星閱讀 788評(píng)論 0 3
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,930評(píng)論 2 89
  • 今天看到一位朋友寫(xiě)的mysql筆記總結(jié)撇眯,覺(jué)得寫(xiě)的很詳細(xì)很用心报嵌,這里轉(zhuǎn)載一下,供大家參考下熊榛,也希望大家能關(guān)注他原文地...
    信仰與初衷閱讀 4,730評(píng)論 0 30
  • 我想要生孩子锚国,那只是因?yàn)槲易哉J(rèn)長(zhǎng)了幅還不錯(cuò)的皮囊,不生孩子年歲老去玄坦,可惜了血筑,我的孩子講不定還可以風(fēng)華絕代。 我需要...
    閉上耳朵閱讀 75評(píng)論 0 0
  • 夜風(fēng)起营搅,酒稍揮 何人不睡待愁催云挟。 玉岸累時(shí)塵梆砸。借書(shū)還時(shí)转质,仙人踏云南去。 今屋人帖世,言慎微 草樹(shù)紅碧滿(mǎn)窗悲休蟹。 星辰扶月...
    博寶寶閱讀 382評(píng)論 0 0