日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java 8系列之Stream的强大工具Collector

發(fā)布時間:2025/3/21 java 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 8系列之Stream的强大工具Collector 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Stream系列:

  • Java 8系列之Stream的基本語法詳解
  • Java 8系列之Stream的強(qiáng)大工具Collector
  • Java 8系列之重構(gòu)和定制收集器
  • Java 8系列之Stream中萬能的reduce?
  • 概述

    前面我們使用過collect(toList()),在流中生成列表。實(shí)際開發(fā)過程中,List又是我們經(jīng)常用到的數(shù)據(jù)結(jié)構(gòu),但是有時候我們也希望Stream能夠轉(zhuǎn)換生成其他的值,比如Map或者set,甚至希望定制生成想要的數(shù)據(jù)結(jié)構(gòu)。

    collect也就是收集器,是Stream一種通用的、從流生成復(fù)雜值的結(jié)構(gòu)。只要將它傳給collect方法,也就是所謂的轉(zhuǎn)換方法,其就會生成想要的數(shù)據(jù)結(jié)構(gòu)。這里不得不提下,Collectors這個工具庫,在該庫中封裝了相應(yīng)的轉(zhuǎn)換方法。當(dāng)然,Collectors工具庫僅僅封裝了常用的一些情景,如果有特殊需求,那就要自定義了。

    顯然,List是能想到的從流中生成的最自然的數(shù)據(jù)結(jié)構(gòu), 但是有時人們還希望從流生成其他值, 比如 Map 或 Set, 或者你希望定制一個類將你想要的東西抽象出來。

    前面已經(jīng)講過,僅憑流上方法的簽名,就能判斷出這是否是一個及早求值的操作。 reduce操作就是一個很好的例子, 但有時人們希望能做得更多。?
    這就是收集器,一種通用的、從流生成復(fù)雜值的結(jié)構(gòu)。只要將它傳給collect 方法,所有的流就都可以使用它了。

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

    <R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,?BiConsumer<R, R> combiner);

    輔助接口

    Supplier

    Supplier<T>接口是一個函數(shù)接口,該接口聲明了一個get方法,主要用來創(chuàng)建返回一個指定數(shù)據(jù)類型的對象。

    • T:指定的數(shù)據(jù)類型
    @FunctionalInterface? public interface Supplier {?T get();? }

    BiConsumer

    BiConsumer<T, U>接口是一個函數(shù)接口,該接口聲明了accept方法,并無返回值,該函數(shù)接口主要用來聲明一些預(yù)期操作。

    同時,該接口定義了一個默認(rèn)方法andThen,該方法接受一個BiConsumer,并返回一個組合的BiConsumer,其會按照順序執(zhí)行操作。如果執(zhí)行任一操作拋出異常,則將其傳遞給組合操作的調(diào)用者。 如果執(zhí)行此操作拋出異常,將不執(zhí)行后操作(after)。

    @FunctionalInterface public interface BiConsumer<T, U> {void accept(T t, U u);default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {Objects.requireNonNull(after);return (l, r) -> {accept(l, r);after.accept(l, r);};} }

    BinaryOperator

    BinaryOperator接口繼承于BiFunction接口,該接口指定了apply方法執(zhí)行的參數(shù)類型及返回值類型均為T。

    @FunctionalInterface public interface BinaryOperator<T> extends BiFunction<T,T,T> {public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {Objects.requireNonNull(comparator);return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;}public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {Objects.requireNonNull(comparator);return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;} }@FunctionalInterface public interface BiFunction<T, U, R> {R apply(T t, U u);default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t, U u) -> after.apply(apply(t, u));} }

    Function

    Funtion是一個函數(shù)接口,其內(nèi)定義了一個轉(zhuǎn)換函數(shù),將T轉(zhuǎn)換為R。比如Stream中的map方法便是接受該函數(shù)參數(shù),將T轉(zhuǎn)換為R。

    @FunctionalInterface public interface Function<T, R> {/*** 轉(zhuǎn)換函數(shù),將T轉(zhuǎn)換為R*/R apply(T t);/*** 返回一個組合函數(shù)Function,首先執(zhí)行before,然后再執(zhí)行該Function** 如果兩個函數(shù)的求值都拋出異常,它將被中繼到組合函數(shù)的調(diào)用者。* 如果before為null,將會拋出NullPointerException*/default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {Objects.requireNonNull(before);return (V v) -> apply(before.apply(v));}/*** 返回一個組合函數(shù)Function,首先執(zhí)行Function,然后再執(zhí)行after** 如果兩個函數(shù)的求值都拋出異常,它將被中繼到組合函數(shù)的調(diào)用者。* 如果after為null,將會拋出NullPointerException*/default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}/*** 將輸入?yún)?shù)返回的函數(shù)*/static <T> Function<T, T> identity() {return t -> t;} }

    Collector

    Collector是Stream的可變減少操作接口,可變減少操作包括:將元素累積到集合中,使用StringBuilder連接字符串;計算元素相關(guān)的統(tǒng)計信息,例如sum,min,max或average等。Collectors(類收集器)提供了許多常見的可變減少操作的實(shí)現(xiàn)。

    Collector<T, A, R>接受三個泛型參數(shù),對可變減少操作的數(shù)據(jù)類型作相應(yīng)限制:

    • T:輸入元素類型
    • A:縮減操作的可變累積類型(通常隱藏為實(shí)現(xiàn)細(xì)節(jié))
    • R:可變減少操作的結(jié)果類型

    Collector接口聲明了4個函數(shù),這四個函數(shù)一起協(xié)調(diào)執(zhí)行以將元素累積到可變結(jié)果容器中,并且可以選擇地對結(jié)果進(jìn)行最終的變換.

    • Supplier<A> supplier(): 創(chuàng)建新的結(jié)果容器
    • BiConsumer<A, T> accumulator(): 將元素添加到結(jié)果容器
    • BinaryOperator<A> combiner(): 將兩個結(jié)果容器合并為一個結(jié)果容器
    • Function<A, R> finisher(): 對結(jié)果容器作相應(yīng)的變換

    在Collector接口的characteristics方法內(nèi),可以對Collector聲明相關(guān)約束

    • Set<Characteristics> characteristics():

    而Characteristics是Collector內(nèi)的一個枚舉類,聲明了CONCURRENT、UNORDERED、IDENTITY_FINISH等三個屬性,用來約束Collector的屬性。

    • CONCURRENT:表示此收集器支持并發(fā),意味著允許在多個線程中,累加器可以調(diào)用結(jié)果容器
    • UNORDERED:表示收集器并不按照Stream中的元素輸入順序執(zhí)行
    • IDENTITY_FINISH:表示finisher實(shí)現(xiàn)的是識別功能,可忽略。?

    注:如果一個容器僅聲明CONCURRENT屬性,而不是UNORDERED屬性,那么該容器僅僅支持無序的Stream在多線程中執(zhí)行。


    身份約束和相關(guān)性約束

    Stream可以順序執(zhí)行,或者并發(fā)執(zhí)行,或者順序并發(fā)執(zhí)行,為了保證Stream可以產(chǎn)生相同的結(jié)果,收集器函數(shù)必須滿足身份約束和相關(guān)項約束。

    身份約束說,對于任何部分累積的結(jié)果,將其與空結(jié)果容器組合必須產(chǎn)生等效結(jié)果。也就是說,對于作為任何系列的累加器和組合器調(diào)用的結(jié)果的部分累加結(jié)果a,a必須等于combiner.apply(a,supplier.get())。

    相關(guān)性約束說,分裂計算必須產(chǎn)生等效的結(jié)果。也就是說,對于任何輸入元素t1和t2,以下計算中的結(jié)果r1和r2必須是等效的:

    A a1 = supplier.get(); accumulator.accept(a1,t1); accumulator.accept(a1,t2); R r1 = finisher.apply(a1); // result without splittingA a2 = supplier.get(); accumulator.accept(a2,t1); A a3 = supplier.get(); accumulator.accept(a3,t2); R r2 = finisher.apply(combiner.apply(a2,a3));?

    創(chuàng)建Collector

    自定義Collector

    Java 8系列之重構(gòu)和定制收集器

    基于Collector工具庫

    在Collector工具庫中,聲明了許多常用的收集器,以供我們快速創(chuàng)建一個收集器。前面我們已經(jīng)了解到,收集器函數(shù)必須滿足身份約束和相關(guān)項約束。而基于Collector實(shí)現(xiàn)簡化的庫(如Stream.collect(Collector))創(chuàng)建收集器時,必須遵守以下約束:

  • 第一個參數(shù)傳遞給accumulator()函數(shù),兩個參數(shù)都傳遞給combiner()函數(shù),傳遞給finisher()函數(shù)的參數(shù)必須是上一次調(diào)用supplier(),accumulator()或combiner()函數(shù)的結(jié)果。
  • 實(shí)現(xiàn)不應(yīng)該對任何accumulator(),combiner()或finisher()函數(shù)的結(jié)果做任何事情,除非收集器將返回的結(jié)果返回給調(diào)用者
  • 如果結(jié)果傳遞到combiner()或finisher()函數(shù),而且返回對象與傳入的不相同,則不會再將對象傳遞給accumulator()函數(shù)調(diào)用。
  • 一旦結(jié)果傳遞到combiner()或finisher()函數(shù),它就不會再次傳遞到accumulator()函數(shù)。
  • 對于串行收集器,supplier(),accumulator()或combiner()函數(shù)返回的任何結(jié)果必須是限制串行的。這使得收集器可以并行進(jìn)行,而收集器不需要執(zhí)行任何額外的同步。reduce操作實(shí)現(xiàn)必須管理Stream的元素被正確區(qū)別并分別處理,并且僅在累積完成之后,對累加器中的數(shù)據(jù)合并。
  • 對于并發(fā)收集器,實(shí)現(xiàn)可以自由地(但不是必須)同時實(shí)現(xiàn)reduce操作。accumulator()可以在多個線程同時調(diào)用,而不是在累積期間保持結(jié)果的獨(dú)立性。僅當(dāng)收集器具有Collector.Characteristics.UNORDERED特性或者原始數(shù)據(jù)是無序的時才應(yīng)用并發(fā)還原。
  • 轉(zhuǎn)換成其他集合

    對于前面提到了很多Stream的鏈?zhǔn)讲僮?#xff0c;但是,我們總是要將Stream生成一個集合,比如:

    • 已有代碼是為集合編寫的, 因此需要將流轉(zhuǎn)換成集合傳入;
    • 在集合上進(jìn)行一系列鏈?zhǔn)讲僮骱?#xff0c; 最終希望生成一個值;
    • 寫單元測試時, 需要對某個具體的集合做斷言。

    有些Stream可以轉(zhuǎn)成集合,比如前面提到toList,生成了java.util.List 類的實(shí)例。當(dāng)然了,還有還有toSet和toCollection,分別生成 Set和Collection 類的實(shí)例。

    toList

    示例:

    List<Integer> collectList = Stream.of(1, 2, 3, 4).collect(Collectors.toList()); System.out.println("collectList: " + collectList); // 打印結(jié)果 // collectList: [1, 2, 3, 4]

    toSet

    示例:

    Set<Integer> collectSet = Stream.of(1, 2, 3, 4).collect(Collectors.toSet()); System.out.println("collectSet: " + collectSet); // 打印結(jié)果 // collectSet: [1, 2, 3, 4]

    toCollection

    通常情況下,創(chuàng)建集合時需要調(diào)用適當(dāng)?shù)臉?gòu)造函數(shù)指明集合的具體類型:

    List<Artist> artists = new ArrayList<>();

    但是調(diào)用toList或者toSet方法時,不需要指定具體的類型,Stream類庫會自動推斷并生成合適的類型。當(dāng)然,有時候我們對轉(zhuǎn)換生成的集合有特定要求,比如,希望生成一個TreeSet,而不是由Stream類庫自動指定的一種類型。此時使用toCollection,它接受一個函數(shù)作為參數(shù), 來創(chuàng)建集合。

    值得我們注意的是,看Collectors的源碼,因為其接受的函數(shù)參數(shù)必須繼承于Collection,也就是意味著Collection并不能轉(zhuǎn)換所有的繼承類,最明顯的就是不能通過toCollection轉(zhuǎn)換成Map

    toMap

    如果生成一個Map,我們需要調(diào)用toMap方法。由于Map中有Key和Value這兩個值,故該方法與toSet、toList等的處理方式是不一樣的。toMap最少應(yīng)接受兩個參數(shù),一個用來生成key,另外一個用來生成value。toMap方法有三種變形:

    • toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper)
  • keyMapper: 該Funtion用來生成Key
  • valueMapper:該Funtion用來生成value
  • 注:若Stream中有重復(fù)的值,導(dǎo)致Map中key重復(fù),在運(yùn)行時會報異常java.lang.IllegalStateException: Duplicate key **

    • toMap(Function

    轉(zhuǎn)成值

    使用collect可以將Stream轉(zhuǎn)換成值。maxBy和minBy允許用戶按照某個特定的順序生成一個值。

    • averagingDouble:求平均值,Stream的元素類型為double
    • averagingInt:求平均值,Stream的元素類型為int
    • averagingLong:求平均值,Stream的元素類型為long
    • counting:Stream的元素個數(shù)
    • maxBy:在指定條件下的,Stream的最大元素
    • minBy:在指定條件下的,Stream的最小元素
    • reducing: reduce操作
    • summarizingDouble:統(tǒng)計Stream的數(shù)據(jù)(double)狀態(tài),其中包括count,min,max,sum和平均。
    • summarizingInt:統(tǒng)計Stream的數(shù)據(jù)(int)狀態(tài),其中包括count,min,max,sum和平均。
    • summarizingLong:統(tǒng)計Stream的數(shù)據(jù)(long)狀態(tài),其中包括count,min,max,sum和平均。
    • summingDouble:求和,Stream的元素類型為double
    • summingInt:求和,Stream的元素類型為int
    • summingLong:求和,Stream的元素類型為long

    示例:

    Optional<Integer> collectMaxBy = Stream.of(1, 2, 3, 4).collect(Collectors.maxBy(Comparator.comparingInt(o -> o))); System.out.println("collectMaxBy:" + collectMaxBy.get()); // 打印結(jié)果 // collectMaxBy:4

    分割數(shù)據(jù)塊

    collect的一個常用操作是將Stream分解成兩個集合。假如有一個數(shù)字的Stream,我們可能希望將其分割成兩個集合,一個是偶數(shù)集合,另外一個是奇數(shù)集合。我們首先想到的就是過濾操作,通過兩次過濾操作,很簡單的就完成了我們的需求。

    但是這樣操作起來有問題。首先,為了執(zhí)行兩次過濾操作,需要有兩個流。其次,如果過濾操作復(fù)雜,每個流上都要執(zhí)行這樣的操作, 代碼也會變得冗余。

    這里我們就不得不說Collectors庫中的partitioningBy方法,它接受一個流,并將其分成兩部分:使用Predicate對象,指定條件并判斷一個元素應(yīng)該屬于哪個部分,并根據(jù)布爾值返回一個Map到列表。因此對于key為true所對應(yīng)的List中的元素,滿足Predicate對象中指定的條件;同樣,key為false所對應(yīng)的List中的元素,不滿足Predicate對象中指定的條件

    這樣,使用partitioningBy,我們就可以將數(shù)字的Stream分解成奇數(shù)集合和偶數(shù)集合了。

    Map<Boolean, List<Integer>> collectParti = Stream.of(1, 2, 3, 4).collect(Collectors.partitioningBy(it -> it % 2 == 0)); System.out.println("collectParti : " + collectParti); // 打印結(jié)果 // collectParti : {false=[1, 3], true=[2, 4]}


    數(shù)據(jù)分組

    數(shù)據(jù)分組是一種更自然的分割數(shù)據(jù)操作, 與將數(shù)據(jù)分成true和false兩部分不同,可以使用任意值對數(shù)據(jù)分組。

    調(diào)用Stream的collect方法,傳入一個收集器,groupingBy接受一個分類函數(shù),用來對數(shù)據(jù)分組,就像partitioningBy一樣,接受一個?Predicate對象將數(shù)據(jù)分成true和false兩部分。我們使用的分類器是一個Function對象,和map操作用到的一樣。

    示例:

    Map<Boolean, List<Integer>> collectGroup= Stream.of(1, 2, 3, 4).collect(Collectors.groupingBy(it -> it > 3)); System.out.println("collectGroup : " + collectGroup); // 打印結(jié)果 // collectGroup : {false=[1, 2, 3], true=[4]}

    注:看groupingBy和partitioningBy的例子,他們的效果都是一樣的,都是將Stream的數(shù)據(jù)進(jìn)行了分割處理并返回一個Map。可能舉的例子給你帶來了誤區(qū),實(shí)際上他們兩個完全是不一樣的。

    partitioningBy是根據(jù)指定條件,將Stream分割,返回的Map為Map

    字符串

    有時候,我們將Stream的元素(String類型)最后生成一組字符串。比如在Stream.of(“1”, “2”, “3”, “4”)中,將Stream格式化成“1,2,3,4”。

    如果不使用Stream,我們可以通過for循環(huán)迭代實(shí)現(xiàn)。

    ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4);StringBuilder sb = new StringBuilder();for (Integer it : list) {if (sb.length() > 0) {sb.append(",");}sb.append(it);} System.out.println(sb.toString()); // 打印結(jié)果 // 1,2,3,4

    在Java 1.8中,我們可以使用Stream來實(shí)現(xiàn)。這里我們將使用 Collectors.joining 收集Stream中的值,該方法可以方便地將Stream得到一個字符串。joining函數(shù)接受三個參數(shù),分別表示允(用以分隔元素)、前綴和后綴。

    示例:

    String strJoin = Stream.of("1", "2", "3", "4").collect(Collectors.joining(",", "[", "]")); System.out.println("strJoin: " + strJoin); // 打印結(jié)果 // strJoin: [1,2,3,4]

    組合Collector

    前面,我們已經(jīng)了解到Collector的強(qiáng)大,而且非常的實(shí)用。如果將他們組合起來,是不是更厲害呢?看前面舉過的例子,在數(shù)據(jù)分組時,我們得到了分組后的數(shù)據(jù)列表 collectGroup : {false=[1, 2, 3], true=[4]}。如果我們的要求更高點(diǎn),我們不需要分組后的列表,只要得到分組后列表的個數(shù)就好了。

    這時候,很多人下意識的都會想到,遍歷Map就好了,然后使用list.size(),就可以輕松的得到各個分組的列表個數(shù)。

    // 分割數(shù)據(jù)塊 Map<Boolean, List<Integer>> collectParti = Stream.of(1, 2, 3, 4).collect(Collectors.partitioningBy(it -> it % 2 == 0));Map<Boolean, Integer> mapSize = new HashMap<>(); collectParti.entrySet().forEach(entry -> mapSize.put(entry.getKey(), entry.getValue().size()));System.out.println("mapSize : " + mapSize); // 打印結(jié)果 // mapSize : {false=2, true=2}

    在partitioningBy方法中,有這么一個變形:

    Map<Boolean, Long> partiCount = Stream.of(1, 2, 3, 4).collect(Collectors.partitioningBy(it -> it.intValue() % 2 == 0,Collectors.counting())); System.out.println("partiCount: " + partiCount); // 打印結(jié)果 // partiCount: {false=2, true=2}

    在partitioningBy方法中,我們不僅傳遞了條件函數(shù),同時傳入了第二個收集器,用以收集最終結(jié)果的一個子集,這些收集器叫作下游收集器。收集器是生成最終結(jié)果的一劑配方,下游收集器則是生成部分結(jié)果的配方,主收集器中會用到下游收集器。這種組合使用收集器的方式, 使得它們在 Stream 類庫中的作用更加強(qiáng)大。

    那些為基本類型特殊定制的函數(shù),如averagingInt、summarizingLong等,事實(shí)上和調(diào)用特殊Stream上的方法是等價的,加上它們是為了將它們當(dāng)作下游收集器來使用的。
    ---------------------?
    作者:行云間?
    來源:CSDN?
    原文:https://blog.csdn.net/IO_Field/article/details/54971608?
    版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!

    總結(jié)

    以上是生活随笔為你收集整理的Java 8系列之Stream的强大工具Collector的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 欧美,日韩,国产在线 | 丰满少妇在线观看bd | 久艹视频在线观看 | 亚洲精品999 | 午夜免费视频网站 | 新91在线 | 色婷婷电影网 | 色无极影院亚洲 | 国产精品久久在线观看 | 男人爆操女人 | 亚洲成人tv| 久久这里有精品 | 99操 | 亚洲熟女乱色一区二区三区 | 黄色激情网站 | 瑟瑟视频在线 | 亚洲蜜臀av乱码久久精品蜜桃 | 色91在线| 欧美亚洲中文精品字幕 | av成人| 亚洲国产精品久久久久爰性色 | 中文字幕观看视频 | 午夜福利视频 | 日本在线中文字幕专区 | 四虎影院免费视频 | 24小时日本在线www免费的 | 黄色av网站免费看 | 久久99久久久久久 | 欧美一区二区三区视频 | 四季av日韩精品一区 | 国产精品丝袜一区二区 | 九色视频在线播放 | 亚洲午夜无码av毛片久久 | 青青草在线播放 | 成人区人妻精品一区二区不卡视频 | 蜜臀av无码精品人妻色欲 | 多男调教一女折磨高潮高h 国内毛片毛片毛片毛片毛片 | 青青青在线视频 | 亚洲精品合集 | 日本不卡一区二区在线观看 | 国产女人被狂躁到高潮小说 | 美女视频黄色在线观看 | 欧美黑人一级片 | 在线日韩一区二区 | 黑人精品一区二区三区 | 综合激情五月婷婷 | 欧美精品亚洲精品日韩精品 | 久草免费在线 | 在线一区二区三区四区 | 偷拍亚洲视频 | 亚洲综合影院 | 97黄色片 | 国产精品久久伊人 | 最好看的mv中文字幕国语电影 | 亚洲日本成人在线观看 | 国产成人无码精品久久二区三 | 人人射人人干 | 亚洲2022国产成人精品无码区 | 伊人伊人伊人 | 精国产人伦一区二区三区 | a级片黄色| av无码久久久久久不卡网站 | 校园春色av| 人操人操 | 伊人久久色 | 国产乱淫av麻豆国产 | 少妇aa| 91av在| 国产三区精品 | 国产三级在线播放 | 美女搞黄在线观看 | 神马三级我不卡 | 日本一区二区不卡视频 | 亚洲国产欧美一区 | 亚洲精品国产福利 | 国产免费观看一区 | 日本电影一区 | 日日噜夜夜噜 | 亚洲免费精品 | 人人超碰在线 | 亚洲国产中文字幕在线 | 白丝美女被草 | 久久天天躁狠狠躁夜夜躁 | 日韩国产在线一区 | 国产一卡二卡在线播放 | 欧美一级黄色录像 | 亚洲天堂精品在线观看 | 欧美性色a | 久久久久久久久久久久久久国产 | 超碰caopeng| 手机在线看黄色 | 成人av在线播放网站 | 欧美激情在线狂野欧美精品 | 狠狠的日| 色四月| 黄色一区二区三区四区 | 美女隐私黄www网站动漫 | 欧美国产日韩一区 | 97精品久久久 |