HashMap jdk1.8版 特性講解

? ? ? ? 大家好,我是IT修真院北京分院第31期的學(xué)員郭计,一枚正直純潔善良的JAVA程序員椒振。今天給大家分享一下杠人,HashMap jdk1.8版 特性講解.

1.背景介紹

什么是HASHMAP?

HashMap是基于哈希表的Map接口的實(shí)現(xiàn),存儲的是鍵值對,并允許使用null鍵和null值.HashMap是非Synchronized,即是線程不安全的.如何要滿足線程安全可以使用ConcurrentHashMap.HashMap根據(jù)鍵的hashCode值存儲數(shù)據(jù),大多數(shù)情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序是不確定的.

哈希表及MAP接口

哈希表(也叫散列表),是根據(jù)關(guān)鍵碼值(key value)而直接進(jìn)行訪問的數(shù)據(jù)結(jié)構(gòu).

也就是說,它通過把關(guān)鍵碼值(key value)映射到表中一個(gè)位置來訪問記錄,以加快查找的速度.這個(gè)映射函數(shù)叫做散列函數(shù),存放記錄的數(shù)組叫做散列表.

Map主要用于存儲鍵值對.Map的三個(gè)特點(diǎn):1,包含鍵值對;2,鍵唯一;3,鍵對應(yīng)的值唯一.Map還提供了3個(gè)集合視圖,分別是一組鍵值對,一組鍵,一組值.

MAP接口主要常用的實(shí)現(xiàn)類

Map接口主要有四個(gè)常用的實(shí)用類,分別是HashMap,Hashtable,LinkedHashMap和TreeMap.類繼承關(guān)系如下圖所示:

HASHTABLE

Hashtable是遺留類,很多映射的常用功能與HashMap類似,不同的是它繼承自Dictionary類,并且是線程安全的,任一時(shí)間只有一個(gè)線程能寫Hashtable.Hashtable不建議在新代碼中使用,不需要線程安全的場合可以用HashMap替換,需要線程安全的場合可以用ConcurrentHashMap替換.

哈希表及Map接口 哈希表(也叫散列表),是根據(jù)關(guān)鍵碼值(key value)而直接進(jìn)行訪問的數(shù)據(jù)結(jié)構(gòu). 也就是說,它通過把關(guān)鍵碼值(key value)映射到表中一個(gè)位置來訪問記錄,以加快查找的速度.這個(gè)映射函數(shù)叫做散列函數(shù),存放記錄的數(shù)組叫做散列表. Map主要用于存儲鍵值對.Map的三個(gè)特點(diǎn):1,包含鍵值對;2,鍵唯一;3,鍵對應(yīng)的值唯一.Map還提供了3個(gè)集合視圖,分別是一組鍵值對,一組鍵,一組值.

MAP接口主要常用的實(shí)現(xiàn)類

Map接口主要有四個(gè)常用的實(shí)用類,分別是HashMap,Hashtable,LinkedHashMap和TreeMap.類繼承關(guān)系如下圖所示:

HASHTABLE

Hashtable是遺留類,很多映射的常用功能與HashMap類似,不同的是它繼承自Dictionary類,并且是線程安全的,任一時(shí)間只有一個(gè)線程能寫Hashtable.Hashtable不建議在新代碼中使用,不需要線程安全的場合可以用HashMap替換,需要線程安全的場合可以用ConcurrentHashMap替換.

LINKEDHASHMAP

LinkedHashMap是HashMap的一個(gè)子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時(shí),先得到的記錄肯定是先插入的,也可以在構(gòu)造時(shí)帶參數(shù),按照訪問次序排序.

Map接口主要常用的實(shí)現(xiàn)類 Map接口主要有四個(gè)常用的實(shí)用類,分別是HashMap,Hashtable,LinkedHashMap和TreeMap.類繼承關(guān)系如下圖所示:

HASHTABLE

Hashtable是遺留類,很多映射的常用功能與HashMap類似,不同的是它繼承自Dictionary類,并且是線程安全的,任一時(shí)間只有一個(gè)線程能寫Hashtable.Hashtable不建議在新代碼中使用,不需要線程安全的場合可以用HashMap替換,需要線程安全的場合可以用ConcurrentHashMap替換.

LINKEDHASHMAP

LinkedHashMap是HashMap的一個(gè)子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時(shí),先得到的記錄肯定是先插入的,也可以在構(gòu)造時(shí)帶參數(shù),按照訪問次序排序.

TREEMAP

TreeMap實(shí)現(xiàn)SortedMap接口辑莫,能夠把它保存的記錄根據(jù)鍵排序各吨,默認(rèn)是按鍵值的升序排序袁铐,也可以指定排序的比較器剔桨,當(dāng)用Iterator遍歷TreeMap時(shí),得到的記錄是排過序的瑰谜。如果使用排序的映射,建議使用TreeMap隐轩。在使用TreeMap時(shí)职车,key必須實(shí)現(xiàn)Comparable接口或者在構(gòu)造TreeMap傳入自定義的Comparator鹊杖,否則會在運(yùn)行時(shí)拋出java.lang.ClassCastException類型的異常仅淑。

Hashtable Hashtable是遺留類,很多映射的常用功能與HashMap類似,不同的是它繼承自Dictionary類,并且是線程安全的,任一時(shí)間只有一個(gè)線程能寫Hashtable.Hashtable不建議在新代碼中使用,不需要線程安全的場合可以用HashMap替換,需要線程安全的場合可以用ConcurrentHashMap替換.

2.知識剖析

JAVA 8系列之重新認(rèn)識HASHMAP

Java1.8對HashMap底層的實(shí)現(xiàn)進(jìn)行了優(yōu)化,例如紅黑樹的數(shù)據(jù)結(jié)構(gòu)和擴(kuò)容的優(yōu)化等,接下來我們將深入探討HashMap 的結(jié)構(gòu)實(shí)現(xiàn)和功能原理.

結(jié)構(gòu)實(shí)現(xiàn)

從結(jié)構(gòu)實(shí)現(xiàn)來講,HashMap是數(shù)組+鏈表+紅黑樹(JDK1.8增加了紅黑樹部分)實(shí)現(xiàn)的,如下圖所示.?

這里需要講明白兩個(gè)問題:數(shù)據(jù)底層具體存儲的是什么?這樣的存儲方法有什么優(yōu)點(diǎn)?

(1)HashMap類中有一個(gè)非常重要的字段,就是Node[]table,即哈希桶數(shù)組,明顯它是一個(gè)Node的數(shù)組.Node就是HashMap的一個(gè)內(nèi)部類,實(shí)現(xiàn)了Map.Entry接口,本質(zhì)就是一個(gè)映射(鍵值對).

(2)HashMap就是使用哈希表來存儲的.在上面已經(jīng)介紹了鍵值對是通過散列函數(shù)(哈希函數(shù))映射到哈希表的地址上的,而所有的散列函數(shù)都有如下一個(gè)基本特征:根據(jù)同一散列函數(shù)計(jì)算出來的散列值不同,那么輸入的信息肯定也不同.但是,根據(jù)同一散列函數(shù)計(jì)算出的散列值如果相同,輸入值不一定相同.

這句話的意思就是輸入不同的兩個(gè)值可能得到一樣的散列值.這種現(xiàn)象叫做碰撞.Java中HashMap為了解決沖突采用鏈表地址法.鏈表地址法:將哈希表的每個(gè)單元作為鏈表的頭結(jié)點(diǎn)涯竟,所有哈希地址為i的元素構(gòu)成一個(gè)同義詞鏈表庐船。即發(fā)生沖突時(shí)就把該關(guān)鍵字鏈在以該單元為頭結(jié)點(diǎn)的鏈表的尾部嘲更。

舉個(gè)例子,例如程序執(zhí)行下面的代碼:map.put("美團(tuán)","小小");系統(tǒng)將調(diào)用"美團(tuán)"這個(gè)key的hashCode()方法得到其hashCode 值(該方法適用于每個(gè)Java對象)赋朦,然后再通過Hash算法的后兩步運(yùn)算(高位運(yùn)算和取模運(yùn)算,下文有介紹)來定位該鍵值對的存儲位置壹将,有時(shí)兩個(gè)key會定位到相同的位置毛嫉,表示發(fā)生了Hash碰撞承粤。當(dāng)然Hash算法計(jì)算結(jié)果越分散均勻,Hash碰撞的概率就越小仙粱。如果哈希桶數(shù)組很大缰盏,即使較差的Hash算法也會比較分散,所以就需要在空間成本和時(shí)間成本之間權(quán)衡.那么通過什么方式來控制map使得Hash碰撞的概率又小负溪,哈希桶數(shù)組(Node[] table)占用空間又少呢济炎?答案就是好的Hash算法和擴(kuò)容機(jī)制须尚。

在理解Hash和擴(kuò)容流程之前耐床,我們得先了解下HashMap的幾個(gè)字段。從HashMap的默認(rèn)構(gòu)造函數(shù)源碼可知胯甩,構(gòu)造函數(shù)就是對下面幾個(gè)字段進(jìn)行初始化堪嫂,源碼如下: int threshold; // 所能容納的key-value對極限 final float loadFactor; // 負(fù)載因子 int modCount; int size;

首先皆串,Node[] table的初始化長度length(默認(rèn)值是16),Loadfactor為負(fù)載因子(默認(rèn)值是0.75)怜森,threshold是HashMap所能容納的最大數(shù)據(jù)量的Node(鍵值對)個(gè)數(shù)谤牡。threshold = length * Load factor拓哟。也就是說断序,在數(shù)組定義好長度之后,負(fù)載因子越大漱凝,所能容納的鍵值對個(gè)數(shù)越多诸迟。結(jié)合負(fù)載因子的定義公式可知,threshold就是在此Load factor和length(數(shù)組長度)對應(yīng)下允許的最大元素?cái)?shù)目感论,超過這個(gè)數(shù)目 就重新resize(擴(kuò)容)紊册,擴(kuò)容后的HashMap容量是之前容量的兩倍。

舉個(gè)例子,例如程序執(zhí)行下面的代碼:map.put("美團(tuán)","小小");系統(tǒng)將調(diào)用"美團(tuán)"這個(gè)key的hashCode()方法得到其hashCode 值(該方法適用于每個(gè)Java對象)囊陡,然后再通過Hash算法的后兩步運(yùn)算(高位運(yùn)算和取模運(yùn)算芳绩,下文有介紹)來定位該鍵值對的存儲位置,有時(shí)兩個(gè)key會定位到相同的位置撞反,表示發(fā)生了Hash碰撞妥色。當(dāng)然Hash算法計(jì)算結(jié)果越分散均勻,Hash碰撞的概率就越小遏片。如果哈希桶數(shù)組很大嘹害,即使較差的Hash算法也會比較分散,所以就需要在空間成本和時(shí)間成本之間權(quán)衡.那么通過什么方式來控制map使得Hash碰撞的概率又小丁稀,哈希桶數(shù)組(Node[] table)占用空間又少呢吼拥?答案就是好的Hash算法和擴(kuò)容機(jī)制齿拂。

在理解Hash和擴(kuò)容流程之前菩暗,我們得先了解下HashMap的幾個(gè)字段。從HashMap的默認(rèn)構(gòu)造函數(shù)源碼可知授账,構(gòu)造函數(shù)就是對下面幾個(gè)字段進(jìn)行初始化,源碼如下: int threshold; // 所能容納的key-value對極限 final float loadFactor; // 負(fù)載因子 int modCount; int size;

首先惨驶,Node[] table的初始化長度length(默認(rèn)值是16)白热,Loadfactor為負(fù)載因子(默認(rèn)值是0.75),threshold是HashMap所能容納的最大數(shù)據(jù)量的Node(鍵值對)個(gè)數(shù)粗卜。threshold = length * Load factor屋确。也就是說,在數(shù)組定義好長度之后续扔,負(fù)載因子越大攻臀,所能容納的鍵值對個(gè)數(shù)越多。結(jié)合負(fù)載因子的定義公式可知纱昧,threshold就是在此Load factor和length(數(shù)組長度)對應(yīng)下允許的最大元素?cái)?shù)目刨啸,超過這個(gè)數(shù)目 就重新resize(擴(kuò)容),擴(kuò)容后的HashMap容量是之前容量的兩倍识脆。

默認(rèn)的負(fù)載因子0.75是對空間和時(shí)間效率的一個(gè)平衡選擇设联,建議大家不要修改善已,除非在時(shí)間和空間比較特殊的情況下,如果內(nèi)存空間很多而又對時(shí)間效率要求很高离例,可以降低負(fù)載因子Load factor的值换团;相反,如果內(nèi)存空間緊張而對時(shí)間效率要求不高宫蛆,可以增加負(fù)載因子loadFactor的值啥寇,這個(gè)值可以大于1。

size這個(gè)字段其實(shí)很好理解洒扎,就是HashMap中實(shí)際存在的鍵值對數(shù)量辑甜。modCount字段主要用來記錄HashMap內(nèi)部結(jié)構(gòu)發(fā)生變化的次數(shù),例如put新鍵值對袍冷,但是某個(gè)key對應(yīng)的value值被覆蓋不屬于結(jié)構(gòu)變化磷醋。在HashMap中,哈希桶數(shù)組table的長度length大小必須為2的n次方(一定是合數(shù))胡诗,HashMap采用這種非常規(guī)設(shè)計(jì)邓线,主要是為了在取模和擴(kuò)容時(shí)做優(yōu)化,同時(shí)為了減少沖突煌恢,HashMap定位哈希桶索引位置時(shí)骇陈,也加入了高位參與運(yùn)算的過程。

這里存在一個(gè)問題瑰抵,即使負(fù)載因子和Hash算法設(shè)計(jì)的再合理你雌,也免不了會出現(xiàn)拉鏈過長的情況,一旦出現(xiàn)拉鏈過長二汛,則會嚴(yán)重影響HashMap的性能婿崭。于是,在JDK1.8版本中肴颊,對數(shù)據(jù)結(jié)構(gòu)做了進(jìn)一步的優(yōu)化氓栈,引入了紅黑樹。而當(dāng)鏈表長度太長(默認(rèn)超過8)時(shí)婿着,鏈表就轉(zhuǎn)換為紅黑樹授瘦,利用紅黑樹快速增刪改查的特點(diǎn)提高HashMap的性能.

功能實(shí)現(xiàn)-方法

接下來我將結(jié)合HashCode的源碼來為大家講解以下三個(gè)具有代表性的功能點(diǎn):

1, 確定哈希桶數(shù)組索引位置; 2, 分析HashMap的put方法; 3,擴(kuò)容機(jī)制.

size這個(gè)字段其實(shí)很好理解,就是HashMap中實(shí)際存在的鍵值對數(shù)量竟宋。modCount字段主要用來記錄HashMap內(nèi)部結(jié)構(gòu)發(fā)生變化的次數(shù)提完,例如put新鍵值對,但是某個(gè)key對應(yīng)的value值被覆蓋不屬于結(jié)構(gòu)變化袜硫。在HashMap中氯葬,哈希桶數(shù)組table的長度length大小必須為2的n次方(一定是合數(shù)),HashMap采用這種非常規(guī)設(shè)計(jì)婉陷,主要是為了在取模和擴(kuò)容時(shí)做優(yōu)化帚称,同時(shí)為了減少沖突官研,HashMap定位哈希桶索引位置時(shí),也加入了高位參與運(yùn)算的過程闯睹。

這里存在一個(gè)問題戏羽,即使負(fù)載因子和Hash算法設(shè)計(jì)的再合理,也免不了會出現(xiàn)拉鏈過長的情況楼吃,一旦出現(xiàn)拉鏈過長始花,則會嚴(yán)重影響HashMap的性能。于是孩锡,在JDK1.8版本中酷宵,對數(shù)據(jù)結(jié)構(gòu)做了進(jìn)一步的優(yōu)化,引入了紅黑樹躬窜。而當(dāng)鏈表長度太長(默認(rèn)超過8)時(shí)浇垦,鏈表就轉(zhuǎn)換為紅黑樹,利用紅黑樹快速增刪改查的特點(diǎn)提高HashMap的性能.


3.編碼實(shí)戰(zhàn)

4.常見問題

一,為什么STRING, INTERGER這樣的WRAPPER類適合作為鍵荣挨?

String, Interger這樣的wrapper類作為HashMap的鍵是再適合不過了男韧,而且String最為常用。因?yàn)镾tring是不可變的默垄,也是final的此虑,而且已經(jīng)重寫了equals()和hashCode()方法了。其他的wrapper類也有這個(gè)特點(diǎn)口锭。不可變性是必要的朦前,因?yàn)闉榱艘?jì)算hashCode(),就要防止鍵值改變讹弯,如果鍵值在放入時(shí)和獲取時(shí)返回不同的hashcode的話况既,那么就不能從HashMap中找到你想要的對象这溅。因?yàn)楂@取對象的時(shí)候要用到equals()和hashCode()方法组民,那么鍵對象正確的重寫這兩個(gè)方法是非常重要的。如果兩個(gè)不相等的對象返回不同的hashcode的話悲靴,那么碰撞的幾率就會小些臭胜,這樣就能提高HashMap的性能。

二,我們可以使用自定義的對象作為鍵嗎癞尚?

這是前一個(gè)問題的延伸耸三。當(dāng)然你可能使用任何對象作為鍵,只要它遵守了equals()和hashCode()方法的定義規(guī)則浇揩,并且當(dāng)對象插入到Map中之后將不會再改變了仪壮。如果這個(gè)自定義對象時(shí)不可變的,那么它已經(jīng)滿足了作為鍵的條件胳徽,因?yàn)楫?dāng)它創(chuàng)建之后就已經(jīng)不能改變了积锅。

三,我們可以使用COCURRENTHASHMAP來代替HASHTABLE嗎爽彤?

這是另外一個(gè)很熱門的面試題,因?yàn)镃oncurrentHashMap越來越多人用了缚陷。我們知道Hashtable是synchronized的适篙,但是ConcurrentHashMap同步性能更好,因?yàn)樗鼉H僅根據(jù)同步級別對map的一部分進(jìn)行上鎖箫爷。ConcurrentHashMap當(dāng)然可以代替HashTable嚷节,但是HashTable提供更強(qiáng)的線程安全性。

5.參考文獻(xiàn)

https://blog.csdn.net/abcdad/article/details/64123291

https://zhuanlan.zhihu.com/p/21673805

https://blog.csdn.net/v_july_v/article/details/6105630

6.更多討論


.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虎锚,一起剝皮案震驚了整個(gè)濱河市硫痰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌窜护,老刑警劉巖碍论,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異柄慰,居然都是意外死亡鳍悠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門坐搔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來藏研,“玉大人,你說我怎么就攤上這事概行〈赖玻” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵凳忙,是天一觀的道長业踏。 經(jīng)常有香客問我,道長涧卵,這世上最難降的妖魔是什么勤家? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮柳恐,結(jié)果婚禮上伐脖,老公的妹妹穿的比我還像新娘。我一直安慰自己乐设,他們只是感情好讼庇,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著近尚,像睡著了一般蠕啄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上戈锻,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天歼跟,我揣著相機(jī)與錄音却嗡,去河邊找鬼。 笑死嘹承,一個(gè)胖子當(dāng)著我的面吹牛窗价,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叹卷,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼撼港,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骤竹?” 一聲冷哼從身側(cè)響起帝牡,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蒙揣,沒想到半個(gè)月后靶溜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懒震,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年罩息,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片个扰。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瓷炮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出递宅,到底是詐尸還是另有隱情娘香,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布办龄,位于F島的核電站烘绽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏俐填。R本人自食惡果不足惜安接,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玷禽。 院中可真熱鬧赫段,春花似錦、人聲如沸矢赁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撩银。三九已至,卻和暖如春豺憔,著一層夾襖步出監(jiān)牢的瞬間额获,已是汗流浹背够庙。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抄邀,地道東北人耘眨。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像境肾,于是被迫代替她去往敵國和親剔难。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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

  • 摘要 HashMap是Java程序員使用頻率最高的用于映射(鍵值對)處理的數(shù)據(jù)類型奥喻。隨著JDK(Java Deve...
    周二倩你一生閱讀 1,243評論 0 5
  • Java8張圖 11偶宫、字符串不變性 12、equals()方法环鲤、hashCode()方法的區(qū)別 13纯趋、...
    Miley_MOJIE閱讀 3,693評論 0 11
  • HashMap 是 Java 面試必考的知識點(diǎn),面試官從這個(gè)小知識點(diǎn)就可以了解我們對 Java 基礎(chǔ)的掌握程度冷离。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,657評論 9 107
  • 今日上課吵冒,總是心不在焉。 “你算什么西剥,你在這兒瞎BB什么桦锄?”我不敢相信這是我一個(gè)最好的的舍友說出來的話,四年的友情...
    空谷幽人閱讀 205評論 0 0
  • 家公家的門口還有一棵梔子花蔫耽,梔子花本不為樹结耀,但這棵梔子花真的很大,我一直覺得它是棵樹匙铡,枝葉繁茂图甜, 葉子四季濃綠。梔...
    元元閱讀 335評論 3 7