Stream API

掌握了Lambda表達(dá)式和方法引用之后邢享,我們就可以很輕松的 Stream API 了鹏往,沒有掌握Lambda表達(dá)式和方法引用的話,建議去看看我之前寫的文章骇塘。因?yàn)榻酉聛?lái)會(huì)大量的用到它們伊履。

Stream

什么是Stream
Stream是一組用來(lái)處理數(shù)組款违、集合的API唐瀑,它支持順序流和并行流的聚合操作。

Stream特性

  • 不是數(shù)據(jù)結(jié)構(gòu)插爹,沒有內(nèi)部存儲(chǔ)哄辣。
  • 不支持索引訪問。
  • 延遲計(jì)算赠尾。
  • 支持并行力穗。
  • 很容易生成數(shù)組或集合(ListSet)气嫁。
  • 支持過(guò)濾当窗、查找夹纫、轉(zhuǎn)換峡谊、匯總患久、聚合等操作另萤。

備注:
1、在沒有執(zhí)行終止操作之前嘶朱,它是不會(huì)進(jìn)行計(jì)算的蛾坯。

Stream運(yùn)行機(jī)制

Stream 分為數(shù)據(jù)源光酣,中間操作疏遏,終止操作。
數(shù)據(jù)源可以是一個(gè)數(shù)組救军、一個(gè)集合财异、一個(gè)生成器方法、一個(gè)I/O通道等等唱遭。
一個(gè)流可以有零個(gè)或者多個(gè)中間操作戳寸,每一個(gè)中間操作都會(huì)返回一個(gè)新的流,供下一個(gè)操作使用拷泽。一個(gè)流只會(huì)有一個(gè)終止操作疫鹊。
Stream 只有遇到終止操作,它的數(shù)據(jù)源才開始執(zhí)行遍歷操作司致。

Stream常用的API

中間操作

  • 過(guò)濾 filter
  • 去重 distinct
  • 排序 sorted
  • 截取 limit拆吆、skip
  • 轉(zhuǎn)換 mapflatMap
  • 其他 peek

終止操作

  • 循環(huán) forEach
  • 計(jì)算 min脂矫、max枣耀、countaverage
  • 匹配 anyMatch庭再、allMatch捞奕、noneMatchfindFirst拄轻、findAny
  • 匯聚 reduce
  • 收集器 toArray颅围、collect

Stream的創(chuàng)建

  • 通過(guò)數(shù)組來(lái)創(chuàng)建。
  • 通過(guò)集合來(lái)創(chuàng)建恨搓。
  • 通過(guò) Stream.generate 方法來(lái)創(chuàng)建院促。
  • 通過(guò) Stream.iterate 方法來(lái)創(chuàng)建。
  • 通過(guò)其他API來(lái)創(chuàng)建(文件奶卓、字符串等等)一疯。

這里說(shuō)明一下,Stream API 在Android使用的話夺姑,要求API 24 以上才能使用墩邀,也可以導(dǎo)入com.annimon:stream:$lastVersion庫(kù)來(lái)使用它。
了解了一下基本的概念后盏浙,下面我們來(lái)看看Stream API 實(shí)戰(zhàn)眉睹。

Stream實(shí)戰(zhàn)

首先我們看看用代碼如何來(lái)實(shí)現(xiàn)Stream的創(chuàng)建荔茬。
1、通過(guò)數(shù)組來(lái)創(chuàng)建竹海。

public static<T> Stream<T> of(T... values) {
    ...
}

String[] arr = {"a", "b", "c", "1", "2"};
Stream<String> stream = Stream.of(arr);

使用Stream.of方法將數(shù)組作為參數(shù)傳入慕蔚,即可得到 Stream

2斋配、通過(guò)集合來(lái)創(chuàng)建孔飒。

default Stream<E> stream() {
        ...
}

List<String> list = Arrays.asList("a", "b", "c", "1", "2");
Stream<String> stream = list.stream();

使用list.stream方法創(chuàng)建得到Stream

3艰争、通過(guò)Stream.generate方法來(lái)創(chuàng)建坏瞄。

public static<T> Stream<T> generate(Supplier<T> s) {
    ...
}

Stream<String> stream = Stream.generate(() -> 1);

使用Stream.generate方法創(chuàng)建得到Stream時(shí),傳入的是Supplier甩卓,它代表一個(gè)輸出鸠匀,說(shuō)明調(diào)用Stream.generate方法會(huì)不斷的輸入一個(gè)值,放到Stream中逾柿。對(duì)于Stream.generate方法的使用缀棍,一般都會(huì)和一些中間操作的API搭配使用,比如使用 limit 截取一部分?jǐn)?shù)據(jù)机错。

4爬范、通過(guò)Stream.iterate方法來(lái)創(chuàng)建。

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
        ...
}

Stream<Integer> stream = Stream.iterate(1, x -> x + 1);

使用Stream.iterate方法創(chuàng)建得到Stream時(shí)毡熏,傳入的是一個(gè)初始值和一個(gè)一元運(yùn)算(輸入和輸出類型相同)的Lambda表達(dá)式坦敌。上面的操作解釋為輸入一個(gè)初始值為1,每次的輸入結(jié)果為上一次輸出的值加1痢法,然后不然循環(huán)輸入狱窘。

備注:Stream.generateStream.iterate 方法生成的是一個(gè)無(wú)止盡的 Stream ,我們?cè)谑褂玫臅r(shí)候需要注意這一點(diǎn)财搁。

5蘸炸、通過(guò)其他API來(lái)創(chuàng)建。

String str = "abcd";
IntStream stream = str.chars();

Stream<String> stream = Files.lines(Paths.get("path"));

我們也可以使用一些其他API自帶的創(chuàng)建 Stream 的方法尖奔。

接下來(lái)看看 Stream 常用的API的使用搭儒。
1、中間操作符提茁。

Stream<T> filter(Predicate<? super T> predicate);

Stream<T> distinct();

Stream<T> sorted();

Stream<T> limit(long maxSize);

Stream<T> skip(long n);

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

Stream<T> peek(Consumer<? super T> action);

S parallel();

S sequential();

2淹禾、終止操作符。

void forEach(Consumer<? super T> action);

Optional<T> min(Comparator<? super T> comparator);

Optional<T> max(Comparator<? super T> comparator);

long count();

OptionalDouble average();

boolean anyMatch(Predicate<? super T> predicate);

boolean allMatch(Predicate<? super T> predicate);

boolean noneMatch(Predicate<? super T> predicate);

Optional<T> findFirst();

Optional<T> findAny();

T reduce(T identity, BinaryOperator<T> accumulator);

Object[] toArray();

<R, A> R collect(Collector<? super T, A, R> collector);

看到這些方法是不是很眼熟茴扁?铃岔??
沒錯(cuò)峭火,在講Lambda表達(dá)式和函數(shù)式接口的時(shí)候毁习,我們就列出過(guò)一些函數(shù)式接口的表格智嚷,我再把它貼出來(lái)吧,不是湊字?jǐn)?shù)哦纺且!

接口名 參數(shù) 返回值 用途 含義
Predicate T boolean 斷言 代表一個(gè)輸入
Consumer T void 消費(fèi) 代表一個(gè)輸入
Function<T,R> T R 函數(shù) 代表一個(gè)輸入盏道,一個(gè)輸出(一般輸入和輸出是不同類型的)
BiFunction<T,U,R> (T,U) R 函數(shù) 代表兩個(gè)輸入,一個(gè)輸出(一般輸入和輸出是不同類型的)
Supplier None T 工廠方法 代表一個(gè)輸出
UnaryOperator T T 邏輯非载碌、迭代器 代表一個(gè)輸入猜嘱,一個(gè)輸出(輸入和輸出是相同類型的)
BinaryOperator (T,T) T 二元操作 代表兩個(gè)輸入,一個(gè)輸出(輸入和輸出是相同類型的)

不知道有沒有人和我一樣恐仑,第一次學(xué)習(xí)Lambda表達(dá)式的時(shí)候泉坐,根本不理解這些函數(shù)式接口的用途代表什么意思。比如什么是“斷言”裳仆,什么是“消費(fèi)”等等。

所以孤钦,我們還是有必要了解一下這些概念的歧斟,這對(duì)于我們之后的學(xué)習(xí)和學(xué)術(shù)討論很有幫助。

斷言:其實(shí)是防止程序意外出錯(cuò)的一種宏偏形,如果其參數(shù)計(jì)算為假静袖,則程序發(fā)出警告,且退出俊扭。
最常見的用法就是在函數(shù)入口處保證輸入?yún)?shù)的正確性队橙。

assert(object != NULL) ;

消費(fèi):是指程序在運(yùn)行過(guò)程中,資源的消耗和使用萨惑。
最常用的用法就是將元素傳遞給其他函數(shù)處理捐康,并且不給與返回值。

stream.forEach(System.out::println)

函數(shù):也稱函數(shù)關(guān)系式庸蔼,是指給定元素解总,經(jīng)過(guò)一些行為、處理后得到其他的元素的關(guān)系表達(dá)姐仅,它的核心就是對(duì)應(yīng)關(guān)系花枫。
函數(shù)的用途就很廣了,尤其是我們學(xué)習(xí)函數(shù)式編程之后掏膏,才能真正體會(huì)它的強(qiáng)大劳翰。

stream.map {
    x -> x.toString
}

工廠方法:我們知道工廠是生成東西的工具,那么我們是不是可以理解為工廠方法是生成方法的工具呢馒疹?是的佳簸,說(shuō)明白一點(diǎn)就是提供一些生成對(duì)象的方法給我們使用,它本身是不創(chuàng)建東西的行冰,我們只需要關(guān)心方法如何使用溺蕉。

stream.collect(Collectors.toList())

可以看到 Collectors 類含有很多生成其他對(duì)象的方法伶丐,比如 toCollection()toSet()疯特、toList()哗魂、toMap() 等等。

邏輯非:顧名思義漓雅,就是取一個(gè)有效值的反值录别。
使用場(chǎng)景多用于輸入和輸出的類型相同。

Stream.iterate(1, x -> !x)

二元操作:即為輸入兩個(gè)參數(shù)和輸出的類型相同邻吞,多用于合并 Stream 的場(chǎng)景组题。

stream.reduce((value1, value2) ->
    value1 + value2
)

了解這些函數(shù)式接口之后,我們?cè)谑褂?Stream API 的時(shí)候就很容易了抱冷,接下來(lái)我們來(lái)看看這些操作符的用法吧崔列。

filter 過(guò)濾

filter中的條件,返回true的才會(huì)保留

// 過(guò)濾基數(shù)值旺遮,保留偶數(shù)值赵讯,最后遍歷
Arrays.asList(1, 2, 3, 4, 5).stream().filter(x -> x%2 == 2).forEach(System.out::println);

distince 去重

distinct是調(diào)用元素的hashCode和equals方法判斷重復(fù)的值或者對(duì)象,從而實(shí)現(xiàn)去重功能耿眉。
對(duì)于實(shí)體類去重時(shí)边翼,需要重寫實(shí)體類的 equals() 以及 hashCode() 方法
也可以使用 filtercollect 操作符去重鸣剪。

//去重组底,barry存在2個(gè),去重后只有一個(gè)
Stream.of("wally", "barry", "rose", "barry").distinct().forEach(System.out::println);
// filter 實(shí)體類字段去重
private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}
stre.filter(distinctByKey(User::getName)).forEach(System.out::println);
// collect 實(shí)體類字段去重
stream.collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)).forEach(System.out::println);

sorted 排序

默認(rèn)按照自然順序筐骇,從小到大排序债鸡。

Stream.of(1, 4, 8, 2, 0, -1).sorted().forEach(System.out::println);
// 倒序排序
Stream.of(1, 4, 8, 2, 0, -1).sorted((a, b) -> b - a).forEach(System.out::println);

limit 限制

限制取出 Stream 的個(gè)數(shù)。

Stream.iterate(1, x -> x + 1).limit(50).forEach(System.out::println);

skip 步長(zhǎng)

跳過(guò)步長(zhǎng)大小取值拥褂。

Stream.iterate(1, x -> x + 1).skip(5).limit(50).forEach(System.out::println);

map 數(shù)據(jù)轉(zhuǎn)換

// 將Stream的每個(gè)元素進(jìn)行大寫轉(zhuǎn)換
Stream.of("wally", "barry", "rose", "barry").map(String.toUpperCase).forEach(System.out::println);

flatMap 攤平

組合多個(gè)流為一個(gè)流

Stream.of(Arrays.asList("wally", "barry", "rose"), Arrays.asList("jack", "ben")).flatMap(names -> names.stream).forEach(System.out::println);

peek 查看

這個(gè)操作符主要目的是用于調(diào)試娘锁,查看 Stream 中的數(shù)據(jù)經(jīng)過(guò)每個(gè)操作符后的狀態(tài)。

Stream.of("wally", "barry", "rose", "barry")
    .peek(System.out::println)
    .map(String.toUpperCase)
    .peek(System.out::println)
    .forEach(System.out::println);

需要注意一點(diǎn)饺鹃,peek是中間操作符莫秆,直接使用是無(wú)效的,不會(huì)打印任何東西悔详。

parallel 并行

使用該操作符镊屎,會(huì)把當(dāng)前 Stream 用并行方式處理。
設(shè)置并行 Stream 的線程數(shù)(包含主線程)茄螃。

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "5")

sequential 串行(默認(rèn))

使用該操作符缝驳,會(huì)把當(dāng)前 Stream 用串行方式處理。

forEach 遍歷

Stream.of("wally", "barry", "rose", "barry").forEach(System.out::println);

min 取最小

int min = Stream.of(1, 2, 3, 4, 5, 6).min(Integer::compareTo).orElse(-1);

max 取最大

int max = Stream.of(1, 2, 3, 4, 5, 6).max(Integer::compareTo).orElse(-1);

count 計(jì)數(shù)

統(tǒng)計(jì)元素個(gè)數(shù)

long count = Stream.of(1, 2, 3, 4, 5, 6).count();

average 取平均

取平均只有IntStream、DoubleStream用狱、LongStream中可以使用运怖,也可以使用collect取平均值

Double averagingValue = Stream.of(1, 2, 3, 4, 5, 6).collect(Collectors.averagingInt(value -> value));

anyMatch 找任意匹配條件的值或?qū)ο?/h4>

判斷,只要一個(gè)匹配夏伊,就返回true

// 匹配長(zhǎng)度大于或者等于 5摇展,只要有一個(gè)匹配,即使 rose 的長(zhǎng)度為 4 不符合也返回 true溺忧。
boolean result = Stream.of("wally", "barry", "rose").anyMatch(s -> s.length() >= 5);

allMatch 找所有匹配條件的值或?qū)ο?/h4>

判斷咏连,所有都匹配,才返回true

// 匹配長(zhǎng)度大于或者等于 5鲁森,因?yàn)?rese 的長(zhǎng)度 4祟滴,所以返回false
boolean result = Stream.of("wally", "barry", "rose").allMatch(s -> s.length() >= 5);

noneMatch 找不符合匹配條件的值或?qū)ο?/h4>
// 匹配長(zhǎng)度大于或者等于 5,因?yàn)?wally歌溉、barry 的長(zhǎng)度符合垄懂,所以返回false
boolean result = Stream.of("wally", "barry", "rose").noneMatch(s -> s.length() >= 5);

findFirst 找第一個(gè)值或?qū)ο?/h4>
Optional<Integer> first = Stream.of(1, 2, 3, 4, 5, 6).findFirst();

findAny 找任意的值或?qū)ο?/h4>
Optional<Integer> any = Stream.of(1, 2, 3, 4, 5, 6).findAny();

reduce 累積、結(jié)合

根據(jù)一定的規(guī)則將 Stream 中的元素進(jìn)行計(jì)算后返回一個(gè)唯一的值研底。
有三種方法可以調(diào)用埠偿,分別是:

  • Optional<T> reduce(BinaryOperator<T> accumulator);
  • T reduce(T identity, BinaryOperator<T> accumulator);
  • <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
    需要注意的是,在使用三個(gè)參數(shù)的方法時(shí)榜晦,如果 Stream 是非并行的,combiner 不會(huì)生效羽圃,計(jì)算過(guò)程和兩個(gè)參數(shù)的方法是一致的乾胶。
// 方法一,二元運(yùn)算處理朽寞,常用于求和识窿、求最小值或最大值。
Optional<Integer> reduce = Stream.of(1, 2, 3, 4, 5, 6).reduce(Integer::sum);
// 方法二脑融,在方法一的基礎(chǔ)上喻频,增加初始值。常用于將一個(gè) String 類型的 Stream 中的所有元素連接到一起并在最前面添加 value 后返回肘迎。
String reduce = Stream.of("Hello", " ", "World").reduce("Log:", (s1, s2) -> s1+ s2);
// 方法三甥温,并行時(shí),將 identity 參數(shù)與序列中的每一個(gè)元素進(jìn)行 accumulator 轉(zhuǎn)換妓布,得到N個(gè)結(jié)果姻蚓,然后使用 combiner 對(duì)N個(gè)結(jié)果進(jìn)行匯總。
// 使用函數(shù)表示就是:(4+1) * (4+2) * (4+3) = 210;
int reduce = Stream.of(1, 2, 3).reduce(4, (v1, v2) -> v1 + v2, (v1, v2) -> v1 * v2);

reduce即使是并行情況下匣沼,也不會(huì)創(chuàng)建多個(gè)初始化對(duì)象狰挡,combiner接收的兩個(gè)參數(shù)永遠(yuǎn)是同一個(gè)對(duì)象。

// array1 == array2 永遠(yuǎn)為True
Predicate<String> predicate = t -> t.contains("a");
        ArrayList<String> list = Stream.of("aa", "ab", "c", "ad").parallel().reduce(new ArrayList<>(),
                (array, s) -> {
                    if (predicate.test(s)) array.add(s);
                    return array;
                },
                (array1, array2) -> {
                    System.out.println(array1 == array2);
                    array1.addAll(array2);
                    return array1;
                });

toArray 轉(zhuǎn)換成數(shù)組

有兩種方法可以調(diào)用,分別是:

  • Object[] toArray();
  • <A> A[] toArray(IntFunction<A[]> generator);
Integer[] array = Stream.of(1, 2, 3, 4, 5, 6).toArray(Integer[]::new);

collect 收集

collect含義與reduce有點(diǎn)相似加叁,區(qū)別是reduce即使是并行情況下倦沧,也不會(huì)創(chuàng)建多個(gè)初始化對(duì)象,combiner接收的兩個(gè)參數(shù)永遠(yuǎn)是同一個(gè)對(duì)象它匕。
有兩種方法可以調(diào)用展融,分別是:

  • <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
  • <R, A> R collect(Collector<? super T, A, R> collector);
// 方法一,和 reduce 的三個(gè)參數(shù)方法用法類似超凳。
Predicate<String> predicate = t -> t.contains("a");
ArrayList<String> list = Stream.of("aa", "ab", "c", "ad").parallel().collect(ArrayList::new,
    (array, s) -> {
        if (predicate.test(s)) array.add(s);
    },
    ArrayList::addAll);
// 使用 Collectors 提供的 API
stream.collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)).forEach(System.out::println);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末愈污,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子轮傍,更是在濱河造成了極大的恐慌暂雹,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件创夜,死亡現(xiàn)場(chǎng)離奇詭異杭跪,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)驰吓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門涧尿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人檬贰,你說(shuō)我怎么就攤上這事姑廉。” “怎么了翁涤?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵桥言,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我葵礼,道長(zhǎng)号阿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任鸳粉,我火速辦了婚禮扔涧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘届谈。我一直安慰自己枯夜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布疼约。 她就那樣靜靜地躺著卤档,像睡著了一般。 火紅的嫁衣襯著肌膚如雪程剥。 梳的紋絲不亂的頭發(fā)上劝枣,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天汤踏,我揣著相機(jī)與錄音,去河邊找鬼舔腾。 笑死溪胶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的稳诚。 我是一名探鬼主播哗脖,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扳还!你這毒婦竟也來(lái)了才避?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤氨距,失蹤者是張志新(化名)和其女友劉穎桑逝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俏让,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡楞遏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了首昔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寡喝。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖勒奇,靈堂內(nèi)的尸體忽然破棺而出预鬓,到底是詐尸還是另有隱情,我是刑警寧澤赊颠,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布珊皿,位于F島的核電站,受9級(jí)特大地震影響巨税,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粉臊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一草添、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扼仲,春花似錦远寸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至矗愧,卻和暖如春灶芝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工夜涕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留犯犁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓女器,卻偏偏與公主長(zhǎng)得像酸役,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驾胆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359