<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          強大的 Stream 函數(shù)式編程

          共 24503字,需瀏覽 50分鐘

           ·

          2021-07-19 14:07

          前言

          Java8(又稱為 Jdk1.8)是 Java 語言開發(fā)的一個主要版本。Oracle 公司于 2014 年 3 月 18 日發(fā)布 Java8,它支持函數(shù)式編程,新的 JavaScript 引擎,新的日期 API,新的 Stream API 等。Java8 API 添加了一個新的抽象稱為流 Stream,可以讓你以一種聲明的方式處理數(shù)據(jù)。Stream API 可以極大提高 Java 程序員的生產(chǎn)力,讓程序員寫出高效率、干凈、簡潔的代碼。

          Java8 新特性

          • Lambda 表達式 ? Lambda 允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中)。

          • 方法引用 ? 方法引用提供了非常有用的語法,可以直接引用已有 Java 類或對象(實例)的方法或構造器。與 lambda 聯(lián)合使用,方法引用可以使語言的構造更緊湊簡潔,減少冗余代碼。

          • 默認方法 ? 默認方法就是一個在接口里面有了一個實現(xiàn)的方法。

          • 新工具 ? 新的編譯工具,如:Nashorn 引擎 jjs、類依賴分析器 jdeps。

          • Stream API ? 新添加的 Stream API(java.util.stream)把真正的函數(shù)式編程風格引入到 Java 中。

          • Date Time API ? 加強對日期與時間的處理。

          • Optional 類 ? Optional 類已經(jīng)成為 Java8 類庫的一部分,用來解決空指針異常。

          • Nashorn JavaScript 引擎 ? Java8 提供了一個新的 Nashorn javascript 引擎,它允許我們在 JVM 上運行特定的 javascript 應用。

          為什么需要 Steam?

          Java8 中的 Stream 是對集合(Collection)對象功能的增強,它專注于對集合對象進行各種非常便利、高效的聚合操作,或者大批量數(shù)據(jù)操作。

          StreamAPI 借助于同樣新出現(xiàn)的 Lambda 表達式,極大的提高編程效率和程序可讀性。同時它提供串行和并行兩種模式進行匯聚操作,并發(fā)模式能夠充分利用多核處理器的優(yōu)勢,使用 fork/join 并行方式來拆分任務和加速處理過程。

          流的操作種類

          中間操作

          當數(shù)據(jù)源中的數(shù)據(jù)上了流水線后,這個過程對數(shù)據(jù)進行的所有操作都稱為“中間操作”。
          中間操作仍然會返回一個流對象,因此多個中間操作可以串連起來形成一個流水線。

          終端操作

          當所有的中間操作完成后,若要將數(shù)據(jù)從流水線上拿下來,則需要執(zhí)行終端操作。
          終端操作將返回一個執(zhí)行結果,這就是你想要的數(shù)據(jù)。

          java.util.Stream 使用示例

          定義一個簡單的學生實體類,用于后面的例子演示:

          public class Student {

              /** 學號 */
              private long id;

              /** 姓名 */
              private String name;

              /** 年齡 */
              private int age;

              /** 性別 */
              private int grade;

              /** 專業(yè) */
              private String major;

              /** 學校 */
              private String school;

              // 省略 getter 和 setter
          }

          // 初始化
          List<Student> students = new ArrayList<Student>() {
              {
                  add(new Student(20160001"孔明"201"土木工程""武漢大學"));
                  add(new Student(20160002"伯約"212"信息安全""武漢大學"));
                  add(new Student(20160003"玄德"223"經(jīng)濟管理""武漢大學"));
                  add(new Student(20160004"云長"212"信息安全""武漢大學"));
                  add(new Student(20161001"翼德"212"機械與自動化""華中科技大學"));
                  add(new Student(20161002"元直"234"土木工程""華中科技大學"));
                  add(new Student(20161003"奉孝"234"計算機科學""華中科技大學"));
                  add(new Student(20162001"仲謀"223"土木工程""浙江大學"));
                  add(new Student(20162002"魯肅"234"計算機科學""浙江大學"));
                  add(new Student(20163001"丁奉"245"土木工程""南京大學"));
              }
          };

          forEach

          Stream 提供了新的方法’forEach’來迭代流中的每個數(shù)據(jù)。ForEach 接受一個 function 接口類型的變量,用來執(zhí)行對每一個元素的操作。ForEach 是一個中止操作,它不返回流,所以我們不能再調(diào)用其他的流操作。

          以下代碼片段使用 forEach 輸出了 10 個隨機數(shù):

          // 隨機生成 10 個 0,100int 類型隨機數(shù)
          new Random()
                  .ints(0100)
                  .limit(10)
                  .forEach(System.out::println);


          從集合 students 中篩選出所有武漢大學的學生:

          List<Student> whuStudents = students
                  .stream()
                  .filter(student -> "武漢大學".equals(student.getSchool()))
                  .collect(Collectors.toList());


          filter/distinct

          filter 方法用于通過設置的條件過濾出元素。Filter 接受一個 predicate 接口類型的變量,并將所有流對象中的元素進行過濾。該操作是一個中間操作,因此它允許我們在返回結果的基礎上再進行其他的流操作。

          以下代碼片段使用 filter 方法過濾出空字符串:

          // 獲取空字符串的數(shù)量
          Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  // stream() ? 為集合創(chuàng)建串行流
                  .stream()
                  .filter(string -> string.isEmpty())
                  .count();


          distinct 方法用于去除重復元素。

          Arrays.asList("a""c""ac""c""a""b")
                  .stream()
                  .distinct()
                  .forEach(System.out::println);

          anyMatch/allMatch/noneMatch

          匹配操作有多種不同的類型,都是用來判斷某一種規(guī)則是否與流對象相互吻合的。所有的匹配操作都是終結操作,只返回一個 boolean 類型的結果。

          anyMatch 方法用于判斷集合中是否有任一元素滿足條件。

          // 集合中是否有任一元素匹配以'a'開頭
          boolean result = Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  .stream()
                  .anyMatch(s -> s.startsWith("a"));

          allMatch 方法用于判斷集合中是否所有元素滿足條件。

          // 集合中是否所有元素匹配以'a'開頭
          boolean result = Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  .stream()
                  .allMatch(s -> s.startsWith("a"));

          noneMatch 方法用于判斷集合中是否所有元素不滿足條件。

          // 集合中是否沒有元素匹配以'a'開頭
          boolean result = Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  .stream()
                  .noneMatch(s -> s.startsWith("a"));

          limit/skip

          limit 方法用于返回前面 n 個元素。

          Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  .stream()
                  .filter(string -> !string.isEmpty())
                  .limit(3)
                  .forEach(System.out::println);

          skip 方法用于舍棄前 n 個元素。

          Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  .stream()
                  .filter(string -> !string.isEmpty())
                  .skip(1)
                  .forEach(System.out::println);

          sorted

          sorted 方法用于對流進行排序。Sorted 是一個中間操作,能夠返回一個排過序的流對象的視圖。流對象中的元素會默認按照自然順序進行排序,除非你自己指定一個 Comparator 接口來改變排序規(guī)則。

          以下代碼片段使用 filter 方法過濾掉空字符串,并對其進行自然順序排序:

          List<String> strings = Arrays.asList("abc""","bc","efg","abcd","""jkl");
          // 一定要記住, sorted 只是創(chuàng)建一個流對象排序的視圖, 而不會改變原來集合中元素的順序。
          strings
                  .stream()
                  .filter(string -> !string.isEmpty())
                  .sorted()
                  .forEach(System.out::println);
          // 輸出原始集合元素, sorted 只是創(chuàng)建排序視圖, 不影響原來集合順序
          strings
                  .stream()
                  .forEach(System.out::println);

          // 按照字符串長度進行排序, 若兩個字符串長度相同, 按照字母順序排列
          strings
                  .stream()
                  .filter(string -> !string.isEmpty())
                  // 1. 首先根據(jù)字符串長度倒序排序; 2. 然后根據(jù)字母順序排列
                  .sorted(Comparator.comparing(String::length).reversed().thenComparing(String::compareTo))
                  .forEach(System.out::println);

          以下代碼片段根據(jù) Person 姓名倒序排序,然后利用 Collectors 返回列表新列表:

          List<Person> persons = new ArrayList();
          // 1. 生成 5 個 Person 對象
          for (int i = 1; i <= 5; i++) {
              Person person = new Person(i, "name" + i);
              persons.add(person);
          }

          // 2. 對 Person 列表進行排序, 排序規(guī)則: 根據(jù) Person 姓名倒序排序, 然后利用 Collectors 返回列表新列表;
          List<Person> personList = persons
                  .stream()
                  .sorted(Comparator.comparing(Person::getName).reversed())
                  .collect(Collectors.toList());

          parallel

          流操作可以是順序的,也可以是并行的。順序操作通過單線程執(zhí)行,而并行操作則通過多線程執(zhí)行??墒褂貌⑿辛鬟M行操作來提高運行效率 parallelStream 是流并行處理程序的代替方法。
          parallelStream()本質上基于 Java7 的 Fork-Join 框架實現(xiàn),F(xiàn)ork-Join 是一個處理并行分解的高性能框架,其默認的線程數(shù)為宿主機的內(nèi)核數(shù)。

          以下實例我們使用 parallelStream 來輸出空字符串的數(shù)量:

          // 獲取空字符串的數(shù)量[parallelStream 為 Collection 接口的一個默認方法]
          Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  // parallelStream() ? 為集合創(chuàng)建并行流
                  .parallelStream()
                  .filter(string -> string.isEmpty())
                  .count();

          parallelStream 中 forEachOrdered 與 forEach 區(qū)別:

          List<String> strings = Arrays.asList("a""b""c");
          strings.stream().forEachOrdered(System.out::print);            //abc
          strings.stream().forEach(System.out::print);                   //abc
          strings.parallelStream().forEachOrdered(System.out::print);    //abc
          strings.parallelStream().forEach(System.out::print);           //bca

          特別注意:1、千萬不要任意地并行 Stream pipeline,如果源頭是來自 stream.iterate,或者中間使用了中間操作的 limit,那么并行 pipeline 也不可能提升性能。因此,在 Stream 上通過并行獲取的性能,最好是通過 ArrayList、HashMap、HashSet 和 CouncurrentHashMap 實例,數(shù)組,int 范圍和 long 范圍等。這些數(shù)據(jù)結構的共性是,都可以被精確、輕松地分成任意大小的子范圍,使并行線程中的分工變得更加輕松。2、Stream pipeline 的終止操作本質上也影響了并發(fā)執(zhí)行的效率。并行的最佳操作是做減法,用一個 Stream 的 reduce 方法,將所有從 pipeline 產(chǎn)生的元素都合并在一起,或者預先打包想 min、max、count 和 sum 這類方法。驟死式操作如 anyMatch、allMatch 和 nonMatch 也都可以并行。由 Stream 的 collect 方法執(zhí)行的操作,都是可變的減法,不是并行的最好選擇,因此并行集合的成本非常高。3、一般來說,程序中所有的并行 Stream pipeline 都是在一個通用的 fork-join 池中運行的。只要有一個 pipeline 運行異常,都是損害到系統(tǒng)中其它不相關部分的性能。因此,如果對 Stream 進行不恰當?shù)牟⑿胁僮鳎赡軐е鲁绦蜻\行失敗,或者造成性能災難。

          map

          map 方法用于映射每個元素到對應的結果。map 是一個對于流對象的中間操作,通過給定的方法,它能夠把流對象中的每一個元素對應到另外一個對象上。
          以下代碼片段使用 map 將集合元素轉為大寫 (每個元素映射到大寫)-> 降序排序 ->迭代輸出:

          Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  // 通過 stream()方法即可獲取流對象
                  .stream()
                  // 通過 filter()過濾元素
                  .filter(string -> !string.isEmpty())
                  // 通過 map()方法用于映射每個元素到對應的結果
                  .map(String::toUpperCase)
                  // 通過 sorted()方法用于對流進行排序
                  .sorted(Comparator.reverseOrder())
                  // 通過 forEach()方法迭代流中的每個數(shù)據(jù)
                  .forEach(System.out::println);

          篩選出所有專業(yè)為計算機科學的學生姓名:

          List<String> names = students
                  .stream()
                  .filter(student -> "計算機科學".equals(student.getMajor()))
                  .map(Student::getName).collect(Collectors.toList());

          計算所有專業(yè)為計算機科學學生的年齡之和:

          int totalAge = students
                  .stream()
                  .filter(student -> "計算機科學".equals(student.getMajor()))
                  .mapToInt(Student::getAge).sum();

          peek

          peek 操作接收的是一個 Consumer<T> 函數(shù)。顧名思義 peek 操作會按照 Consumer<T> 函數(shù)提供的邏輯去消費流中的每一個元素,同時有可能改變元素內(nèi)部的一些屬性。

          按照 Java 團隊的說法,peek() 方法存在的主要目的是用調(diào)試,通過 peek() 方法可以看到流中的數(shù)據(jù)經(jīng)過每個處理點時的狀態(tài)。

          Stream.of("one", "two", "three","four").filter(e -e.length() > 3)
                          .peek(e -System.out.println("Filtered value: " + e))
                          .map(String::toUpperCase)
                          .peek(e -System.out.println("Mapped value: " + e))
                          .collect(Collectors.toList());

          除去用于調(diào)試,peek() 在需要修改元素內(nèi)部狀態(tài)的場景也非常有用,比如我們想將所有 Student 的名字修改為大寫,當然也可以使用 map() 和 flatMap() 實現(xiàn),但是相比來說 peek() 更加方便,因為我們并不想替代流中的數(shù)據(jù)。

          students
                  .stream()
                  .peek(student -student.setName(student.getName().toUpperCase()))
                  .forEach(System.out::println);

          那么 peek() 和 map() 有什么區(qū)別呢?peek 接收一個 Consumer,而 map 接收一個 Function。Consumer 是沒有返回值的,它只是對 Stream 中的元素進行某些操作,但是操作之后的數(shù)據(jù)并不返回到 Stream 中,所以 Stream 中的元素還是原來的元素。而 Function 是有返回值的,這意味著對于 Stream 的元素的所有操作都會作為新的結果返回到 Stream 中。

          findFirst/findAny

          findAny 能夠從流中隨便選一個元素出來,它返回一個 Optional 類型的元素。

          Optional<Stringoptional = Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  .stream()
                  .findAny();

          findFirst 能夠從流中選第一個元素出來,它返回一個 Optional 類型的元素。

          Optional<Stringoptional = Arrays.asList("abc""","bc","efg","abcd","""jkl")
                  .stream()
                  .findFirst();

          collect

          collect 方法是一個終端操作,它接收的參數(shù)是將流中的元素累積到匯總結果的各種方式(稱為收集器)。

          Collectors 工具類提供了許多靜態(tài)工具方法來為大多數(shù)常用的用戶用例創(chuàng)建收集器,比如將元素裝進一個集合中、將元素分組、根據(jù)不同標準對元素進行匯總等。

          Collectors.joining()

          Collectors.joining()方法以遭遇元素的順序拼接元素。我們可以傳遞可選的拼接字符串、前綴和后綴。

          List<String> strings = Arrays.asList("abc""","bc","efg","abcd","""jkl");
          // 篩選列表
          List<String> filtered = strings
                 .stream()
                 .filter(string -> !string.isEmpty())
                 .collect(Collectors.toList());

          // 合并字符串
          String mergedString = strings
                 .stream()
                 .filter(string -> !string.isEmpty())
                 .collect(Collectors.joining(","));

          Collectors.groupingBy

          Collectors.groupingBy 方法根據(jù)項目的一個屬性的值對流中的項目作問組,并將屬性值作為結果 Map 的鍵。

          1. List 里面的對象元素,以某個屬性來分組。

          // 按學校對學生進行分組:
          Map<StringList<Student>> groups = students
                  .stream()
                  .collect(Collectors.groupingBy(Student::getSchool));

          // 多級分組, 在按學校分組的基礎之上再按照專業(yè)進行分組
          Map<StringMap<StringList<Student>>> groups2 = students
                  .stream()
                  .collect(
                          Collectors.groupingBy(Student::getSchool,  // 一級分組,按學校
                                  Collectors.groupingBy(Student::getMajor)));  // 二級分組,按專業(yè)
          1. 統(tǒng)計 List 集合重復元素出現(xiàn)次數(shù)。

          List<String> items = Arrays.asList("apple""apple""banana""apple""orange""banana""papaya");

          // 方式一
          Map<String, Long> result = items
                  .stream()
                  // Function.identity() 返回一個輸出跟輸入一樣的 Lambda 表達式對象, 等價于形如 t -> t 形式的 Lambda 表達式.
                  .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

          // 方式二
          Map<String, Long> result2 = items
                  .stream()
                  // Collectors.counting() 計算流中數(shù)量
                  .collect(Collectors.groupingBy(String::toString, Collectors.counting()));

          //  Output :
          //  {papaya=1, orange=1, banana=2, apple=3}

          統(tǒng)計每個組的個數(shù):

          Map<String, Long> groups = students
                  .stream()
                  .collect(Collectors.groupingBy(Student::getSchool, Collectors.counting()));
          1. 累加求和

          // 統(tǒng)計相同姓名, 總年齡大小
          Map<String, Integer> sumMap = persons
                  .stream()
                  // Collectors.summingInt() 返回流中整數(shù)屬性求和
                  .collect(Collectors.groupingBy(Person::getName, Collectors.summingInt(Person::getAge)));
          1. 轉換

          // 按照姓名對學生分布組,并只保留員工的年齡
          Map<StringList<String>> nameMap = persons
                  .stream()
                  .collect(Collectors.groupingBy(Person::getName,
                          Collectors.mapping(Employee::getName,   // 下游收集器
                                  Collectors.toList()))); // 更下游的收集器

          Collectors.toMap

          Collectors.toMap 方法將 List 轉 Map。

          // 根據(jù) Person 年齡生成 Map
          Map<Integer, Person> personMap = persons
                  .stream()
                  .collect(Collectors.toMap(Person::getAge, person -> person));

          // account -> account 是一個返回本身的 lambda 表達式, 其實還可以使用 Function 接口中的一個默認方法代替, 使整個方法更簡潔優(yōu)雅.
          Map<Integer, Person> personMap = persons
                  .stream()
                  .collect(Collectors.toMap(Person::getAge, Function.identity()));

          當 key 重復時,會拋出異常:java.lang.IllegalStateException: Duplicate key **

          // 針對重復 key 的, 覆蓋之前的 value
          Map<Integer, Person> personMap = persons
                  .stream()
                  .collect(Collectors.toMap(Person::getAge, Function.identity()(person, person2) -> person2));

          指定具體收集的 map:

          Map<Integer, Person> personMap = persons
                  .stream()
                  .collect(Collectors.toMap(Person::getAge, Function.identity()(person, person2) -> person2LinkedHashMap::new));

          當 value 為 null 時,會拋出異常:java.lang.NullPointerException[Collectors.toMap 底層是基于 Map.merge 方法來實現(xiàn)的,而 merge 中 value 是不能為 null 的,如果為 null,就會拋出空指針異常。]

          Map<Integer, String> personMap = persons
                  .stream()
                  .collect(Collectors.toMap(Person::getAge, Person::getName, (person, person2) -> person2));


          // 1. 解決方式 1: 用 for 循環(huán)的方式亦或是 forEach 的方式
          Map<Integer, String> personMap = new HashMap<>();
          for (Person person : persons) {
              personMap.put(person.getAge(), person.getName());
          }

          // 2. 解決方式 2: 使用 stream 的 collect 的重載方法
          Map<Integer, String> personMap = persons
                  .stream()
                  .collect(HashMap::new, (m, v) -> m.put(v.getAge(), v.getName()), HashMap::putAll);
          Collectors.collectingAndThen

          Collectors.collectingAndThen 方法主要用于轉換函數(shù)返回的類型。

          List 里面的對象元素,以某個屬性去除重復元素。

          List<Person> unique = persons
                  .stream()
                  .collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparingInt(Person::getAge))), ArrayList::new));

          Collectors.partitioningBy

          Collectors.partitioningBy 方法主要用于根據(jù)對流中每個項目應用謂詞的結果來對項目進行分區(qū)。

          “年齡小于 18”進行分組后可以看到,不到 18 歲的未成年人是一組,成年人是另外一組。

          Map<Boolean, List<Person>> groupBy = persons
                  .stream()
                  .collect(Collectors.partitioningBy(o -> o.getAge() >= 18));

          Collectors 收集器靜態(tài)方法:


          Collectors 收集器靜態(tài)方法

          Collectors 收集器靜態(tài)方法

          數(shù)值流的使用

          在 Stream 里元素都是對象,那么,當我們操作一個數(shù)字流的時候就不得不考慮一個問題,拆箱和裝箱。雖然自動拆箱不需要我們處理,但依舊有隱含的成本在里面。Java8 引入了 3 個原始類型特化流接口來解決這個問題:IntStream、DoubleStream、LongStream,分別將流中的元素特化為 int、long、double,從而避免了暗含的裝箱成本。

          將對象流映射為數(shù)值流

          // 將對象流映射為數(shù)值流
          IntStream intStream = persons
                  .stream()
                  .mapToInt(Person::getAge);

          默認值 OptinalInt

          由于數(shù)值流經(jīng)常會有默認值,比如默認為 0。數(shù)值特化流的終端操作會返回一個 OptinalXXX 對象而不是數(shù)值。

          // 每種數(shù)值流都提供了數(shù)值計算函數(shù), 如 max、min、sum 等
          OptionalInt optionalInt = persons
                  .stream()
                  .mapToInt(Person::getAge)
                  .max();

          int max = optionalInt.orElse(1);

          生成一個數(shù)值范圍流

          // 創(chuàng)建一個包含兩端的數(shù)值流, 比如 1 到 10, 包含 10:
          IntStream intStream = IntStream.rangeClosed(110);
          // 創(chuàng)建一個不包含結尾的數(shù)值流, 比如 1 到 9:
          IntStream range = IntStream.range(19);

          將數(shù)值流轉回對象流

          // 將數(shù)值流轉回對象流
          Stream<Integer> boxed = intStream.boxed();

          流的扁平化

          案例:對給定單詞列表 [“Hello”,”World”],你想返回列表[“H”,”e”,”l”,”o”,”W”,”r”,”d”]

          方法一:錯誤方式

          String[] words = new String[]{"Hello""World"};
          List<String[]> a = Arrays.stream(words)
                  .map(word -> word.split(""))
                  .distinct()
                  .collect(Collectors.toList());
          a.forEach(System.out::print);

          // Output
          // [Ljava.lang.String;@12edcd21[Ljava.lang.String;@34c45dca

          返回一個包含兩個 String[]的 list,傳遞給 map 方法的 lambda 為每個單詞生成了一個 String[]。因此,map 返回的流實際上是 Stream<String[]>類型的。

          方法二:正確方式

          String[] words = new String[]{"Hello""World"};
          List<String> a = Arrays.stream(words)
                  .map(word -> word.split(""))
                  .flatMap(Arrays::stream)
                  .distinct()
                  .collect(Collectors.toList());
          a.forEach(System.out::print);

          // Output
          // HeloWrd


          使用 flatMap 方法的效果是,各個數(shù)組并不是分別映射一個流,而是映射成流的內(nèi)容,所有使用 map(Array::stream)時生成的單個流被合并起來,即扁平化為一個流。


          最近給大家找了  Vue進階


          資源,怎么領?。?/span>


          掃二維碼,加我微信,回復:Vue進階

           注意,不要亂回復 

          沒錯,不是機器人
          記得一定要等待,等待才有好東西
          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲中文字幕一二三无码欧美 | 男女抽插网网站 | 国产精选久久久60086 | 豆花视频在线欧美亚洲自拍 | 婷婷丁香五月婷婷 |