哈希算法詳解(附帶 iOS 開發(fā)中實(shí)際應(yīng)用)

前言

哈希(Hash)或者說散列表,它是一種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。Hash 表是一種特殊的數(shù)據(jù)結(jié)構(gòu),它同數(shù)組、鏈表以及二叉排序樹等相比較有很明顯的區(qū)別,但它又是是數(shù)組和鏈表的基礎(chǔ)上演化而來,既具有數(shù)組的有點(diǎn),又具有鏈表的有點(diǎn)。能夠快速定位到想要查找的記錄,而不是與表中存在的記錄的關(guān)鍵字進(jìn)行比較來進(jìn)行查找。應(yīng)用了函數(shù)映射的思想將記錄的存儲位置與記錄的關(guān)鍵字關(guān)聯(lián)起來,從而能夠很快速地進(jìn)行查找。

一、Hash設(shè)計(jì)思想

試想如果我們對一個(gè)數(shù)組進(jìn)行查詢,這個(gè)數(shù)組里,每一個(gè)元素都是一個(gè)字符串。我們知道數(shù)組最快的檢索辦法是通過數(shù)組的下標(biāo)進(jìn)行檢索,但是對于這種場景,我們無能為力,只能從頭查到尾,從而查詢出目標(biāo)元素。

1 2 3 4 5 6
zhangsan lisi wanger wangwu zhangsi gaofei

如果我們要根據(jù)名字找到其中的任何一個(gè)元素,就需要遍歷整個(gè)數(shù)組。最壞情況下時(shí)間復(fù)雜度是O(n) ,但是借助 Hash 可以將時(shí)間復(fù)雜度降為O(1)。

Hash表采用一個(gè)映射函數(shù) f :key —> address 將關(guān)鍵字映射到該記錄在表中的存儲位置,從而在想要查找該記錄時(shí),可以直接根據(jù)關(guān)鍵字和映射關(guān)系計(jì)算出該記錄在表中的存儲位置,通常情況下,這種映射關(guān)系稱作為Hash函數(shù),而通過Hash函數(shù)和關(guān)鍵字計(jì)算出來的存儲位置(注意這里的存儲位置只是表中的存儲位置,并不是實(shí)際的物理地址)稱作為Hash地址。比如上述例子中,假如聯(lián)系人信息采用Hash表存儲,則當(dāng)想要找到 “l(fā)isi” 的信息時(shí),直接根據(jù) “l(fā)isi” 和 Hash 函數(shù)計(jì)算出 Hash 地址即可。

哈希算法歷史悠久,業(yè)界著名的哈希算法也有很多,比如 MD5、SHA。哈希算法是指將任意長度的二進(jìn)制值串映射為固定長度的二進(jìn)制值串,這個(gè)映射的規(guī)則就是哈希算法,而通過原始數(shù)據(jù)映射之后得到的二進(jìn)制值串就是哈希值。有以下幾個(gè)特點(diǎn):

  • 從哈希值不能反向推導(dǎo)出原始數(shù)據(jù)(所以哈希算法也叫單向哈希算法或單向散列函數(shù))。
  • 對輸入數(shù)據(jù)非常敏感,哪怕原始數(shù)據(jù)只修改了一個(gè) Bit,最后得到的哈希值也大不相同。
  • 散列沖突的概率要很小,對于不同的原始數(shù)據(jù),哈希值相同的概率非常小。
  • 哈希算法的執(zhí)行效率要盡量高效,針對較長的文本,也能快速地計(jì)算出哈希值。

為了更好說明這種設(shè)計(jì)思想,筆者先設(shè)計(jì)出一種最笨的 Hash 函數(shù),將所有字符串中的字符轉(zhuǎn)化為數(shù)字后相加。

858 433 644 665 756 619
zhangsan lisi wanger wangwu zhangsi gaofei

上表中數(shù)組的下標(biāo)就是字符串對應(yīng)的數(shù)字值。根據(jù)對應(yīng)的數(shù)字值,我們就能輕易找到任何想要的對象,時(shí)間復(fù)雜度為O(1)。

二、Hash函數(shù)設(shè)計(jì)

所謂的 hash 算法就是將字符串轉(zhuǎn)換為數(shù)字的算法。通常有以下幾種構(gòu)造 Hash 函數(shù)的方法:

2.1 直接定址法

取關(guān)鍵字或者關(guān)鍵字的某個(gè)線性函數(shù)為 Hash 地址,即address(key) = a * key + b; 如知道學(xué)生的學(xué)號從2000開始,最大為4000,則可以將address(key)=key-2000(其中a = 1)作為Hash地址。

2.2 平方取中法

對關(guān)鍵字進(jìn)行平方計(jì)算,取結(jié)果的中間幾位作為 Hash 地址。如有以下關(guān)鍵字序列 {421,423,436} ,平方之后的結(jié)果為 {177241,178929,190096} ,那么可以取中間的兩位數(shù) {72,89,00} 作為 Hash 地址。

2.3 折疊法

將關(guān)鍵字拆分成幾部分,然后將這幾部分組合在一起,以特定的方式進(jìn)行轉(zhuǎn)化形成Hash地址。如圖書的 ISBN 號為 8903-241-23,可以將 address(key)=89+03+24+12+3 作為 Hash 地址。

2.4 除留取余法

如果知道 Hash 表的最大長度為 m,可以取不大于m的最大質(zhì)數(shù) p,然后對關(guān)鍵字進(jìn)行取余運(yùn)算,address(key)=key % p。這里 p 的選取非常關(guān)鍵,p 選擇的好的話,能夠最大程度地減少?zèng)_突,p 一般取不大于m的最大質(zhì)數(shù)。

三、Hash表大小的確定

Hash 表的空間如果遠(yuǎn)遠(yuǎn)大于實(shí)際存儲的記錄數(shù)據(jù)的個(gè)數(shù),則造成空間浪費(fèi);如果過小,則容易造成沖突。Hash 表大小確定通常有這兩種思路:

  • 如果最初知道存儲的數(shù)據(jù)量,則需要根據(jù)存儲個(gè)數(shù)關(guān)鍵字的分布特點(diǎn)來確定 Hash 表的大小。
  • 事先不知道最終需要存儲的記錄個(gè)數(shù),需要?jiǎng)討B(tài)維護(hù)Hash表的容量,此時(shí)可能需要重新計(jì)算 Hash 地址。

四、Hash 沖突及解決方案

4.1 Hash沖突產(chǎn)生

有這樣一個(gè)問題:因?yàn)槲覀兪怯脭?shù)組大小對哈希值進(jìn)行取模,有可能不同鍵值所得到的索引值相同,這里就是沖突。如在最初的實(shí)例中,如果多出了sizhang這樣一個(gè)元素,那么就存在兩個(gè) 756。

858 433 644 665 756 619 756
zhangsan lisi wanger wangwu zhangsi gaofei sizhang

顯然出現(xiàn)的這種情況是不合理的,解決該沖突的方法就是改變數(shù)據(jù)結(jié)構(gòu)。我們將數(shù)組內(nèi)的元素改變?yōu)橐粋€(gè)鏈表,這樣就能容下足夠多的元素了,沖突問題也能得到解決。具體如何解決請看下面的鏈地址法。

4.2 Hash 沖突解決

4.2.1 開放定址法

發(fā)生沖突時(shí),使用某種探測技術(shù)在 Hash 表中形成一個(gè)探測序列,然后沿著這個(gè)探測序列依次查找下去,當(dāng)碰到一個(gè)空的單元時(shí),則插入其中。比較常用的探測方法有線性探測法,如有一組關(guān)鍵字{12,13,25,23,38,34,6,84,91},Hash 表長為14,Hash 函數(shù)為 address(key) = key % 11,當(dāng)插入12,13,25時(shí)可以直接插入,而當(dāng)插入 23 時(shí),地址 1 被占用了(因?yàn)?12%11 和 23%11 的結(jié)果相同)。此時(shí)沿著地址 1 依次往下探測(探測步長可以根據(jù)情況而定),直到探測到地址4,發(fā)現(xiàn)為空,則將 23 插入其中。

4.2.2 鏈地址法

采用數(shù)組和鏈表相結(jié)合的數(shù)據(jù)結(jié)構(gòu),將 Hash 地址相同的記錄存儲在一張線性表中,而每張表的表頭的序號即為計(jì)算得到的Hash地址。如下圖最左邊是數(shù)組結(jié)構(gòu),數(shù)組內(nèi)的元素為鏈表結(jié)構(gòu)。

采用鏈地址法形成的 Hash 表存儲形式

所以針對之前案列沖突的解決方案如下:


檢索的時(shí)候可以這樣檢索,首先找到gaofei后,之后再遍歷鏈表,找到feigao了。同理對于 sizhang 的沖突也是如此解決。

五、Hash 表的用處以及優(yōu)劣

5.1 Hash 表的實(shí)際應(yīng)用

上述說了這么多關(guān)于 Hash 表的知識點(diǎn),但是 Hash 表在代碼的世界中,實(shí)際上又有什么應(yīng)用場景,可能有些讀者會(huì)一頭霧水,這里筆者就以簡單的三個(gè)例子來說明 Hash 表的實(shí)際應(yīng)用場景。

  • 1 、找出兩文件找出重復(fù)的元素
    假設(shè)有兩個(gè)文件,文件中均包含一些短字符串,字符串個(gè)數(shù)分別為n。它們是有重復(fù)的字符串,現(xiàn)在需要找出所有重復(fù)的字符串。
    最笨的解決辦法可能是:遍歷文件 1 中的每個(gè)元素,取出每一個(gè)元素分別去文件 2 中進(jìn)行查找,這樣的時(shí)間復(fù)雜度為O(n^2)。
    但是借助 Hash 表可以有一種相對巧妙的方法,分別遍歷文件 1 中的元素和文件 2 中的元素,然后放入 Hash Table 中,對于遍歷的每一個(gè)元素我們只要簡單的做一下計(jì)數(shù)處理即可。最后遍歷整個(gè) Hash 列表,找出所有個(gè)數(shù)大于 1 的元素即為重復(fù)的元素。
  • 2、找出兩文件找出出現(xiàn)次數(shù)最多的元素
    找出兩文件找出重復(fù)的元素這樣的問題解決方案類似,只是在最后遍歷的時(shí)找計(jì)數(shù)最大的元素,即為出現(xiàn)次數(shù)最多的元素。
  • 3、路由算法
    多線程處理數(shù)據(jù)的場景下,通常需要將一個(gè)數(shù)據(jù)集分給不同的線程進(jìn)行處理,同時(shí)要保證,相同的元素需要分到相同的處理線程上。這
    其實(shí)這個(gè)就是一個(gè)很典型的 Hash 值應(yīng)用場景,對于很多的計(jì)算引擎默認(rèn)都是用 Hash 算法去解決這個(gè)問題。因?yàn)橄嗤氐?Hash 值相同,那么我們可以取 Hash 之后進(jìn)行模運(yùn)算,運(yùn)算結(jié)果分配到不同的線程。

5.2 Hash 表的優(yōu)缺點(diǎn)及注意點(diǎn)

  • 優(yōu)點(diǎn)
    哈希表的效率非常高,查找、插入、刪除操作只需要接近常量的時(shí)間即0(1)的時(shí)間級。如果需要在一秒種內(nèi)查找上千條記錄通常使用哈希表,哈希表的速度明顯比樹快,樹的操作通常需要O(N)的時(shí)間級。哈希表不僅速度快,編程實(shí)現(xiàn)也相對容易。如果不需要遍歷數(shù)據(jù),不二的選擇。
  • 缺點(diǎn)
    它是基于數(shù)組的,數(shù)組創(chuàng)建后難于擴(kuò)展。有些情況下,哈希表被基本填滿時(shí),性能下降得非常嚴(yán)重,所以開發(fā)者必須要清楚表中將要存儲的數(shù)據(jù)量?;蛘咭部梢远ㄆ诘匕褦?shù)據(jù)轉(zhuǎn)移到更大的哈希表中,不過這個(gè)過程耗時(shí)相對比較大。
  • 注意點(diǎn)
    在設(shè)計(jì)Hash算法的時(shí)候。一定要保證相同字符串產(chǎn)生的 Hash 值相同,同時(shí)要盡量的減小Hash沖突的發(fā)生,這樣才算是好的 hash 算法。

六、Hash 在 iOS 中的應(yīng)用

這一部分的篇幅可能稍稍有點(diǎn)大,筆者原本打算給這一部分抽出來單獨(dú)寫一篇文章,但是發(fā)現(xiàn)沒有 Hash 概念做鋪墊,文章略顯空洞,所以這里干脆把所有東西整合到一起,請讀者耐下心來看。

這一部分的內(nèi)容就以下面的一個(gè)問題為中心。并在此問題上不斷的擴(kuò)充,以點(diǎn)帶面。

iOS系統(tǒng)API給我們提供一個(gè)自動(dòng)過濾重復(fù)元素的容器 NSMutableSet/NSSet,如:當(dāng)我們向該實(shí)例對象中添加字符串時(shí),如果重復(fù)添加兩個(gè)相同的字符串,集合中只會(huì)保留一個(gè)。NSMutableSet/NSSet內(nèi)部一些實(shí)現(xiàn)機(jī)制要比我們自己寫的濾重方法效率高。但是對于自定義一個(gè)類如Person,如果想利用NSMutableSet/NSSet來過濾重復(fù)元素(如多個(gè)Person實(shí)例的uid相同),我們必須要同時(shí)實(shí)現(xiàn)- (BOOL)isEqual:- (NSUInteger)hash這兩個(gè)方法。這里先簡單介紹他們的關(guān)系:兩個(gè)相等的實(shí)例,他們的hash值一定相等。但是hash值相等的兩個(gè)實(shí)例,不一定相等。重點(diǎn)來了,利用 NSMutableSet/NSSet 具體如何實(shí)現(xiàn)過濾 Person 重復(fù)元素 ?

在解決這個(gè)問題之前我先用簡單的篇幅 6.1 小結(jié) 和 6.2 小結(jié) 分別介紹- (BOOL)isEqual:- (NSUInteger)hash這兩個(gè)方法。具體可以參考這篇文章。

6.1 關(guān)于- (BOOL)isEqual:方法

  • 為什么要有isEqual方法?
    OC 中 == 運(yùn)算符只是簡單地判斷是否是同一個(gè)對象, 而 isEqual 方法可以判斷對象是否相同。

  • 如何重寫isEqual方法?
    但對于自定義類型來說, 做判等時(shí)通常需要重寫isEqual方法。

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *birthday;
@end
- (BOOL)isEqual:(id)object {
    if (self == object) {
        return YES;
    }

    if (![object isKindOfClass:[Person class]]) {
        return NO;
    }

    return [self isEqualToPerson:(Person *)object];
}

- (BOOL)isEqualToPerson:(Person *)person {
    if (!person) {
        return NO;
    }

    BOOL haveEqualNames = (!self.name && !person.name) || [self.name isEqualToString:person.name];
    BOOL haveEqualBirthdays = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday];

    return haveEqualNames && haveEqualBirthdays;
}

上述代碼主要步驟如下:
1、 ==運(yùn)算符判斷是否是同一對象, 因?yàn)橥粚ο蟊厝煌耆嗤?br> 2、 判斷是否是同一類型, 這樣不僅可以提高判等的效率, 還可以避免隱式類型轉(zhuǎn)換帶來的潛在風(fēng)險(xiǎn)
3、通過封裝的isEqualToPerson方法, 提高代碼復(fù)用性
4、 判斷person是否是nil, 做參數(shù)有效性檢查
5、 對各個(gè)屬性分別使用默認(rèn)判等方法進(jìn)行判斷
6、 返回所有屬性判等的與結(jié)果

6.2 關(guān)于- (NSUInteger)hash方法

  • hash方法什么時(shí)候被調(diào)用?
    如果在 Person 類中重寫- (NSUInteger)hash方法,該方法只在 Person 實(shí)例對象被添加至NSSet或?qū)erson實(shí)例對象設(shè)置為NSDictionary的 key 時(shí)會(huì)調(diào)用。注意是設(shè)置為 key 而不是 value

  • hash方法和判等的關(guān)系?
    為了優(yōu)化判等的效率, 基于 hash 的 NSSet 和 NSDictionary 在判斷成員是否相等時(shí), 通常會(huì)這樣做:
    首先判斷 hash 值是否和目標(biāo) hash 值相等。如果相同再進(jìn)行對象之后的判等邏輯, 作為判等的結(jié)果; 如果不等, 直接判斷為不相等。
    簡單地說:hash值是對象判等的必要非充分條件。

  • 如何重寫 hash 方法?
    很多人在iOS開發(fā)中, 都是這么重寫hash方法的,如果自己親自測試一下會(huì)發(fā)現(xiàn)直接重寫父類方法并不能實(shí)現(xiàn)過濾重復(fù)元素的功能。

- (NSUInteger)hash {
    return [super hash];
}

對于上面的 Person 類正確的 Hash 實(shí)現(xiàn)方法應(yīng)該是借助位運(yùn)算。代碼如下:

- (NSUInteger)hash {
    return [self.name hash] ^ [self.birthday hash];
}

6.3 同時(shí)實(shí)現(xiàn)- (BOOL)isEqual:- (NSUInteger)hash方法,實(shí)現(xiàn)過濾自定義實(shí)例的功能

6.3.1 代碼實(shí)現(xiàn)
@interface Person : NSObject
@property (nonatomic, assign) NSInteger uid;
@property (nonatomic, strong) NSString *name;
@end

@implementation Person
- (instancetype)initWithID:(NSInteger)uid name:(NSString *)name{
    if (self = [super init]) {
        self.uid = uid;
        self.name = name;
    }
    return self;
}

- (BOOL)isEqual:(Person *)object{
    BOOL result;
    if (self == object) {
        result = YES;
    }else{
        if (object.uid == self.uid) {
            result = YES;
        }else{
            result = NO;
        }
    }
    NSLog(@"%@ compare with %@ result = %@",self,object,result ? @"Equal":@"NO Equal");
    return result;
}

- (NSString *)description{
    return [NSString stringWithFormat:@"%p(%ld,%@)",self,self.uid,self.name];
}

- (NSUInteger)hash{
    NSUInteger hashValue = self.uid; //在這里只需要比較uid就行。這
    樣的話就滿足如果兩個(gè)實(shí)例相等,那么他們的 hash 一定相等,但反過
    來hash值相等,那么兩個(gè)實(shí)例不一定相等。但是在 Person 這個(gè)實(shí)例
    中,hash值相等那么實(shí)例一定相等。(不考慮繼承之類的)
    NSLog(@"hash = %lu,addressValue = %lu,address = %p",(NSUInteger)hashValue,(NSUInteger)self,self);
    return hashValue;
}
@end
//調(diào)用重寫hash后的方法
- (void)viewDidLoad {
    [super viewDidLoad];
    self.mutSet = [NSMutableSet set];
    Person *person1 = [[Person alloc] initWithID:1 name:@"nihao"];
    Person *person2 = [[Person alloc] initWithID:2 name:@"nihao2"];
    NSLog(@"begin add %@",person1);
    [self.mutSet addObject:person1];
    NSLog(@"after add %@",person1);

    NSLog(@"begin add %@",person2);
    [self.mutSet addObject:person2];
    NSLog(@"after add %@",person2);

    NSLog(@"count = %d",self.mutSet.count);

    Person *person3 = [[Person alloc] initWithID:1 name:@"nihao"];
    NSLog(@"begin add %@",person3);
    [self.mutSet addObject:person3];
    NSLog(@"after add %@",person3);

    NSLog(@"count = %d",self.mutSet.count);
}
6.3.2 關(guān)于一些結(jié)論
  • NSMutableSet/NSSet中添加 Person 對象的時(shí)候,就會(huì)調(diào)用- (NSUInteger)hash方法。

  • NSMutableSet/NSSet中添加 personA 對象的時(shí)候,如果NSMutableSet/NSSet 中之前就已經(jīng)存在 personB對象,且 personB 對象的 - (NSUInteger)hash返回值和personA的- (NSUInteger)hash返回值相等, 則 personA 會(huì)繼續(xù)調(diào)用- (BOOL)isEqual:方法 ,其中此方法以personB為參數(shù);否則不等, 繼續(xù)下一個(gè)元素判斷。

  • 具體的判等過程如下。
    1、如果 personB 的- (NSUInteger)hash返回值是否和 personA 的- (NSUInteger)hash返回值相等,則直接執(zhí)行第 3 步;如果不相等,則執(zhí)行第 2 步。
    2、判斷 NSMutableSet/NSSet 中是否存在下一個(gè)沒有比較過的元素,如果有繼續(xù)執(zhí)行第 1 步;如果沒有,則personA 會(huì)被添加到NSMutableSet/NSSet 集合中,執(zhí)行結(jié)束命令。
    3、調(diào)用 personA 的- (BOOL)isEqual:其中該方法 以personB為參數(shù),如果返回結(jié)果為 NO(兩者不相等), 則執(zhí)行第 2 步;如果返回結(jié)果為Yes(兩者是相同元素),則 NSMutableSet/NSSet 中存在和 personA 相同的元素,personA不會(huì)被添加到集合中,直接執(zhí)行結(jié)束命令。

七、總結(jié)

本文章主要講解了 Hash 的設(shè)計(jì)思想、Hash 函數(shù)設(shè)計(jì)、Hash 沖突的產(chǎn)生和解決、 Hash 的優(yōu)缺點(diǎn)以及應(yīng)用,最后結(jié)合實(shí)際代碼,說明了 Hash 在 iOS 判等過程中的實(shí)際應(yīng)用。關(guān)于 Hash 實(shí)際上還有很多值得我們研究的問題,就單單是 Hash 函數(shù)設(shè)計(jì)而言,就足夠我們花上很多功夫去研究,當(dāng)然感興趣的同學(xué)可以去仔細(xì)研究下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,516評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,484評論 2 379

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

  • 閑話少說,先說本編博客的核心 iOS系統(tǒng)API給我們提供一個(gè)自動(dòng)過濾重復(fù)元素的容器 NSMutableSet...
    upworld閱讀 2,877評論 8 21
  • 卷首語 歡迎來到 objc.io 第七期! 這個(gè)月,我們選擇了 Foundation 框架作為我們的主題。 Fou...
    評評分分閱讀 1,554評論 0 8
  • 本文Demo的完整工程代碼, 參考這里的EqualAndHashDemo 目錄 為什么要有isEqual方法? 如...
    諾之林閱讀 44,577評論 34 212
  • 最近好多文章都在說女人要獨(dú)立,經(jīng)濟(jì)獨(dú)立,人格獨(dú)立等等。也不知道如今的社會(huì)女人們到底怎么了還是男人們到底怎么了...
    匆匆不離去閱讀 202評論 0 0
  • 減肥訓(xùn)練營第一天 6:00-8:00早晨起床空腹稱重拍照 然后喝一瓶酵素?300cc溫開水 20分鐘后吃早餐 早餐...
    lly520616閱讀 423評論 0 0