<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 實戰 Groovy: for each 剖析 _使用最熟悉的方法進行迭代_ 在這一期的 [_實戰 Groovy_](http://www.ibm.com/developerworks/cn/java/j-pg/) 中,Scott Davis 提出了一組非常好的遍歷方法,這些方法可以遍歷數組、列表、文件、URL 以及很多其它內容。最令人印象深刻的是,Groovy 提供了一種一致的機制來遍歷所有這些集合和其它內容。 迭代是編程的基礎。您經常會遇到需要進行逐項遍歷的內容,比如 `List`、`File` 和 JDBC `ResultSet`。Java 語言幾乎總是提供了某種方法幫助您逐項遍歷所需的內容,但令人沮喪的是,它并沒有給出一種標準方法。Groovy 的迭代方法非常實用,在這一點上,Groovy 編程與 Java 編程截然不同。通過一些代碼示例(可從 [下載](#download) 小節獲得),本文將介紹 Groovy 的萬能的 `each()` 方法,從而將 Java 語言的那些迭代怪癖拋在腦后。 ## Java 迭代策略 假設您有一個 Java 編程語言的 `java.util.List`。清單 1 展示了在 Java 語言中如何使用編程實現迭代: ##### 清單 1\. Java 列表迭代 ``` import java.util.*; public class ListTest{ public static void main(String[] args){ List<String> list = new ArrayList<String>(); list.add("Java"); list.add("Groovy"); list.add("JavaScript"); for(Iterator<String> i = list.iterator(); i.hasNext();){ String language = i.next(); System.out.println("I know " + language); } } } ``` 由于提供了大部分集合類都可以共享的 `java.lang.Iterable` 接口,您可以使用相同的方法遍歷 `java.util.Set` 或 `java.util.Queue`。 ## 關于本系列 Groovy 是一款運行在 Java 平臺之上的現代編程語言。它能夠與現有 Java 代碼無縫集成,同時引入了閉包和元編程等出色的新特性。簡而言之,Groovy 類似于 21 世紀的 Java 語言。 如果要將新工具集成到開發工具箱中,最關鍵的是理解什么時候需要使用它以及什么時候不適合使用它。Groovy 可以變得非常強大,但前提是它被適當地應用到合適的場景中。因此,[_實戰 Groovy_](http://www.ibm.com/developerworks/cn/java/j-pg/) 系列旨在展示 Groovy 的實際使用,以及何時和如何成功應用它。 現在,假設該語言存儲在 `java.util.Map` 中。在編譯時,嘗試對 `Map` 獲取 `Iterator` 會導致失敗 — `Map` 并沒有實現 `Iterable` 接口。幸運的是,可以調用 `map.keySet()` 返回一個 `Set`,然后就可以繼續處理。這些小差異可能會影響您的速度,但不會妨礙您的前進。需要注意的是,`List`、`Set` 和 `Queue` 實現了 `Iterable`,但是 `Map` 沒有 — 即使它們位于相同的 `java.util` 包中。 現在假設該語言存在于 `String` 數組中。數組是一種數據結構,而不是類。不能對 `String` 數組調用 `.iterator()`,因此必須使用稍微不同的迭代策略。您再一次受到阻礙,但可以使用如清單 2 所示的方法解決問題: ##### 清單 2\. Java 數組迭代 ``` public class ArrayTest{ public static void main(String[] args){ String[] list = {"Java", "Groovy", "JavaScript"}; for(int i = 0; i < list.length; i++){ String language = list[i]; System.out.println("I know " + language); } } } ``` 但是等一下 — 使用 Java 5 引入的 for-each 語法怎么樣(參見 [參考資料](#resources))?它可以處理任何實現 `Iterable` 的類和數組,如清單 3 所示: ##### 清單 3\. Java 語言的 for-each 迭代 ``` import java.util.*; public class MixedTest{ public static void main(String[] args){ List<String> list = new ArrayList<String>(); list.add("Java"); list.add("Groovy"); list.add("JavaScript"); for(String language: list){ System.out.println("I know " + language); } String[] list2 = {"Java", "Groovy", "JavaScript"}; for(String language: list2){ System.out.println("I know " + language); } } } ``` 因此,您可以使用相同的方法遍歷數組和集合(`Map` 除外)。但是如果語言存儲在 `java.io.File`,那該怎么辦?如果存儲在 JDBC `ResultSet`,或者存儲在 XML 文檔、`java.util.StringTokenizer` 中呢?面對每一種情況,必須使用一種稍有不同的迭代策略。這樣做并不是有什么特殊目的 — 而是因為不同的 API 是由不同的開發人員在不同的時期開發的 — 但事實是,您必須了解 6 個 Java 迭代策略,特別是使用這些策略的特殊情況。 Eric S. Raymond 在他的 _The Art of Unix Programming_(參見 [參考資料](#resources))一書中解釋了 “最少意外原則”。他寫道,“要設計可用的接口,最好不要設計全新的接口模型。新鮮的東西總是難以入門;會為用戶帶來學習的負擔,因此應當盡量減少新內容。”Groovy 對迭代的態度正是采納了 Raymond 的觀點。在 Groovy 中遍歷幾乎任何結構時,您只需要使用 `each()` 這一種方法。 * * * ## Groovy 中的列表迭代 首先,我將 [清單 3](#listing3) 中的 `List` 重構為 Groovy。在這里,只需要直接對列表調用 `each()` 方法并傳遞一個閉包,而不是將 `List` 轉換成 `for` 循環(順便提一句,這樣做并不是特別具有面向對象的特征,不是嗎)。 創建一個名為 listTest.groovy 的文件并添加清單 4 中的代碼: ##### 清單 4\. Groovy 列表迭代 ``` def list = ["Java", "Groovy", "JavaScript"] list.each{language-> println language } ``` 清單 4 中的第一行是 Groovy 用于構建 `java.util.ArrayList` 的便捷語法。可以將 `println list.class` 添加到此腳本來驗證這一點。接下來,只需對列表調用 `each()`,并在閉包體內輸出 `language` 變量。在閉包的開始處使用 `language-&gt;` 語句命名 `language` 變量。如果沒有提供變量名,Groovy 提供了一個默認名稱 `it`。在命令行提示符中輸入 `groovy listTest` 運行 listTest.groovy。 清單 5 是經過簡化的 [清單 4](#listing4) 代碼版本: ##### 清單 5\. 使用 Groovy 的 `it` 變量的迭代 ``` // shorter, using the default it variable def list = ["Java", "Groovy", "JavaScript"] list.each{ println it } // shorter still, using an anonymous list ["Java", "Groovy", "JavaScript"].each{ println it } ``` Groovy 允許您對數組和 `List` 交替使用 `each()` 方法。為了將 `ArrayList` 改為 `String` 數組,必須將 `as String[]` 添加到行末,如清單 6 所示: ##### 清單 6\. Groovy 數組迭代 ``` def list = ["Java", "Groovy", "JavaScript"] as String[] list.each{println it} ``` 在 Groovy 中普遍使用 `each()` 方法,并且 getter 語法非常便捷(`getClass()` 和 `class` 是相同的調用),這使您能夠編寫既簡潔又富有表達性的代碼。例如,假設您希望利用反射顯示給定類的所有公共方法。清單 7 展示了這個例子: ##### 清單 7\. Groovy 反射 ``` def s = "Hello World" println s println s.class s.class.methods.each{println it} //output: $ groovy reflectionTest.groovy Hello World class java.lang.String public int java.lang.String.hashCode() public volatile int java.lang.String.compareTo(java.lang.Object) public int java.lang.String.compareTo(java.lang.String) public boolean java.lang.String.equals(java.lang.Object) ... ``` 腳本的最后一行調用 `getClass()` 方法。`java.lang.Class` 提供了一個 `getMethods()` 方法,后者返回一個數組。通過將這些操作串連起來并對 `Method` 的結果數組調用 `each()`,您只使用了一行代碼就完成了大量工作。 但是,與 Java for-each 語句不同的是,萬能的 `each()` 方法并不僅限于 `List` 和數組。在 Java 語言中,故事到此結束。然而,在 Groovy 中,故事才剛剛開始。 * * * ## Map 迭代 從前文可以看到,在 Java 語言中,無法直接迭代 `Map`。在 Groovy 中,這完全不是問題,如清單 8 所示: ##### 清單 8\. Groovy map 迭代 ``` def map = ["Java":"server", "Groovy":"server", "JavaScript":"web"] map.each{ println it } ``` 要處理名稱/值對,可以使用隱式的 `getKey()` 和 `getValue()` 方法,或在包的開頭部分顯式地命名變量,如清單 9 所示: ##### 清單 9\. 從 map 獲得鍵和值 ``` def map = ["Java":"server", "Groovy":"server", "JavaScript":"web"] map.each{ println it.key println it.value } map.each{k,v-> println k println v } ``` 可以看到,迭代 `Map` 和迭代其它任何集合一樣自然。 在繼續研究下一個迭代例子前,應當了解 Groovy 中有關 `Map` 的另一個語法。與在 Java 語言中調用 `map.get("Java")` 不一樣,可以簡化對 `map.Java` 的調用,如清單 10 所示: ##### 清單 10\. 獲得 map 值 ``` def map = ["Java":"server", "Groovy":"server", "JavaScript":"web"] //identical results println map.get("Java") println map.Java ``` 不可否認,Groovy 針對 `Map` 的這種便捷語法非常酷,但這也是在對 `Map` 使用反射時引起一些常見問題的原因。對 `list.class` 的調用將生成 `java.util.ArrayList`,而調用 `map.class` 返回 `null`。這是因為獲得 map 元素的便捷方法覆蓋了實際的 getter 調用。`Map` 中的元素都不具有 `class` 鍵,因此調用實際會返回 `null`,如清單 11 的示例所示: ##### 清單 11\. Groovy map 和 `null` ``` def list = ["Java", "Groovy", "JavaScript"] println list.class // java.util.ArrayList def map = ["Java":"server", "Groovy":"server", "JavaScript":"web"] println map.class // null map.class = "I am a map element" println map.class // I am a map element println map.getClass() // class java.util.LinkedHashMap ``` 這是 Groovy 比較罕見的打破 “最少意外原則” 的情況,但是由于從 map 獲取元素要比使用反射更加常見,因此我可以接受這一例外。 * * * ## String 迭代 現在您已經熟悉 `each()` 方法了,它可以出現在所有相關的位置。假設您希望迭代一個 `String`,并且是逐一迭代字符,那么馬上可以使用 `each()` 方法。如清單 12 所示: ##### 清單 12\. `String` 迭代 ``` def name = "Jane Smith" name.each{letter-> println letter } ``` 這提供了所有的可能性,比如使用下劃線替代所有空格,如清單 13 所示: ##### 清單 13\. 使用下劃線替代空格 ``` def name = "Jane Smith" println "replace spaces" name.each{ if(it == " "){ print "_" }else{ print it } } // output Jane_Smith ``` 當然,在替換一個單個字母時,Groovy 提供了一個更加簡潔的替換方法。您可以將清單 13 中的所有代碼合并為一行代碼:`"Jane Smith".replace(" ", "_")`。但是對于更復雜的 `String` 操作,`each()` 方法是最佳選擇。 * * * ## Range 迭代 Groovy 提供了原生的 `Range` 類型,可以直接迭代。使用兩個點分隔的所有內容(比如 `1..10`)都是一個 `Range`。清單 14 展示了這個例子: ##### 清單 14\. Range 迭代 ``` def range = 5..10 range.each{ println it } //output: 5 6 7 8 9 10 ``` `Range` 不局限于簡單的 `Integer`。考慮清單 15 在的代碼,其中迭代 `Date` 的 `Range`: ##### 清單 15\. `Date` 迭代 ``` def today = new Date() def nextWeek = today + 7 (today..nextWeek).each{ println it } //output: Thu Mar 12 04:49:35 MDT 2009 Fri Mar 13 04:49:35 MDT 2009 Sat Mar 14 04:49:35 MDT 2009 Sun Mar 15 04:49:35 MDT 2009 Mon Mar 16 04:49:35 MDT 2009 Tue Mar 17 04:49:35 MDT 2009 Wed Mar 18 04:49:35 MDT 2009 Thu Mar 19 04:49:35 MDT 2009 ``` 可以看到,`each()` 準確地出現在您所期望的位置。Java 語言缺乏原生的 `Range` 類型,但是提供了一個類似地概念,采取 `enum` 的形式。毫不奇怪,在這里 `each()` 仍然派得上用場。 * * * ## Enumeration 類型 Java `enum` 是按照特定順序保存的隨意的值集合。清單 16 展示了 `each()` 方法如何自然地配合 `enum`,就好象它在處理 `Range` 操作符一樣: ##### 清單 16\. `enum` 迭代 ``` enum DAY{ MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } DAY.each{ println it } (DAY.MONDAY..DAY.FRIDAY).each{ println it } ``` 在 Groovy 中,有些情況下,`each()` 這個名稱遠未能表達它的強大功能。在下面的例子中,將看到使用特定于所用上下文的方法對 `each()` 方法進行修飾。Groovy `eachRow()` 方法就是一個很好的例子。 * * * ## SQL 迭代 在處理關系數據庫表時,經常會說 “我需要針對表中的每一行執行操作”。比較一下前面的例子。您很可能會說 “我需要對列表中的每一種語言執行一些操作”。根據這個道理,`groovy.sql.Sql` 對象提供了一個 `eachRow()` 方法,如清單 17 所示: ##### 清單 17\. `ResultSet` 迭代 ``` import groovy.sql.* def sql = Sql.newInstance( "jdbc:derby://localhost:1527/MyDbTest;create=true", "username", "password", "org.apache.derby.jdbc.ClientDriver") println("grab a specific field") sql.eachRow("select name from languages"){ row -> println row.name } println("grab all fields") sql.eachRow("select * from languages"){ row -> println("Name: ${row.name}") println("Version: ${row.version}") println("URL: ${row.url}\n") } ``` 該腳本的第一行代碼實例化了一個新的 `Sql` 對象:設置 JDBC 連接字符串、用戶名、密碼和 JDBC 驅動器類。這時,可以調用 `eachRow()` 方法,傳遞 SQL `select` 語句作為一個方法參數。在閉包內部,可以引用列名(`name`、`version`、`url`),就好像實際存在 `getName()`、`getVersion()` 和 `getUrl()` 方法一樣。 這顯然要比 Java 語言中的等效方法更加清晰。在 Java 中,必須創建單獨的 `DriverManager`、`Connection`、`Statement` 和 `JDBCResultSet`,然后必須在嵌套的 `try`/`catch`/`finally` 塊中將它們全部清除。 對于 `Sql` 對象,您會認為 `each()` 或 `eachRow()` 都是一個合理的方法名。但是在接下來的示例中,我想您會認為 `each()` 這個名稱并不能充分表達它的功能。 * * * ## 文件迭代 我從未想過使用原始的 Java 代碼逐行遍歷 `java.io.File`。當我完成了所有的嵌套的 `BufferedReader` 和 `FileReader` 后(更別提每個流程末尾的所有異常處理),我已經忘記最初的目的是什么。 清單 18 展示了使用 Java 語言完成的整個過程: ##### 清單 18\. Java 文件迭代 ``` import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class WalkFile { public static void main(String[] args) { BufferedReader br = null; try { br = new BufferedReader(new FileReader("languages.txt")); String line = null; while((line = br.readLine()) != null) { System.out.println("I know " + line); } } catch(FileNotFoundException e) { e.printStackTrace(); } catch(IOException e) { e.printStackTrace(); } finally { if(br != null) { try { br.close(); } catch(IOException e) { e.printStackTrace(); } } } } } ``` 清單 19 展示了 Groovy 中的等效過程: ##### 清單 19\. Groovy 文件迭代 ``` def f = new File("languages.txt") f.eachLine{language-> println "I know ${language}" } ``` 這正是 Groovy 的簡潔性真正擅長的方面。現在,我希望您了解為什么我將 Groovy 稱為 “[Java 程序員的 DSL](http://www.ibm.com/developerworks/cn/java/j-pg02179.html)”。 注意,我在 Groovy 和 Java 語言中同時處理同一個 `java.io.File` 類。如果該文件不存在,那么 Groovy 代碼將拋出和 Java 代碼相同的 `FileNotFoundException` 異常。區別在于,Groovy 沒有已檢測的異常。在 `try`/`catch`/`finally` 塊中封裝 `eachLine()` 結構是我自己的愛好 — 而不是一項語言需求。對于一個簡單的命令行腳本中,我欣賞 [清單 19](#listing19) 中的代碼的簡潔性。如果我在運行應用服務的同時執行相同的迭代,我不能對這些異常坐視不管。我將在與 Java 版本相同的 `try/catch` 塊中封裝 `eachLine()` 塊。 `File` 類對 `each()` 方法進行了一些修改。其中之一就是 `splitEachLine(String separator, Closure closure)`。這意味著您不僅可以逐行遍歷文件,同時還可以將它分為不同的標記。清單 20 展示了一個例子: ##### 清單 20\. 分解文件的每一行 ``` // languages.txt // notice the space between the language and the version Java 1.5 Groovy 1.6 JavaScript 1.x // splitTest.groovy def f = new File("languages.txt") f.splitEachLine(" "){words-> words.each{ println it } } // output Java 1.5 Groovy 1.6 JavaScript 1.x ``` 如果處理的是二進制文件,Groovy 還提供了一個 `eachByte()` 方法。 當然,Java 語言中的 `File` 并不總是一個文件 — 有時是一個目錄。Groovy 還提供了一些 `each()` 修改以處理子目錄。 * * * ## 目錄迭代 使用 Groovy 代替 shell 腳本(或批處理腳本)非常容易,因為您能夠方便地訪問文件系統。要獲得當前目錄的目錄列表,參見清單 21: ##### 清單 21\. 目錄迭代 ``` def dir = new File(".") dir.eachFile{file-> println file } ``` `eachFile()` 方法同時返回了文件和子目錄。使用 Java 語言的 `isFile()` 和 `isDirectory()` 方法,可以完成更復雜的事情。清單 22 展示了一個例子: ##### 清單 22\. 分離文件和目錄 ``` def dir = new File(".") dir.eachFile{file-> if(file.isFile()){ println "FILE: ${file}" }else if(file.isDirectory()){ println "DIR: ${file}" }else{ println "Uh, I'm not sure what it is..." } } ``` 由于兩種 Java 方法都返回 `boolean` 值,可以在代碼中添加一個 Java 三元操作符。清單 23 展示了一個例子: ##### 清單 23\. 三元操作符 ``` def dir = new File(".") dir.eachFile{file-> println file.isDirectory() ? "DIR: ${file}" : "FILE: ${file}" } ``` 如果只對目錄有興趣,那么可以使用 `eachDir()` 而不是 `eachFile()`。還提供了 `eachDirMatch()` 和 `eachDirRecurse()` 方法。 可以看到,對 `File` 僅使用 `each()` 方法并不能提供足夠的含義。典型 `each()` 方法的語義保存在 `File` 中,但是方法名更具有描述性,從而提供更多有關這個高級功能的信息。 * * * ## URL 迭代 理解了如何遍歷 `File` 后,可以使用相同的原則遍歷 HTTP 請求的響應。Groovy 為 `java.net.URL` 提供了一個方便的(和熟悉的)`eachLine()` 方法。 例如,清單 24 將逐行遍歷 ibm.com 主頁的 HTML: ##### 清單 24\. URL 迭代 ``` def url = new URL("http://www.ibm.com") url.eachLine{line-> println line } ``` 當然,如果這就是您的目的的話,Groovy 提供了一個只包含一行代碼的解決辦法,這主要歸功于 `toURL()` 方法,它被添加到所有 `Strings`:`"http://www.ibm.com".toURL().eachLine{ println it }`。 但是,如果希望對 HTTP 響應執行一些更有用的操作,該怎么辦呢?具體來講,如果發出的請求指向一個 RESTful Web 服務,而該服務包含您要解析的 XML,該怎么做呢?`each()` 方法將在這種情況下提供幫助。 * * * ## XML 迭代 您已經了解了如何對文件和 URL 使用 `eachLine()` 方法。XML 給出了一個稍微有些不同的問題 — 與逐行遍歷 XML 文檔相比,您可能更希望對逐個元素進行遍歷。 例如,假設您的語言列表存儲在名為 languages.xml 的文件中,如清單 25 所示: ##### 清單 25\. languages.xml 文件 ``` <langs> <language>Java</language> <language>Groovy</language> <language>JavaScript</language> </langs> ``` Groovy 提供了一個 `each()` 方法,但是需要做一些修改。如果使用名為 `XmlSlurper` 的原生 Groovy 類解析 XML,那么可以使用 `each()` 遍歷元素。參見清單 26 所示的例子: ##### 清單 26\. XML 迭代 ``` def langs = new XmlSlurper().parse("languages.xml") langs.language.each{ println it } //output Java Groovy JavaScript ``` `langs.language.each` 語句從名為 `&lt;language&gt;` 的 `&lt;langs&gt;` 提取所有元素。如果同時擁有 `&lt;format&gt;` 和 `&lt;server&gt;` 元素,它們將不會出現在 `each()` 方法的輸出中。 如果覺得這還不夠的話,那么假設這個 XML 是通過一個 RESTful Web 服務的形式獲得,而不是文件系統中的文件。使用一個 URL 替換文件的路徑,其余代碼仍然保持不變,如清單 27 所示: ##### 清單 27\. Web 服務調用的 XML 迭代 ``` def langs = new XmlSlurper().parse("http://somewhere.com/languages") langs.language.each{ println it } ``` 這真是個好方法,`each()` 方法在這里用得很好,不是嗎? * * * ## 結束語 在使用 `each()` 方法的整個過程中,最妙的部分在于它只需要很少的工作就可以處理大量 Groovy 內容。解了 `each()` 方法之后,Groovy 中的迭代就易如反掌了。正如 Raymond 所說,這正是關鍵所在。一旦了解了如何遍歷 `List`,那么很快就會掌握如何遍歷數組、`Map`、`String`、`Range`、`enum`、SQL `ResultSet`、`File`、目錄和 `URL`,甚至是 XML 文檔的元素。 本文的最后一個示例簡單提到使用 `XmlSlurper` 實現 XML 解析。在下一期文章中,我將繼續討論這個問題,并展示使用 Groovy 進行 XML 解析有多么簡單!您將看到 `XmlParser` 和 `XmlSlurper` 的實際使用,并更好地了解 Groovy 為什么提供兩個類似但又略有不同的類實現 XML 解析。到那時,希望您能發現 Groovy 的更多實際應用。 * * * ## 下載 | 描述 | 名字 | 大小 | | --- | --- | --- | | 本文源代碼 | [j-pg04149.zip](http://www.ibm.com/developerworks/apps/download/index.jsp?contentid=394595&filename=j-pg04149.zip&method=http&locale=zh_CN) | 17KB |
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看