WeakHashMap源碼分析

WeakHashMap

WeakHashMap介紹

image.png
java.lang.Object
   ?     java.util.AbstractMap<K, V>
         ?     java.util.WeakHashMap<K, V>

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {}

WeakHashMap繼承于AbstractMap,實(shí)現(xiàn)了Map接口聚凹。和HashMap一樣驰弄,WeakHashMap也是一個(gè)散列表乾闰,它存儲(chǔ)的內(nèi)容也是鍵值對(key-value)映射,而且鍵和值都可以是null疏日。

不過WeakHashMap的鍵是“弱鍵”偿洁。在WeakHashMap中,當(dāng)某個(gè)鍵不再正常使用時(shí)沟优,會(huì)從WeakHashMap中被自動(dòng)移除涕滋。更精確地說,對于一個(gè)給定的鍵挠阁,其映射的存在并不阻止垃圾回收器對該鍵的回收宾肺,這就使該鍵成為可終止的溯饵,被終止,然后被回收锨用。某個(gè)鍵被終止時(shí)丰刊,它對應(yīng)的鍵值對也就從映射中有效地移除了。

和HashMap一樣增拥,WeakHashMap是不同步的啄巧。可以使用 Collections.synchronizedMap方法來構(gòu)造同步的WeakHashMap掌栅。

WeakHashMap的構(gòu)造函數(shù)

  • 默認(rèn)構(gòu)造函數(shù)秩仆,使用默認(rèn)初始化容量:16,默認(rèn)負(fù)載因子:0.75f

    WeakHashMap()

  • 指定“容量大小”的構(gòu)造函數(shù)猾封,使用默認(rèn)負(fù)載因子:0.75f

    WeakHashMap(int capacity)

  • 指定“容量大小”和“負(fù)載因子”的構(gòu)造函數(shù)

    WeakHashMap(int capacity, float loadFactor)

  • 包含“子Map”的構(gòu)造函數(shù)

    WeakHashMap(Map<? extends K, ? extends V> map)

WeakHashMap的屬性

  • Entry<K,V>[] table

table是一個(gè)Entry數(shù)組澄耍,而Entry實(shí)際上就是一個(gè)單向鏈表。哈希表的“key-value鍵值對”都是存儲(chǔ)在Entry數(shù)組中的晌缘。

  • private int size

size是Hashtable的大小齐莲,它是Hashtable保存的鍵值對的數(shù)量。

  • private int threshold

threshold是閾值枚钓,用于判斷是否需要調(diào)整容量铅搓。threshold=“容量*負(fù)載因子”。

  • private final float loadFactor

loadFactor就是負(fù)載因子搀捷,它是衡量哈希表能放多滿的數(shù)值。

  • int modCount

modCount是用來實(shí)現(xiàn)fail-fast機(jī)制的多望,記錄哈希表被修改的次數(shù)嫩舟。

  • private final ReferenceQueue<Object> queue = new ReferenceQueue<>()

引用隊(duì)列:當(dāng)弱引用所引用的對象被垃圾回收器回收時(shí),該弱引用將被垃圾回收器添加到與之關(guān)聯(lián)的引用隊(duì)列中怀偷。

WeakHashMap的關(guān)鍵實(shí)現(xiàn)

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>

Entry繼承自WeakReference(弱引用)家厌,那么Entry本身就是一個(gè)弱引用。

    Entry(Object key, V value,
          ReferenceQueue<Object> queue,
          int hash, Entry<K,V> next) {
        super(key, queue);
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }

從Entry的構(gòu)造函數(shù)中可以看出:Entry通過傳入key和queue調(diào)用了父類WeakReference的構(gòu)造函數(shù)椎工,那么key就成為了這個(gè)弱引用所引用的對象饭于,并把這個(gè)弱引用注冊到了引用隊(duì)列上。

image.png

如果一個(gè)對象只具有弱引用维蒙,那就類似于可有可無的生活用品掰吕。只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中颅痊,一旦發(fā)現(xiàn)了只具有弱引用的對象殖熟,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存斑响。不過菱属,由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程钳榨, 因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對象。 弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用纽门,如果弱引用所引用的對象被垃圾回收薛耻,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

因?yàn)榇鎯?chǔ)在Entry中的key只具有弱引用赏陵,所以并不能阻止垃圾回收線程對它進(jìn)行回收昭卓,當(dāng)發(fā)生垃圾回收時(shí),Entry中的key被回收瘟滨,java虛擬機(jī)就會(huì)把這個(gè)Entry添加到與之關(guān)聯(lián)的queue中去候醒。

通過上面的分析,存儲(chǔ)在WeakHashMap中的key隨時(shí)都會(huì)面臨被回收的風(fēng)險(xiǎn)杂瘸,因此每次查詢WeakHashMap時(shí)倒淫,都要確認(rèn)當(dāng)前WeakHashMap是否已經(jīng)有key被回收了。當(dāng)key被回收時(shí)败玉,引用這個(gè)key的Entry對象就會(huì)被添加到引用隊(duì)列中去敌土,所以只要查詢引用隊(duì)列是否有Entry對象,就可以確認(rèn)是否有key被回收了运翼。WeakHashMap通過調(diào)用expungeStaleEntries方法來清除已經(jīng)被回收的key所關(guān)聯(lián)的Entry對象返干。

private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
                Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, table.length);

            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    // Must not null out e.next;
                    // stale entries may be in use by a HashIterator
                    e.value = null; // Help GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

WeakHashMap在調(diào)用putget方法之前,都會(huì)調(diào)用expungeStaleEntries方法來清除已經(jīng)被回收的key所關(guān)聯(lián)的Entry對象血淌。因?yàn)镋ntry是弱引用矩欠,即使引用著key對象,但是依然不能阻止垃圾回收線程對key對象的回收悠夯。

如果存放在WeakHashMap中的key都存在強(qiáng)引用癌淮,那么WeakHashMap就會(huì)退化成HashMap。如果在系統(tǒng)中希望通過WeakHashMap自動(dòng)清除數(shù)據(jù)沦补,請盡量不要在系統(tǒng)的其他地方強(qiáng)引用WeakHashMap的key乳蓄,否則,這些key就不會(huì)被回收夕膀,WeakHashMap也就無法正常釋放它們所占用的表項(xiàng)虚倒。

案例應(yīng)用

如果在一個(gè)普通的HashMap中存儲(chǔ)一些比較大的值如下:

Map<Integer,Object> map = new HashMap<>();
for(int i=0;i<10000;i++)
{
    Integer ii = new Integer(i);
    map.put(ii, new byte[i]);
}

運(yùn)行參數(shù):-Xmx5M
運(yùn)行結(jié)果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at collections.WeakHashMapTest.main(WeakHashMapTest.java:39)

如果我們將HashMap換成WeakHashMap其余都不變:

Map<Integer,Object> map = new WeakHashMap<>();
for(int i=0;i<10000;i++)
{
    Integer ii = new Integer(i);
    map.put(ii, new byte[i]);
}

運(yùn)行結(jié)果:(無任何報(bào)錯(cuò))

這兩段代碼比較可以看到WeakHashMap的功效,如果在系統(tǒng)中需要一張很大的Map表产舞,Map中的表項(xiàng)作為緩存使用魂奥,這也意味著即使沒能從該Map中取得相應(yīng)的數(shù)據(jù),系統(tǒng)也可以通過候選方案獲取這些數(shù)據(jù)庞瘸。雖然這樣會(huì)消耗更多的時(shí)間捧弃,但是不影響系統(tǒng)的正常運(yùn)行。

在這種場景下,使用WeakHashMap是最合適的违霞。因?yàn)閃eakHashMap會(huì)在系統(tǒng)內(nèi)存范圍內(nèi)嘴办,保存所有表項(xiàng),而一旦內(nèi)存不夠买鸽,在GC時(shí)涧郊,沒有被引用的表項(xiàng)又會(huì)很快被清除掉,從而避免系統(tǒng)內(nèi)存溢出眼五。

我們這里稍微改變一下上面的代碼(加了一個(gè)List):

Map<Integer,Object> map = new WeakHashMap<>();
List<Integer> list = new ArrayList<>();
for(int i=0;i<10000;i++)
{
    Integer ii = new Integer(i);
    list.add(ii);
    map.put(ii, new byte[i]);
}   

運(yùn)行結(jié)果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at collections.WeakHashMapTest.main(WeakHashMapTest.java:43)

如果存放在WeakHashMap中的key都存在強(qiáng)引用妆艘,那么WeakHashMap就會(huì)退化成HashMap。如果在系統(tǒng)中希望通過WeakHashMap自動(dòng)清除數(shù)據(jù)看幼,請盡量不要在系統(tǒng)的其他地方強(qiáng)引用WeakHashMap的key批旺,否則,這些key就不會(huì)被回收诵姜,WeakHashMap也就無法正常釋放它們所占用的表項(xiàng)汽煮。

要想WeakHashMap能夠釋放掉被回收的key關(guān)聯(lián)的value對象,要盡可能的多調(diào)用下put/size/get等操作棚唆,因?yàn)檫@些方法會(huì)調(diào)用expungeStaleEntries方法暇赤,expungeStaleEntries方法是關(guān)鍵,而如果不操作WeakHashMap宵凌,以企圖WeakHashMap“自動(dòng)”釋放內(nèi)存是不可取的鞋囊,這里的“自動(dòng)”是指譬如:map.put(obj, new byte[10M]);之后obj=null了瞎惫,之后再也沒調(diào)用過map的任何方法溜腐,那么new出來的10M空間是不會(huì)釋放的。

注意

WeakHashMap的key可以為null微饥,那么當(dāng)put一個(gè)key為null逗扒,value為一個(gè)很大對象的時(shí)候,這個(gè)很大的對象怎么采用WeakHashMap的自帶功能自動(dòng)釋放呢欠橘?

代碼如下:

Map<Object,Object> map = new WeakHashMap<>();
map.put(null,new byte[5*1024*928]);
int i = 1;
while(true)
{
    System.out.println();
    TimeUnit.SECONDS.sleep(2);
    System.out.println(map.size());
    System.gc();
    System.out.println("==================第"+i+++"次GC結(jié)束====================");
}

運(yùn)行參數(shù):-Xmx5M -XX:+PrintGCDetails
運(yùn)行結(jié)果:

1
[GC [PSYoungGen: 680K->504K(2560K)] 5320K->5240K(7680K), 0.0035741 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 504K->403K(2560K)] [ParOldGen: 4736K->4719K(5120K)] 5240K->5123K(7680K) [PSPermGen: 2518K->2517K(21504K)], 0.0254473 secs] [Times: user=0.06 sys=0.00, real=0.03 secs] 
==================第1次GC結(jié)束====================

1
[Full GC [PSYoungGen: 526K->0K(2560K)] [ParOldGen: 4719K->5112K(5120K)] 5246K->5112K(7680K) [PSPermGen: 2520K->2520K(21504K)], 0.0172785 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
==================第2次GC結(jié)束====================

1
[Full GC [PSYoungGen: 41K->0K(2560K)] [ParOldGen: 5112K->5112K(5120K)] 5153K->5112K(7680K) [PSPermGen: 2520K->2520K(21504K)], 0.0178421 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
==================第3次GC結(jié)束====================

1
[Full GC [PSYoungGen: 41K->0K(2560K)] [ParOldGen: 5112K->5112K(5120K)] 5153K->5112K(7680K) [PSPermGen: 2520K->2520K(21504K)], 0.0164874 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
==================第4次GC結(jié)束====================

1
[Full GC [PSYoungGen: 41K->0K(2560K)] [ParOldGen: 5112K->5112K(5120K)] 5153K->5112K(7680K) [PSPermGen: 2520K->2520K(21504K)], 0.0191096 secs] [Times: user=0.05 sys=0.00, real=0.02 secs] 
==================第5次GC結(jié)束====================
(一直循環(huán)下去)

可以看到在map.put(null, new byte[5*1024*928]);之后,相應(yīng)的內(nèi)存一直沒有得到釋放现恼。

通過顯式的調(diào)用map.remove(null)可以將內(nèi)存釋放掉肃续,如下代碼所示:

Map<Integer,Object> map = new WeakHashMap<>();
System.gc();
System.out.println("===========gc:1=============");
map.put(null,new byte[4*1024*1024]);
TimeUnit.SECONDS.sleep(5);
System.gc();
System.out.println("===========gc:2=============");
TimeUnit.SECONDS.sleep(5);
System.gc();
System.out.println("===========gc:3=============");
map.remove(null);
TimeUnit.SECONDS.sleep(5);
System.gc();
System.out.println("===========gc:4=============");

運(yùn)行參數(shù):-Xmx5M -XX:+PrintGCDetails
運(yùn)行結(jié)果:

[GC [PSYoungGen: 720K->504K(2560K)] 720K->544K(6144K), 0.0023652 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 40K->480K(3584K)] 544K->480K(6144K) [PSPermGen: 2486K->2485K(21504K)], 0.0198023 secs] [Times: user=0.11 sys=0.00, real=0.02 secs] 
===========gc:1=============
[GC [PSYoungGen: 123K->32K(2560K)] 4699K->4608K(7680K), 0.0026722 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 4576K->4578K(5120K)] 4608K->4578K(7680K) [PSPermGen: 2519K->2519K(21504K)], 0.0145734 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
===========gc:2=============
[GC [PSYoungGen: 40K->32K(2560K)] 4619K->4610K(7680K), 0.0013068 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 4578K->4568K(5120K)] 4610K->4568K(7680K) [PSPermGen: 2519K->2519K(21504K)], 0.0189642 secs] [Times: user=0.06 sys=0.00, real=0.02 secs] 
===========gc:3=============
[GC [PSYoungGen: 40K->32K(2560K)] 4609K->4600K(7680K), 0.0011742 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 4568K->472K(5120K)] 4600K->472K(7680K) [PSPermGen: 2519K->2519K(21504K)], 0.0175907 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
===========gc:4=============
Heap
 PSYoungGen      total 2560K, used 82K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd14820,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 5120K, used 472K [0x00000000ff800000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 5120K, 9% used [0x00000000ff800000,0x00000000ff876128,0x00000000ffd00000)
 PSPermGen       total 21504K, used 2526K [0x00000000fa600000, 0x00000000fbb00000, 0x00000000ff800000)
  object space 21504K, 11% used [0x00000000fa600000,0x00000000fa8778f8,0x00000000fbb00000)

分析:

1、在WeakHashMap中叉袍,put的key為null時(shí)始锚,放入的是NULL_KEY,即:private static final Object NULL_KEY = new Object()喳逛,是一個(gè)靜態(tài)常量瞧捌。
2、在WeakHashMap中,由于傳給WeakReference的只有key和queue姐呐,即gc只回收里面的KEY殿怜,而不會(huì)動(dòng)value,value的清除則是在expungeStaleEntries這個(gè)私有方法進(jìn)行的曙砂。
3头谜、而static的就不在gc之列,所以key也就不會(huì)被gc鸠澈,所以它的大值value柱告,也就不會(huì)被設(shè)為null,不會(huì)被回收笑陈。
4际度、通過調(diào)用remove方法,最終table[k]設(shè)為null涵妥,此時(shí)大對象游離所以被回收乖菱。

只有通過remove方法才能刪除null鍵所關(guān)聯(lián)的value,建議在使用WeakHashMap的時(shí)候盡量避免使用null作為鍵妹笆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末块请,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拳缠,更是在濱河造成了極大的恐慌墩新,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窟坐,死亡現(xiàn)場離奇詭異海渊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)哲鸳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門臣疑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人徙菠,你說我怎么就攤上這事讯沈。” “怎么了婿奔?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵缺狠,是天一觀的道長。 經(jīng)常有香客問我萍摊,道長挤茄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任冰木,我火速辦了婚禮穷劈,結(jié)果婚禮上笼恰,老公的妹妹穿的比我還像新娘。我一直安慰自己歇终,他們只是感情好社证,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著练湿,像睡著了一般猴仑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肥哎,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天辽俗,我揣著相機(jī)與錄音,去河邊找鬼篡诽。 笑死崖飘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的杈女。 我是一名探鬼主播朱浴,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼达椰!你這毒婦竟也來了翰蠢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對情侶失蹤啰劲,失蹤者是張志新(化名)和其女友劉穎梁沧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝇裤,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廷支,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了栓辜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恋拍。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖藕甩,靈堂內(nèi)的尸體忽然破棺而出施敢,到底是詐尸還是另有隱情,我是刑警寧澤狭莱,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布悯姊,位于F島的核電站,受9級(jí)特大地震影響贩毕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仆嗦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一辉阶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦谆甜、人聲如沸垃僚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谆棺。三九已至,卻和暖如春罕袋,著一層夾襖步出監(jiān)牢的瞬間改淑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工浴讯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朵夏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓榆纽,卻偏偏與公主長得像仰猖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子奈籽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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

  • 一衣屏、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對于byte類型而言...
    龍貓小爺閱讀 4,254評(píng)論 0 16
  • 概述 ??在學(xué)習(xí)WeakHashMap之前躏升,先簡單來說一下Java中的4種引用類型,它們分別是:強(qiáng)引用(Stron...
    騎著烏龜去看海閱讀 734評(píng)論 2 5
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法勾拉,類相關(guān)的語法煮甥,內(nèi)部類的語法,繼承相關(guān)的語法藕赞,異常的語法成肘,線程的語...
    子非魚_t_閱讀 31,587評(píng)論 18 399
  • 人言滴滴双霍,必言“顛覆”、“共享經(jīng)濟(jì)”批销,必言“估值”洒闸、“融資”。但這些惹人眼球的字眼不能讓一家公司變得卓越和偉大均芽。喧...
    會(huì)飛的駱駝閱讀 335評(píng)論 0 0
  • 世界在哪里 我們執(zhí)著去尋覓 世界在這里 我們的心在一起 我們是命運(yùn)共同體 我們的心永遠(yuǎn)在一起 太行山在哪里 我們千...
    銀河謎米閱讀 267評(píng)論 0 0