HashMap

我們通常使用的dict刮萌、map基本都是hashmap,表現(xiàn)形式就是key-value鍵值對的形式,如下圖:


我們可以使用鏈表或者數(shù)組的方式實現(xiàn)這個功能,但是他們各自有各自的的優(yōu)缺點
數(shù)組:尋址容易,但是增加刪除太麻煩婿禽,每次都要整體移動很多數(shù)據(jù)
鏈表:增減刪除簡單,只需要改變next指針的指向大猛,但是尋址麻煩

所以hashmap采用的是數(shù)組+鏈表的方式:

HashMap.png

實現(xiàn)原理:

通過開辟一定數(shù)量的數(shù)組空間bucket扭倾,數(shù)組里的指針指向一個鏈表,當(dāng)輸入key時挽绩,通過計算hashCode,然后對數(shù)組空間數(shù)量取模膛壹,來獲取數(shù)組bucket的index:即index = key.hash % capacity; 其中capacity就是數(shù)組的數(shù)量(如上圖的數(shù)量是10)。為什么bucket里的指針指向的是一個鏈表呢?是因為兩個key的hash值在取模之后有可能是一樣的琼牧。這種情況就將bucketIndex一樣的key恢筝,放在同一個鏈表上。

talk is cheap ,show me the code

struct Node{
    char * key; 
    void * value;
    struct Node * next; //指向下一個Node地址或者null
};

struct HashMap{
    //容量 (是容量巨坊,指的是bucketArr數(shù)組的的數(shù)量撬槽,不是key-value的數(shù)量)
    int capacity;
    //bucket array
    struct Node ** bucketArr;
    //添加key-value
    void(*add)(struct Node ** bucketArr,int capacity, char * key,void * value);
    //移除key-value
    void(*remove)(struct Node ** bucketArr,int capacity,char * key);
    //根據(jù)key獲取value的值
    void *(*get)(struct Node ** bucketArr,int capacity,char * key);
};

//根據(jù)key,通過hash,然后計算出index
int getBucketIndex(char * key,int capacity){
    unsigned long hashCode = [NSString stringWithFormat:@"%s",key].hash;
    return hashCode%capacity;
}

//添加bucket的index指向的鏈表中
void addKeyValue(struct Node ** bucketArr,int capacity, char * key,void * value){
    if(!key)return;
    int index = getBucketIndex(key, capacity);
    struct Node * head = bucketArr[index];
   //循環(huán)遍歷Node
    while (head!=NULL) {
        //如果鏈表中的Node存在相同key,直接替換value
        if(!strcmp(head->key, key)){
            head->next = value;
            return;
        }
        head = head->next;
    }
 
    //生成新node
    struct Node * newNode = malloc(sizeof(struct Node));
    newNode->key = key;
    newNode->value = value;
    newNode->next = NULL;
    //鏈表為空時
    if(head==NULL){
        bucketArr[index] = newNode;
    }else{
        //鏈表不為空時趾撵,將新的node的next指向原來的head,然后再將指向新node 地址的指針保存到bucket中侄柔。
        newNode->next = bucketArr[index];;
        bucketArr[index] = newNode;
    }
    
    //擴容(根據(jù)實際情況自定義,當(dāng)keyvalue數(shù)量過多占调,增加capaity)
    //TODO 暫時就不實現(xiàn)了
}

//移除bucket的index指向的鏈表中的特定key
void removeKey(struct Node ** bucketArr,int capacity,char * key){
    if(!key)return;
    int index = getBucketIndex(key, capacity);
    struct Node * head = bucketArr[index];
    struct Node * pre = NULL;
    struct Node * removeNode = NULL;
    //查找bucketIndex指向的鏈表中是否有對應(yīng)的key
     while (head!=NULL) {
         if(!strcmp(head->key, key)){
             removeNode = head;
             break;
         }
         //往后查找
         pre = head;
         head = head->next;
     }
    
    //說明鏈表中沒有對應(yīng)的要刪除的Node
    if(removeNode==NULL){
        return;
    }
    
    //說明不是鏈表頭結(jié)點
    if(pre!=NULL){
        pre->next = head->next;
    }
    //鏈表頭結(jié)點
    else{
        bucketArr[index] = head->next;
    }
}

//根據(jù)key獲取value的值
void * getKeyValue(struct Node ** bucketArr,int capacity,char * key){
    if(!key) return NULL;
    int index = getBucketIndex(key, capacity);
    struct Node * head = bucketArr[index];
    while (head!=NULL) {
        if(!strcmp(head->key, key)){
            return head->value;
            break;
        }
        head = head->next;
    }
    return NULL;
}


int main(int argc,char *argv[]){
    struct HashMap * hashMap = malloc(sizeof(struct HashMap));
    hashMap->capacity = 10;
    struct Node * nodeArr[10] = {0};
    hashMap->bucketArr = nodeArr;
    hashMap->add = addKeyValue;
    hashMap->remove = removeKey;
    hashMap->get = getKeyValue;
    //驗證
    char * key1 = "姓名1";
    char * key2 = "姓名2";
    char * key3 = "姓名3";
    hashMap->add(hashMap->bucketArr, hashMap->capacity, key1, @"王二牛");
    hashMap->add(hashMap->bucketArr, hashMap->capacity, key2, @"王大牛");
    hashMap->add(hashMap->bucketArr, hashMap->capacity, key3, @"王老牛");
    
    char * keys[3] = {key1,key2,key3};
    for(int i=0;i<3;i++){
        NSString * value = (__bridge NSString *)hashMap->get(hashMap->bucketArr,hashMap->capacity,keys[i]);
        NSLog(@"%s:%@",keys[i],value);
    }
/*
2020-09-14 08:32:07.696646+0800 Test[11948:327436] 姓名1:王二牛
2020-09-14 08:32:07.698029+0800 Test[11948:327436] 姓名2:王大牛
2020-09-14 08:32:07.698129+0800 Test[11948:327436] 姓名3:王老牛
能驗證get set功能
*/    

    hashMap->remove(hashMap->bucketArr, hashMap->capacity, key1);
    for(int i=0;i<3;i++){
        NSString * value = (__bridge NSString *)hashMap->get(hashMap->bucketArr,hashMap->capacity,keys[i]);
        NSLog(@"%s:%@",keys[i],value);
    }
    
/*
2020-09-14 08:32:07.698210+0800 Test[11948:327436] 姓名1:(null)
2020-09-14 08:32:07.698289+0800 Test[11948:327436] 姓名2:王大牛
2020-09-14 08:32:07.698364+0800 Test[11948:327436] 姓名3:王老牛
能驗證remove set功能
*/

//TODO: free object 釋放堆上創(chuàng)建的對象

    return 1;
}

這里說一下小插曲暂题,add方法中的struct Node * newNode = malloc(sizeof(struct Node))新生成一個對象這個地方,我調(diào)用兩次add方法究珊,每次返回的Node的地址都是一樣薪者,導(dǎo)致后面add的key-value會將前面的值覆蓋掉。應(yīng)該是方法棧執(zhí)行完后剿涮,棧對象被回收言津,導(dǎo)致每次調(diào)用alloc返回的都是同一個地址。后來改用malloc在堆上開辟空間取试,這樣悬槽,就得手動調(diào)用free來釋放對象。

總結(jié):

HashMap是通過數(shù)組+鏈表的方式實現(xiàn)快速的增加瞬浓、刪除初婆、獲取等方法
增加:通過key的hash值對bucket數(shù)量取模,來獲取index,進而獲取單向鏈表,判斷當(dāng)鏈表中沒有Node的key與新增的key相同磅叛,如果有則直接替換value屑咳。如果沒有,則創(chuàng)建一個新的Node,新Node的next指向原來鏈表的Head弊琴,并將bucket的index指向新的Node的地址乔宿。
刪除:通過key的hash值對bucket數(shù)量取模,來獲取index,進而獲取單向鏈表访雪。然后遍歷鏈表,遍歷Node沒有相同key掂林,則直接退出臣缀,如果有Node的key相等,若是頭結(jié)點則直接直接將bucketIndex指向head->next泻帮,若不是頭結(jié)點精置,則將pre->next指向head->next來實現(xiàn)刪除node結(jié)點。
獲取:找到bucketIndex指向的鏈表锣杂,然后遍歷鏈表脂倦,根據(jù)key找到對應(yīng)的Node,然后返回value值。找不到返回null元莫。
通過上面的實現(xiàn)赖阻,我們自然而然的也就理解了為什么hashmap是無序的了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末踱蠢,一起剝皮案震驚了整個濱河市火欧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茎截,老刑警劉巖苇侵,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異企锌,居然都是意外死亡榆浓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門撕攒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陡鹃,“玉大人,你說我怎么就攤上這事打却∩际剩” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵柳击,是天一觀的道長猿推。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么蹬叭? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任藕咏,我火速辦了婚禮,結(jié)果婚禮上秽五,老公的妹妹穿的比我還像新娘孽查。我一直安慰自己,他們只是感情好坦喘,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布盲再。 她就那樣靜靜地躺著,像睡著了一般瓣铣。 火紅的嫁衣襯著肌膚如雪答朋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天棠笑,我揣著相機與錄音梦碗,去河邊找鬼。 笑死蓖救,一個胖子當(dāng)著我的面吹牛洪规,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播循捺,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼斩例,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了巨柒?” 一聲冷哼從身側(cè)響起樱拴,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洋满,沒想到半個月后晶乔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡牺勾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年正罢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驻民。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡翻具,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出回还,到底是詐尸還是另有隱情裆泳,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布柠硕,位于F島的核電站工禾,受9級特大地震影響运提,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜闻葵,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一民泵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧槽畔,春花似錦栈妆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至早直,卻和暖如春铅檩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背莽鸿。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拾给,地道東北人祥得。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像蒋得,于是被迫代替她去往敵國和親级及。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355