String常見的面試題之String、StringBuilder选浑、StringBuffer的區(qū)別是什么

前情提要

?不管你是入行多年的老碼農(nóng)蓝厌,或者是涉世未深的小白菜,都一定會被人問過下面這樣問題古徒。

String拓提、StringBuilder、StringBuffer的區(qū)別是什么隧膘?

?上面的問題代态,僅僅要求作答還是很簡單的寺惫。但當面試官接著你的答案往底層問,這個問題就復(fù)雜了蹦疑。一般遇到這樣的情況西雀,五橋先生都會大聲的質(zhì)問面試官:下一題吧...

情景模擬

?我們來模擬一下這個面試的場景:假定小東是面試官,小西是應(yīng)聘者歉摧。

小東:你好蒋搜,小西是吧?請做一個簡短的自我介紹吧判莉。
小西:您好豆挽,我叫小西,今年18歲券盅,來自...

?哎帮哈?WK,誰TM給我丟磚頭锰镀?

不知名的觀眾A:誰讓你扯這些的娘侍?直奔主題行不。
不知名的觀眾B:對啊泳炉,直接從這題開始不就好了憾筏,扯那么遠干嘛?
......

?不好意思花鹅,剛剛發(fā)生了一些小插曲氧腰,現(xiàn)場有幾名觀眾正在被送往醫(yī)院的途中...呃,應(yīng)廣大觀眾朋友們的要求刨肃,咱們直接從這個問題開始古拴。前面的那些精彩的場景咱們就不得不跳過了。

...此處忽略前面的一堆細節(jié)...
...
小西:一般咱們會從三個方面去對他們進行比較真友。
?可變性:String類中使用final關(guān)鍵字修飾黄痪,所以String對象是不可變的;而而StringBuilder與StringBuffer都繼承自AbstractStringBuilder類盔然,沒有用final關(guān)鍵字修飾桅打,所以這兩種對象都是可變的;
?線程安全性:因為String中的對象是不可變的愈案,所以線程安全挺尾,StringBuffer對方法加了同步鎖或者對調(diào)用的方法加了同步鎖,所以是線程安全的刻帚,StringBuilder并沒有對方法進行加同步鎖潦嘶,所以是線程不安全;
?性能方面:每次改變String類型的對象時,都會生成一個新的String對象掂僵。而StringBuffer和StringBuilder都是基于源對象的更改航厚,所以在大量字符串拼接的時候性能都比String高很多,由于StringBuffer實現(xiàn)了線程安全锰蓬,在性能上略低于StringBuilder幔睬。

?話不多說,直接模擬面試官的靈魂追問環(huán)節(jié)芹扭。

  1. 那如何判定兩個String類型的對象是同一個對象麻顶,如何判定值相等?
  2. 我可以寫一個自定義的class舱卡,繼承自String類嗎辅肾?我可以自己寫一個和String同名的class,并用在程序里嗎轮锥?
  3. String str = new String("abc");上面的代碼創(chuàng)建了幾個對象矫钓,為什么?
  4. String是包裝類型還是基本類型舍杜?基本類型和包裝類型有什么區(qū)別新娜?Integer包裝類有對應(yīng)的int基本類型,為什么String沒有對應(yīng)的基本類型呢既绩?
  5. 為什么我們總是習慣于用String類型作為HashMap的key概龄,為什么不用其他的Object類型?
  6. ......

?上面的這些問題有的還是比較簡單的饲握,有的問題考察的知識點比較復(fù)雜私杜,如果對相應(yīng)的知識點沒有基本的理解,確實難以作答互拾。
?本章重點內(nèi)容是關(guān)于String歪今,所以對StringBuilder和StringBuffer不會做過多的介紹。如果有需要颜矿,會在后面的章節(jié)補充StringBuilder和StringBuffer的相關(guān)內(nèi)容。

?請注意嫉晶,該文檔中所涉及到的代碼都是基于jdk1.8來說的骑疆。

簡單聊一下String類型

String到底是啥?

?在官方API文檔上看見關(guān)于String的部分描述是這樣的:

String類代表字符串替废。 Java程序中的所有字符串文字(例如"abc" )都被實現(xiàn)為此類的實例箍铭。
字符串不變性: 它們的值在創(chuàng)建后不能被更改。
字符串緩沖區(qū)支持可變字符串椎镣。因為String對象是不可變的诈火,它們可以被共享。

關(guān)于String的字符串不可變性

?如果有翻過String源碼的朋友状答,應(yīng)該都知道String其實是一個基于字符數(shù)組實現(xiàn)的數(shù)據(jù)結(jié)構(gòu)冷守,在char[]的定義上加了final關(guān)鍵字刀崖,表示其是一個常量。

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
        ...
    }

?“字符數(shù)組”沒啥好說的拍摇,final這個關(guān)鍵字簡單解釋下亮钦。

final關(guān)鍵字的字面意思是最終的,貼近不能被更改的意思充活;在Java中蜂莉,可用來修飾成員變量、局部變量混卵、方法映穗、類;

  1. 在修飾變量時幕随,表示該變量一旦被賦值就不能被修改男公;
  2. 修飾方法時,該方法不能被重寫合陵;
  3. 修飾類時枢赔,該類不能被繼承;

?在后面拥知,會單獨拿一個章節(jié)的內(nèi)容來談這個final關(guān)鍵字踏拜。關(guān)于final關(guān)鍵字參見:此處應(yīng)該插眼。
?回到正題中低剔,從上面的內(nèi)容可以看出:final關(guān)鍵字保證了char數(shù)組一旦被賦值后速梗,引用指向就不能被更改。如下:

       final char[] value = {'a','b','c'};
       char[] another = {'d','e','f'};
       // 編譯錯誤襟齿,會提示:Cannot assign a value to final variable 'value'
       value = another;

?事實上姻锁,String的不可變性并不僅僅只是因為String類持有的value對象由final修飾。String的不可變性是因為以下幾個條件共同保證的:

第一猜欺,value[]被final修飾位隶,在賦值后不可變;
第二开皿,String的value[]未對外界提供任何修改數(shù)組值的方法實現(xiàn)涧黄;
第三,String類本身也被final所修飾赋荆,所以String不可以被任何類繼承笋妥;

?String類的不可變性的關(guān)鍵點依托于上面三個條件,缺一不可窄潭。我們常常會忘記其中的第二個必要條件春宣,其實這個很重要。舉個例子說明一下第二個條件是如何保證不可變性的:

        final char[] value = {'a','b','c'};
        // 此處,只要我們能拿到char[]這個對象月帝,我們就可以輕松的改變Array里的元素
        value[2] = 'd';
        System.out.println(value);

?所以躏惋,只要我們能拿到String類持有的char數(shù)組,就意味著我們可以隨意更改里面的內(nèi)容嫁赏,這樣就沒有辦法保證String的不可變性其掂。所以,String類在設(shè)計的時候潦蝇,將char[]對象限定為private款熬,并且不給外部提供任何可以操作到char[]的實現(xiàn)。

關(guān)于String的緩沖特性

?String類在工作中中被大量用到攘乒,為了提高效率贤牛,引入了String的緩沖區(qū)。引入緩沖區(qū)的背景意義和具體細節(jié)就不展開了则酝,這也不是今天的重點殉簸,直接看實現(xiàn)的結(jié)果。

一個對象的創(chuàng)建過程

?我們在創(chuàng)建一個對象(包括自定義數(shù)據(jù)類型)的時候沽讹,大概有以下幾個步驟般卑。

1.嘗試在棧上分配內(nèi)容;(這部分可以忽略爽雄,里面的水很深蝠检,有興趣的可以研究下方法逃逸原理)
2.在堆上分配一塊足夠大內(nèi)存;
3.設(shè)置對象頭挚瘟、初始化零值叹谁、對齊填充;(這部分也可以忽略)
4.執(zhí)行相應(yīng)的構(gòu)造方法乘盖,按照用戶的意愿初始化對象信息焰檩;(到這里,一個對象才基本創(chuàng)建完成)
5.將我們定義的變量指向剛創(chuàng)建對象的引用地址订框;
Tips:這部分內(nèi)容在虛擬機相關(guān)的內(nèi)容里會詳細說明析苫,本章節(jié)重點關(guān)心2和5兩點即可。
由于效率方面的考慮布蔗,基本數(shù)據(jù)類型基本上都是在棧上面分配內(nèi)存藤违。

?以上是對象創(chuàng)建的基本流程,但String的實現(xiàn)并不只是上面這個簡單的步驟纵揍,在jvm里面提供了一個專門用于String的擴展區(qū)域——字符串常量池。
?搞個例子看一下:

     // 直接常量賦值
     String a1 = "五橋";
     String a1_1 = "五橋";
     String a2 = "先生";
     // 常量相加賦值
     String b1 = "五橋先生";
     String b2 = "五橋" + "先生";
     // 變量相加賦值
     String c = a1 + a2;
     // new
     String d1 = new String("五橋");
     String d1_1 = new String("五橋");
     String d2 = new String(a1);
     String d3 = new String(a1 + a2);
     System.out.println("(1)...a1==a1_1? "+(a1==a1_1));
     System.out.println("(2)...d1==d1_1? "+(d1==d1_1));
     System.out.println("(3)...b2=='五橋先生'? "+(b2=="五橋先生"));
     System.out.println("(4)...b2==c'? "+(b2==c));
     System.out.println("(5)...d1==d2'? "+(d1==d2));
     System.out.println("(6)...d3=='五橋先生'? "+(d3=="五橋先生"));
     System.out.println("(7)...d3==b2? "+(d3==b2));
     System.out.println("(8)...b1==b2? "+(b1==b2));
     System.out.println("(9)...b1==c? "+(b1==c));

?針對上面的三種創(chuàng)建方式议街,分別簡單看一下創(chuàng)建這些對象過程是怎么樣的泽谨。

(一)new一個String對象:
1.先在字符串常量池中找(通過equals方法)對應(yīng)的值,如果找到了,進入第2步吧雹,找不到則在字符串常量池中創(chuàng)建一個對象骨杂;
2.會在堆棧中產(chǎn)生一個新的對象(只要有new,就會有新對象的產(chǎn)生)雄卷,然后把引用地址指向字符串常量池中相應(yīng)值的地址搓蚪;
(二)直接常量賦值的方式:
1.先在字符串常量池中找對應(yīng)的值,如果找到了丁鹉,進入第2步妒潭,找不到則在字符串常量池中創(chuàng)建一個對象;
2.返回字符串常量池中相應(yīng)值的地址引用揣钦;
(三)變量相加賦值的方式雳灾,返回對象:
1.直接在堆棧中創(chuàng)建一個新的對象,返回該對象的引用地址冯凹;

?根據(jù)上面的這幾條簡述結(jié)合上面的代碼谎亩,我們大致分析一下代碼段中每一條語句到底要干什么事。

    // 直接常量賦值
    // 常量池中創(chuàng)建一個'五橋'的對象宇姚,返回這個對象的引用地址給a1
    String a1 = "五橋";
    // 常量池中找到了有一個叫'五橋'的對象匈庭,返回這個對象的引用地址給a1_1
    String a1_1 = "五橋";
    // 常量池中創(chuàng)建一個'先生'的對象,返回這個對象的引用地址給a2
    String a2 = "先生";

    // 常量相加賦值
    // 常量池中創(chuàng)建一個'五橋先生'的對象浑劳,返回這個對象的引用地址給b1
    String b1 = "五橋先生";
    // 常量直接相加阱持,jvm會幫忙優(yōu)化成一個常量——'五橋先生'
    // 在常量池中剛好能找到'五橋先生'的對象,返回引用地址給b2
    String b2 = "五橋" + "先生";

    // 變量相加賦值
    // 變量(這個變量的概念是相對于jvm來說的呀洲,jvm對它具體的值不能感知所以稱為變量)相加紊选,不會維護常量池,直接在堆棧中分配一個對象
    String c = a1 + a2;

    // new
    // 常量池中找到'五橋'的對象
    // 堆棧中分配一個對象道逗,這個對象指向了常量池中'五橋'的對象
    // 讓d1指向堆棧中剛分配的對象
    String d1 = new String("五橋");
    // 過程同d1兵罢,但注意d1和d1_1不是同一個對象,只不過這兩個對象都指向了常量池中的'五橋'
    String d1_1 = new String("五橋");
    // 這個好理解滓窍,d2指向一個對象卖词,這個對象又指向a1所指向的對象
    String d2 = new String(a1);
    // a1 + a2:堆棧中分配一個對象,這個對象的值是'五橋先生'
    // 再分配一個對象吏夯,這個對象的引用指向了上面分配的對象
    // 注意這個過程與常量池無關(guān)
    String d3 = new String(a1 + a2);

?關(guān)于上面提到的jvm的優(yōu)化此蜈,可參閱常量折疊技術(shù)。大概的意思是jvm在編譯代碼的過程(語法分析階段)中噪生,會將常量之間的計算結(jié)果存到俗稱的'語法樹'中裆赵,在程序的運行階段,就可以直接從'語法樹'中獲取值跺嗽。該優(yōu)化不僅僅適用于String對象的創(chuàng)建战授,類似于int a = 1 + 2這樣的語句同樣適用页藻。
?為什么變量相加不負責維護常量池?因為在String底層多個對象相加是調(diào)用的StringBuilder的append方法實現(xiàn)的植兰,該過程在方法中直接實現(xiàn)(事實上份帐,我們通過編譯.java文件為.class文件觀察到)。我們甚至可以理解為所有在方法中返回的String對象都是直接在堆中分配內(nèi)存楣导,具體可參考相應(yīng)的源碼废境,在此不再展開贅述。
?到這里筒繁,我們應(yīng)該能給上面的例子答案了噩凹。結(jié)果如下:

    (1)...a1==a1_1? true
    (2)...d1==d1_1? false
    (3)...b2=='五橋先生'? true
    (4)...b2==c'? false
    (5)...d1==d2'? false
    (6)...d3=='五橋先生'? false
    (7)...d3==b2? false
    (8)...b1==b2? true
    (9)...b1==c? false

?到這里,我們還可以大致畫一個String的對象布局圖(簡化后的)幫助理解一下整個對象創(chuàng)建的過程膝晾。

String對象分配內(nèi)存布局圖

?補充內(nèi)容:在jdk1.7之前栓始,字符串常量池被存放在方法區(qū)中(具體是在方法區(qū)中的運行時常量池中);在jdk1.7的時候血当,字符串常量池被單獨拿到堆中了幻赚。

拓展題

?五橋先生曾被朋友問到一個問題,當時因為對常量池這塊沒有研究所以壓根不知道這個題是啥意思臊旭。題是下面這樣的落恼。

    String Q1 = "xxx";// 第一句
    String M1 = "yyy" + Q1 + "zzz";//第二句
    String M2 = "yyy" + "zzz" + Q1;//第三句
    // 問:第一句和第二句有什么區(qū)別?

?廢話不多說离熏,直接看結(jié)果:

  1. 首先佳谦,第一句會在常量池中創(chuàng)建一個"xxx",由于第二句和第三句都遇到了變量滋戳,所以肯定都分別會在堆中分配對象钻蔑;
  2. 第二句中,引用變量在三個對象的中間奸鸯,jvm不會優(yōu)化咪笑,所以會在常量池中分別創(chuàng)建"yyy"和"zzz";
  3. 第三句中,上來是兩個常量相加的結(jié)果娄涩,再和變量相加窗怒,這個語句會被jvm優(yōu)化成String M2 = "yyyzzz" + Q1;所以只會在常量池中創(chuàng)建一個"yyyzzz";

?另外:有朋友說需要考慮jvm指令重排序的問題,這個地方蓄拣,我簡單說一下我的看法:這個地方跟指令重排序沒有關(guān)系哈扬虚,指令重排的一個原則是兩條指令沒有依賴關(guān)系(說白了就是對虛擬機來說先執(zhí)行哪一條指令對程序的運行沒有影響)。而這里是無法指令重排序的球恤,三個內(nèi)容相加時辜昵,前面兩個的結(jié)果作為一個中間值再去和后面的值相加,屬于有前后依賴關(guān)系的順序執(zhí)行咽斧。

情景模擬中部分問題剖析

?回到本文一開始情景模擬里面的幾個問題中來路鹰,我想在這里贷洲,已經(jīng)可以嘗試著回答里面的部分問題了收厨。

(1) 如何判定兩個String類型的對象是同一個對象晋柱,如何判定值相等?

答:判斷兩個String對象是不是同一個對象诵叁,可直接用“==”符號雁竞;判斷值相等,用equals方法拧额。

?對于String對象來說碑诉,“==”是比較引用是不是指向內(nèi)存中的同一塊地址,而equals方法在String類中被重寫過侥锦,有自己的實現(xiàn):判斷兩個對象堆中的內(nèi)容是否相同进栽。
?一般來說,除了基本數(shù)據(jù)類型恭垦,所有的class(包括我們自定義的類)都繼承自O(shè)bject類快毛,在Object中有一個equals方法的實現(xiàn),我們在定義一個class的額時候番挺,如果需要比較兩個對象唠帝,都要重寫equals方法(當然也要重寫hashCode方法,在另一章會講到)玄柏。
關(guān)于==襟衰、equals以及hashCode參見:此處應(yīng)該插眼。

(2) 可以寫一個自定義的class粪摘,繼承自String類嗎瀑晒?我可以自己寫一個和String同名的class,并用在程序里嗎徘意?

答:String類加了final關(guān)鍵字修飾苔悦,所以不能被繼承。
?關(guān)于后一個問題映砖,筆者也沒有去嘗試過间坐,只是有一個朋友在面試中被問到了,這個問題其實不用太在意邑退,一般的面試官是問不到這個問題的竹宋。這里就按照網(wǎng)上的描述給一個答案吧:我們可以寫一個和Java.lang.String同名的class,但是并不能應(yīng)用在程序中地技。因為類加載器在加載String類的時候蜈七,根據(jù)雙親委派模型會加載其默認的類;就算你自定義一個類加載器去加載自己實現(xiàn)的類莫矗,也只能得到一個SecurityException的異常飒硅。

(3) String str = new String("abc");上面的代碼創(chuàng)建了幾個對象砂缩,為什么?

答:創(chuàng)建了1或者2個對象
?根據(jù)String類的緩存機制三娩,首先嘗試在字符串常量池中找對應(yīng)的'abc'對象庵芭,找不到就創(chuàng)建一個'abc'對象;再在堆棧中創(chuàng)建一個對象并將引用指向'abc'雀监。

(4) String是包裝類型還是基本類型双吆?基本類型和包裝類型有什么區(qū)別?Integer包裝類有對應(yīng)的int基本類型会前,為什么String沒有對應(yīng)的基本類型呢好乐?

答:Java中有8種基本類型(byte、short瓦宜、int蔚万、long、float临庇、double反璃、char、boolean)苔巨,String不在其中版扩,屬于包裝類型。
?關(guān)于基本類型和包裝類型的區(qū)別參見:傳送門侄泽。
?為什么String沒有對應(yīng)的包裝類型礁芦?這其實是個開放性的問題,可以從很多角度去解釋悼尾。我個人的理解是這樣的:
?按照細粒度去區(qū)分的話柿扣,基本數(shù)據(jù)類型和String有兩個完全不同的出發(fā)點,例如Integer是int的包裝類闺魏,包裝的是單個int類型的值未状;而String的設(shè)計理念就決定了他一旦被初始化就會指向一串連續(xù)的內(nèi)存單元,這一塊內(nèi)存單元中析桥,存放的數(shù)據(jù)不止一個(char數(shù)組嘛)司草。所以,String沒有對應(yīng)的基礎(chǔ)類型泡仗,實在要說的話埋虹,我們可以暴力的說char就是String對應(yīng)的基本類型。

(5) 為什么我們總是習慣于用String類型作為HashMap的key娩怎,為什么不用其他的Object類型搔课?

答:從HashMap的設(shè)計來講,任何對象(除基本數(shù)據(jù)類型截亦,因為基本數(shù)據(jù)類型不支持泛型)都可以作為Key爬泥,但實際情況是柬讨,我們通常會用String作為Key,因為String具有天然的優(yōu)勢袍啡。
?(1)首先踩官,我們需要知道HashMap的內(nèi)部實現(xiàn)是通過key的hashcode來確定Entry的存儲位置;
?(2)自定義的對象作為key的缺點:作為HashMap的key的前提就是必須要重寫equals和hashcode方法葬馋,如果不重寫會導(dǎo)致在你的對象屬性發(fā)生變化后卖鲤,找不到對應(yīng)的key;
?(3)相比自定義對象String作為key的優(yōu)點:因為String具有不可變性畴嘶,在被創(chuàng)建后,hashcode就可以被緩存下來集晚,不需要再次計算窗悯,所以在定位Entry的時候非常快偷拔。
將在另外一章中會詳細說明這個問題蒋院。關(guān)于HashMap的相關(guān)內(nèi)容參見:此處應(yīng)該插眼。


擴展區(qū)域

擴展區(qū)域主體

這是一個沒有實現(xiàn)的擴展莲绰。


上一篇:前言
下一篇:你知道Java中基本類型和包裝類的區(qū)別嗎

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末欺旧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蛤签,更是在濱河造成了極大的恐慌辞友,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件震肮,死亡現(xiàn)場離奇詭異炼团,居然都是意外死亡婆瓜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來造寝,“玉大人,你說我怎么就攤上這事木张〈档蓿” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵豪嚎,是天一觀的道長搔驼。 經(jīng)常有香客問我,道長疙渣,這世上最難降的妖魔是什么匙奴? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮妄荔,結(jié)果婚禮上泼菌,老公的妹妹穿的比我還像新娘谍肤。我一直安慰自己,他們只是感情好哗伯,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布荒揣。 她就那樣靜靜地躺著,像睡著了一般焊刹。 火紅的嫁衣襯著肌膚如雪系任。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天虐块,我揣著相機與錄音俩滥,去河邊找鬼。 笑死贺奠,一個胖子當著我的面吹牛霜旧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播儡率,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼挂据,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了儿普?” 一聲冷哼從身側(cè)響起崎逃,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎眉孩,沒想到半個月后个绍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡勺像,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年障贸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吟宦。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡篮洁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出殃姓,到底是詐尸還是另有隱情袁波,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布蜗侈,位于F島的核電站篷牌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏踏幻。R本人自食惡果不足惜枷颊,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夭苗,春花似錦信卡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至界赔,卻和暖如春丢习,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背淮悼。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工咐低, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敛惊。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓渊鞋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞧挤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354