哈希表,布隆過濾器哟冬,一致性哈希

哈希函數(shù)與哈希表

哈希函數(shù)的性質(zhì)

哈希函數(shù)又叫散列函數(shù),例如MD5,SHA1等错敢,哈希函數(shù)具有以下特性:

  • 一個(gè)哈希函數(shù)的輸入域是無窮大的
  • 一個(gè)哈希函數(shù)的輸出域雖然很大纸淮,但是是有限的
    例如MD5輸出的hashcode為16位的16進(jìn)制數(shù)咽块,則MD5的輸出域S表示的范圍為:0~2^64-1
  • 相同的輸入具有相同的輸出
    即:same input , same output
    例如:一個(gè)哈希函數(shù)接收一個(gè)字符串
    那么有:h("A") = h("A")
  • 哈希碰撞
    輸入不同糜芳,但有可能得到相同的輸出
    試想峭竣,一個(gè)哈希函數(shù)的輸入域無窮大但輸出域有固定范圍皆撩,那么必然會有幾個(gè)不同的輸入可能返回一樣的輸出扛吞,這種情況叫做哈希碰撞或哈希沖突
  • 哈希函數(shù)的離散型
    假設(shè)有一樣本量是0~998的999個(gè)整數(shù)滥比,現(xiàn)有一哈希函數(shù)盲泛,輸出域固定為[0,2],那么每個(gè)樣本經(jīng)過哈希函數(shù)后得到的值會大致平均落在輸出域上寺滚,即:
    落在0上的樣本有33個(gè)左右
    落在1上的樣本有33個(gè)左右
    落在2上的樣本有33個(gè)左右
    這就叫做哈希函數(shù)的離散型
    通過哈希函數(shù)具有離散性可以知道官套,哈希函數(shù)的輸入和輸出是無關(guān)的奶赔,如果輸入和輸出相關(guān)杠氢,那么結(jié)果必然不是離散的修然。所以即便是兩個(gè)很相近的輸入也有可能得到相差很大的輸出,所以利用哈希函數(shù)的離散性结榄,可以打亂輸入規(guī)律臼朗。
    根據(jù)哈希函數(shù)的離散性還可以推出视哑,如果對所有輸入經(jīng)過hash函數(shù)得到的hashcode取模即:hashcode%M挡毅,那么在[0,M-1]這個(gè)域上結(jié)果也是均勻分布的。

哈希表

哈希表的本質(zhì)是“桶”取逾,對于哈希表而言砾隅,增刪改查操作都是O(1)的時(shí)間復(fù)雜度晴埂,經(jīng)典結(jié)構(gòu)的哈希表使用了鏈地址法來解決哈希沖突的問題

鏈地址法

對于一個(gè)輸入邑时,通過hash函數(shù)求出它所對應(yīng)的hashcode后再取模晶丘,那么結(jié)果所對應(yīng)的值就會落到[0,M-1]這個(gè)區(qū)間內(nèi)浅浮。


image.png

鏈地址法的本質(zhì)就是"數(shù)組+鏈表",數(shù)組中存儲的是一個(gè)個(gè)Node,如果出現(xiàn)了哈希沖突,那么就將沖突的值掛在鏈表的后面。
假設(shè)輸入i1落到了2這個(gè)位置上淮捆,i2落到了3的位置桐腌,i3算出hashcode并取模后為2案站,發(fā)生哈希沖突蟆盐,解決辦法就是將沖突的值添加到鏈上石挂。


image.png

那么哈希表是如何做到增刪改查為O(1)的時(shí)間復(fù)雜度誊稚?我們說過城瞎,哈希表是一個(gè)離散表脖镀,當(dāng)樣本量足夠大且某個(gè)鏈的長度達(dá)到一定的值蜒灰,比如說這個(gè)值為10强窖,那么我就可以認(rèn)為翅溺,每個(gè)鏈的平均長度都達(dá)到了10左右咙崎,這個(gè)時(shí)候再向哈希表中put元素效率就會變差褪猛,此時(shí)對數(shù)組進(jìn)行擴(kuò)容操作伊滋。擴(kuò)容后笑旺,"桶"的數(shù)量增加燥撞,但是也要相對付出擴(kuò)容代價(jià),因?yàn)樵竟1砩蠏斓拿總€(gè)值都要重新模上擴(kuò)容后數(shù)組的長度再重新分配到新的表上戏锹。
java中的哈希表

Java中的HashMap的結(jié)構(gòu)本質(zhì)上是數(shù)組+TreeMap锦针,也就是數(shù)組上掛的是一個(gè)個(gè)紅黑樹悉盆。


image.png

Java8以前,哈希表的結(jié)構(gòu)為數(shù)組+鏈表脚翘,在Java8以后来农,當(dāng)裝載因子(數(shù)組承載的鏈表的長度)達(dá)到一定的閾值后沃于,每個(gè)位置從鏈表進(jìn)化成紅黑樹,這個(gè)閾值為8饿肺。

開放地址法

開放地址法可以做到所有輸入的元素全部盛放在桶中雪标,也就是說桶上面不需要掛鏈表或者紅黑樹村刨,裝載因子不會超過1嵌牺。
開放地址法的基本思路為:當(dāng)發(fā)生哈希沖突逆粹,根據(jù)再尋址的方法即僻弹,以當(dāng)前地址為基準(zhǔn)蹋绽,繼續(xù)探測哈希表中的其他存儲單元退敦,直到找到空位置為止。

HashMap的增刪改查

Java 中 HashMap 的增刪改查

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

public class HashMapTest {

    public static void main(String[] args){
        HashMap<String,Integer> hashMap = new HashMap<>();
        hashMap.put("kim",25);
        // 新的value值會覆蓋掉原來的value值
        hashMap.put("kim",26);
        hashMap.put("dog",2);
        hashMap.put("cat",2);
        hashMap.put("gary",39);
        hashMap.put("laowang",27);
        hashMap.put("laozhang",28);

        // 是否存在 key: hashMap.containsKey(key)
        System.out.println(hashMap.containsKey("kim"));
        System.out.println("========================");
        // 獲取key對應(yīng)的value值: value = hashMap.get(key)
        System.out.println(hashMap.get("kim"));
        System.out.println("========================");
        // isEmpty & size
        System.out.println(hashMap.isEmpty());
        System.out.println(hashMap.size());
        System.out.println("========================");
        // 從哈希表中刪除key和value: hashMap.remove(key),返回刪除的key對應(yīng)的value
        System.out.println(hashMap.remove("laozhang"));
        System.out.println(hashMap.containsKey("laozhang"));
        System.out.println(hashMap.size());
        System.out.println("========================");

        // 遍歷
        // 遍歷hashMap中所有的key
        for(String key : hashMap.keySet()){
            System.out.print(key + " ");
        }
        System.out.println();
        System.out.println("========================");

        // 遍歷hashMap中所有的value
        for(Integer value : hashMap.values()){
            System.out.print(value + " ");
        }
        System.out.println();
        System.out.println("========================");

        // 遍歷hashMap中所有的 key 和 value
        for(Entry<String,Integer> entry : hashMap.entrySet()){
            System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());
        }
        System.out.println("========================");

        // 批量刪除,本示例中為刪除所有value為 2 的元素
        List<String> removeKeys = new ArrayList<>();
        for(Entry<String,Integer> entry : hashMap.entrySet()){
            if(entry.getValue().equals(2)){
                removeKeys.add(entry.getKey());
            }
        }
        for(String removeKey:removeKeys){
            hashMap.remove(removeKey);
        }
        for(Entry<String,Integer> entry : hashMap.entrySet()){
            System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());
        }
    }
}

hash的應(yīng)用-海量數(shù)據(jù)分流處理

對于一個(gè)大流量請求時(shí),通過hash函數(shù)可以將流量分流到幾個(gè)服務(wù)器進(jìn)行處理,因?yàn)閔ash函數(shù)具有離散性战虏,所以我們認(rèn)為每個(gè)服務(wù)器是負(fù)載均衡的烦感。

image.png

但是這樣做也有缺點(diǎn)手趣,如果說某一臺服務(wù)器出現(xiàn)了故障或者說需要新添加一臺服務(wù)器,那么就需要重新去計(jì)算每一個(gè)請求的key的哈希值肥荔,這樣一來绿渣,大量的key會被重新定位到不同的服務(wù)器而造成大量的緩存不命中。一致性哈希則能有效解決此類問題燕耿,本文后面有具體的介紹中符。
(以上內(nèi)容參考了文章:海量數(shù)據(jù)分流處理-------一致性哈希算法)

哈希表相關(guān)題目

設(shè)計(jì)一種 RandomPool 結(jié)構(gòu)
設(shè)計(jì)一種結(jié)構(gòu),在該結(jié)構(gòu)中有如下三種功能:
1. insert(key):將某個(gè)key加入到該結(jié)構(gòu)誉帅,做到不重復(fù)加入淀散。
2. delete(key):將原本在結(jié)構(gòu)中的某個(gè)key移除。
3. getRandom():等概率隨機(jī)返回結(jié)構(gòu)中的任何一個(gè)key

要求 : insert、delete和getRandom方法的時(shí)間復(fù)雜度都是 O(1)

當(dāng)看到插入胀瞪,刪除,查詢的操作均為O(1)時(shí)轴咱,我們就想到了使用哈希表去實(shí)現(xiàn)RandomPoll結(jié)構(gòu)戈稿,思路如下:


image.png

使用兩個(gè)哈希表和一個(gè)size變量般甲,map1存儲的key和value類型分別為K和Integer,map2存儲的key和value類型分別為Integer和K。
當(dāng)向RandPool結(jié)構(gòu)中插入數(shù)據(jù)時(shí)挽牢,兩表同時(shí)put相應(yīng)的數(shù)據(jù)和索引,變量size++;
當(dāng)執(zhí)行g(shù)etRandom操作返回一個(gè)隨機(jī)的key時(shí),通過Math.random確定隨機(jī)數(shù)豁辉,這個(gè)隨機(jī)數(shù)即對應(yīng)map2中的key,隨之返回map2中的value即可;
當(dāng)執(zhí)行delete操作時(shí)餐抢,如圖所示:
依次向哈希表中 put 進(jìn) A~Z 26 個(gè)字符串苦蒿。假設(shè)現(xiàn)在要?jiǎng)h除的key為“C”


image.png

如果刪除了("C",3)這樣一組元素报强,那么在hashmap上面就會出現(xiàn)“空洞”,這樣就無法做到可以等概率隨機(jī)返回此結(jié)構(gòu)中的任何一個(gè)key了,解決方案如下:
image.png

即淳玩,將“最后一個(gè)元素”覆蓋掉刪除的元素承匣,這樣哈希表就可以保證連貫性宽闲。具體實(shí)現(xiàn)代碼如下:

import java.util.HashMap;

public class RandPoolProblem {

    public static class Pool<K>{

        private HashMap<K,Integer> map1;
        private HashMap<Integer,K> map2;
        private int size;

        public Pool(){
            this.map1 = new HashMap<>();
            this.map2 = new HashMap<>();
            this.size = 0;
        }

        public void insert(K key){
            if(!map1.containsKey(key)){
                map1.put(key,size);
                map2.put(size,key);
                size++;
            }
        }

        public void delete(K key){
            if(map1.containsKey(key)){
                int delIndex = map1.get(key);
                int lastIndex = --size;
                K lastKey = map2.get(lastIndex);
                map1.put(lastKey,delIndex);
                map2.put(delIndex,lastKey);
                map1.remove(key);
                map2.remove(lastIndex);
            }
        }

        public K getRandom(){
            if(size == 0){
                return null;
            }
            int randomIndex = (int)(Math.random() * size);
            return map2.get(randomIndex);
        }
    }
}

布隆過濾器

布隆過濾器(Bloom Filter)實(shí)際上是一個(gè)比特?cái)?shù)組习蓬。布隆過濾器可以用于檢測一個(gè)元素是否在一個(gè)集合中浪规,它的優(yōu)點(diǎn)是省空間找御,以及時(shí)間讨永,缺點(diǎn)是有誤差率揪漩。
布隆過濾器的應(yīng)用案例主要有:爬蟲去重,黑名單問題等塘娶,我們從黑名單問題開始認(rèn)識布隆過濾器

黑名單問題
現(xiàn)在有10億個(gè)URL黑名單根吁,每個(gè)URL固定為32字節(jié)
需要設(shè)計(jì)一種結(jié)構(gòu)衡瓶,當(dāng)一個(gè)URL傳過來時(shí),判斷這個(gè)URL是否在黑名單中缩抡。

如果我們使用HashSet嘴高,可以做到準(zhǔn)確查詢以及查詢的時(shí)間復(fù)雜度為O(1),但是這樣一來哈希表在服務(wù)器內(nèi)存上的消耗將會是巨大的套啤,至少需要320億字節(jié)争占。
布隆過濾器則可以做到減少內(nèi)存壓力握童,快速查詢片效。

首先準(zhǔn)備一個(gè)足夠大的長度為M的int類型的數(shù)組

int[] arr = new int[M];

因?yàn)閕nt類型為4字節(jié)蛮浑,每個(gè)字節(jié)有8位,所以開辟的數(shù)組空間可以存放32M個(gè)bit《旱眨現(xiàn)在準(zhǔn)備K個(gè)哈希函數(shù),對10億個(gè)URL依次求出hashcode并取模


image.png

那么如何判斷hashcode取模后落在bit數(shù)組上的位置呢商源?拿hash1()%M舉例车份,假設(shè)該值為code1

int intIndex = code1 / 32; // 在int數(shù)組上的哪一個(gè)位置
int bitIndex = code1 % 32; // 在對應(yīng)的int數(shù)組上的哪一個(gè)bit位
arr[intIndex] = (arr[intIndex] | (1 << bitIndex));
// 1 << bitIndex 即,除了bitIndex外的bit位都為0牡彻,通過和arr[intIndex]進(jìn)行或運(yùn)算后
// code1在bit數(shù)組上對應(yīng)的位置被“抹黑”(該位置值為1)

除了code1以外扫沼,其他的哈希函數(shù)也在32M長度的bit數(shù)組上分別"抹黑"了相應(yīng)的位置
十億個(gè)URL在K個(gè)哈希函數(shù)下出爹,標(biāo)記的位置變?yōu)?,首先要確定的是M要足夠大缎除,要不然可以試想一下严就,如果32M個(gè)位置幾乎全部抹黑,那么誤差率就會大大提升器罐,另外我們也可以解釋為什么布隆過濾器會產(chǎn)生誤差梢为,產(chǎn)生的誤差類型是有可能為不在黑名單的URL,但是碰巧算出的hashcode都為黑,這樣就會返回true,不過可以確定的是如果是在黑名單的URL那么返回值一定為true,其通過hash函數(shù)后落在bit數(shù)組上的位置必然均為1轰坊,布隆過濾器的誤差率可以形象地說是“寧可錯(cuò)殺一千铸董,絕不放過一人”。
不難看出肴沫,當(dāng)檢驗(yàn)的哈希函數(shù)的個(gè)數(shù)K增加粟害,誤差率必然下降;如果bit數(shù)組的長度增加颤芬,誤差率也必然下降悲幅。

布隆過濾器樣本量n,bit數(shù)組長度m,哈希函數(shù)個(gè)數(shù)k與失誤率p的關(guān)系
image.png

給定樣本量以及預(yù)期失誤率可以推斷出需要開辟多少bit長度的數(shù)組

image.png

計(jì)算出需要開辟多少bit長度的數(shù)組后,可以推斷出需要多少個(gè)hash函數(shù)

image.png

直到m以及k的取值后驻襟,可以重新計(jì)算失誤率夺艰。試想公式一推斷出的m值向上取值芋哭,那么m的數(shù)量增加沉衣,bit數(shù)組長度增加失誤率則會相應(yīng)減少;通過公式二推斷出k值也向上取值减牺,k的數(shù)量增加豌习,哈希函數(shù)校驗(yàn)的個(gè)數(shù)增加失誤率也會相應(yīng)地減少,所以得到的真實(shí)失誤率必然要比預(yù)期失誤率好拔疚。

布隆過濾器的重要應(yīng)用

參考文章:大白話布隆過濾器

一致性哈希

在hash應(yīng)用-海量數(shù)據(jù)分流一節(jié)中:


image.png

我們知道肥隆,當(dāng)發(fā)起大流量的請求時(shí),后端的服務(wù)器負(fù)載均衡稚失,但是也有缺點(diǎn)栋艳,如果某臺服務(wù)器故障,或者需要新增加一臺服務(wù)器的時(shí)候句各,那么所有的請求元都要在重新計(jì)算自己分配到了哪一臺服務(wù)器上吸占。有沒有方法可以優(yōu)化這一點(diǎn)并且還能做到負(fù)載均衡呢?一致性哈希和虛擬節(jié)點(diǎn)技術(shù)就解決了這樣的問題凿宾。

一致性哈希

以MD5舉例矾屯,MD5的S域表示的范圍為[0,2^64],那么就假想一個(gè)環(huán):


image.png

假設(shè)對每臺服務(wù)器的ip求hashcode后分布在環(huán)上的情況如下


image.png

某數(shù)據(jù)經(jīng)過前端的MD5哈希函數(shù)得到的數(shù)值必然落在環(huán)上,從落點(diǎn)開始順時(shí)針走初厚,最近的服務(wù)器則會被分流到件蚕。如果增加服務(wù)器或者減少服務(wù)器:
image.png

如圖所示,新增s4,那么原本在s3的黃色部分?jǐn)?shù)據(jù)就會轉(zhuǎn)到s4上排作,減少服務(wù)器也是同理牵啦,可見不需要太大的犧牲。
每一臺前端服務(wù)器記錄著排好序的后端服務(wù)器hash(ip),當(dāng)有新的數(shù)據(jù)需要分配到后端服務(wù)器妄痪,則在前端服務(wù)器上通過二分法找到大于等于新的數(shù)據(jù)的那個(gè)server蕾久,然后再進(jìn)行分配。
不過拌夏,這種環(huán)結(jié)構(gòu)卻犧牲了傳統(tǒng)哈希分流負(fù)載均衡的優(yōu)點(diǎn)僧著,虛擬節(jié)點(diǎn)則解決了這一問題

虛擬節(jié)點(diǎn)

還是有三臺服務(wù)器,不過現(xiàn)在為每個(gè)服務(wù)器配1000個(gè)虛擬節(jié)點(diǎn)障簿。虛擬節(jié)點(diǎn)負(fù)責(zé)"搶"數(shù)據(jù)盹愚。通過路由表可以知道哪一個(gè)服務(wù)器對應(yīng)著那些虛擬節(jié)點(diǎn)。因?yàn)檫@樣下來平均就是3000個(gè)虛擬節(jié)點(diǎn)站故,在整個(gè)S域上可以做到負(fù)載均衡皆怕,搶數(shù)據(jù)的原理和上面介紹的一樣,順時(shí)針走西篓,遇到最近的虛擬節(jié)點(diǎn)然后通過路由表看該虛擬節(jié)點(diǎn)對應(yīng)的是哪一個(gè)實(shí)體服務(wù)器愈腾,那么數(shù)據(jù)就被分配到哪個(gè)服務(wù)器上。
新增服務(wù)器或者是減少服務(wù)器也是一樣的岂津,如果新增服務(wù)器虱黄,那么就再原有虛擬節(jié)點(diǎn)的基礎(chǔ)上再次增加1000個(gè)虛擬節(jié)點(diǎn),原本散布在別的服務(wù)器上的數(shù)據(jù)吮成,也會通過虛擬節(jié)點(diǎn)移至新的服務(wù)器上橱乱,這樣一來通過一致性哈希不僅可以做到新增或減少服務(wù)器不需要消耗巨大代價(jià),也可以做到服務(wù)器之間負(fù)載均衡粱甫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泳叠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子茶宵,更是在濱河造成了極大的恐慌危纫,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乌庶,死亡現(xiàn)場離奇詭異种蝶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)安拟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門蛤吓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人糠赦,你說我怎么就攤上這事会傲」兀” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵淌山,是天一觀的道長裸燎。 經(jīng)常有香客問我,道長泼疑,這世上最難降的妖魔是什么德绿? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮退渗,結(jié)果婚禮上移稳,老公的妹妹穿的比我還像新娘。我一直安慰自己会油,他們只是感情好个粱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翻翩,像睡著了一般都许。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嫂冻,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天胶征,我揣著相機(jī)與錄音,去河邊找鬼桨仿。 笑死睛低,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蹬敲。 我是一名探鬼主播暇昂,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼莺戒,長吁一口氣:“原來是場噩夢啊……” “哼伴嗡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起从铲,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤瘪校,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后名段,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阱扬,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年伸辟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了麻惶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡信夫,死狀恐怖窃蹋,靈堂內(nèi)的尸體忽然破棺而出卡啰,到底是詐尸還是另有隱情,我是刑警寧澤警没,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布匈辱,位于F島的核電站,受9級特大地震影響杀迹,放射性物質(zhì)發(fā)生泄漏亡脸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一树酪、第九天 我趴在偏房一處隱蔽的房頂上張望浅碾。 院中可真熱鬧,春花似錦续语、人聲如沸及穗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埂陆。三九已至,卻和暖如春娃豹,著一層夾襖步出監(jiān)牢的瞬間焚虱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工懂版, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹃栽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓躯畴,卻偏偏與公主長得像民鼓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子蓬抄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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