精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

天天在用Stream,那你知道如此強大的Stream的實現原理嗎?

開發 后端
自動并行又是怎么做到的,線程個數是多少?本節我們學習Stream流水線的原理,這是Stream實現的關鍵所在。

我們已經學會如何使用Stream API,用起來真的很爽,但簡潔的方法下面似乎隱藏著無盡的秘密,如此強大的API是如何實現的呢?

比如Pipeline是怎么執行的,每次方法調用都會導致一次迭代嗎?自動并行又是怎么做到的,線程個數是多少?本節我們學習Stream流水線的原理,這是Stream實現的關鍵所在。

首先回顧一下容器執行Lambda表達式的方式,以ArrayList.forEach()方法為例,具體代碼如下: 

  1. // ArrayList.forEach()  
  2. public void forEach(Consumer<? super E> action) {  
  3.     ... 
  4.      for (int i=0modCount == expectedModCount && i < size; i++) {  
  5.         action.accept(elementData[i]);// 回調方法  
  6.     }  
  7.     ...  

我們看到ArrayList.forEach()方法的主要邏輯就是一個for循環,在該for循環里不斷調用action.accept()回調方法完成對元素的遍歷。

這完全沒有什么新奇之處,回調方法在Java GUI的監聽器中廣泛使用。Lambda表達式的作用就是相當于一個回調方法,這很好理解。

Stream API中大量使用Lambda表達式作為回調方法,但這并不是關鍵。理解Stream我們更關心的是另外兩個問題:流水線和自動并行。使用Stream或許很容易寫入如下形式的代碼: 

  1. int longestStringLengthStartingWithA  
  2.         = strings.stream()  
  3.               .filter(s -> s.startsWith("A"))  
  4.               .mapToInt(String::length)  
  5.               .max(); 

上述代碼求出以字母A開頭的字符串的最大長度,一種直白的方式是為每一次函數調用都執一次迭代,這樣做能夠實現功能,但效率上肯定是無法接受的。

類庫的實現著使用流水線(Pipeline)的方式巧妙的避免了多次迭代,其基本思想是在一次迭代中盡可能多的執行用戶指定的操作。為講解方便我們匯總了Stream的所有操作。

Stream操作分類
中間操作(Intermediate operations) 無狀態(Stateless) unordered() filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek()
有狀態(Stateful) distinct() sorted() sorted() limit() skip()
結束操作(Terminal operations) 非短路操作 forEach() forEachOrdered() toArray() reduce() collect() max() min() count()
短路操作(short-circuiting) anyMatch() allMatch() noneMatch() findFirst() findAny()

Stream上的所有操作分為兩類:中間操作和結束操作,中間操作只是一種標記,只有結束操作才會觸發實際計算。中間操作又可以分為無狀態的(Stateless)和有狀態的(Stateful),無狀態中間操作是指元素的處理不受前面元素的影響,而有狀態的中間操作必須等到所有元素處理之后才知道最終結果。

比如排序是有狀態操作,在讀取所有元素之前并不能確定排序結果;結束操作又可以分為短路操作和非短路操作,短路操作是指不用處理全部元素就可以返回結果,比如找到第一個滿足條件的元素。之所以要進行如此精細的劃分,是因為底層對每一種情況的處理方式不同。

為了更好的理解流的中間操作和終端操作,可以通過下面的兩段代碼來看他們的執行過程。 

  1. IntStream.range(1, 10)  
  2.    .peek(x -> System.out.print("\nA" + x))  
  3.    .limit(3)  
  4.    .peek(x -> System.out.print("B" + x))  
  5.    .forEach(x -> System.out.print("C" + x)); 

輸出為: 

  1. A1B1C1  
  2. A2B2C2  
  3. A3B3C3 

中間操作是懶惰的,也就是中間操作不會對數據做任何操作,直到遇到了最終操作。而最終操作,都是比較熱情的。他們會往前回溯所有的中間操作。也就是當執行到最后的forEach操作的時候,它會回溯到它的上一步中間操作,上一步中間操作,又會回溯到上上一步的中間操作,...,直到最初的第一步。

第一次forEach執行的時候,會回溯peek 操作,然后peek會回溯更上一步的limit操作,然后limit會回溯更上一步的peek操作,頂層沒有操作了,開始自上向下開始執行,輸出:A1B1C1 第二次forEach執行的時候,然后會回溯peek 操作,然后peek會回溯更上一步的limit操作,然后limit會回溯更上一步的peek操作,頂層沒有操作了,開始自上向下開始執行,輸出:A2B2C2

... 當第四次forEach執行的時候,然后會回溯peek 操作,然后peek會回溯更上一步的limit操作,到limit的時候,發現limit(3)這個job已經完成,這里就相當于循環里面的break操作,跳出來終止循環。

再來看第二段代碼: 

  1. IntStream.range(1, 10)  
  2.    .peek(x -> System.out.print("\nA" + x))  
  3.    .skip(6)  
  4.    .peek(x -> System.out.print("B" + x))  
  5.    .forEach(x -> System.out.print("C" + x)); 

輸出為: 

  1. A1  
  2. A2  
  3. A3  
  4. A4  
  5. A5  
  6. A6  
  7. A7B7C7  
  8. A8B8C8  
  9. A9B9C9 

第一次forEach執行的時候,會回溯peek操作,然后peek會回溯更上一步的skip操作,skip回溯到上一步的peek操作,頂層沒有操作了,開始自上向下開始執行,執行到skip的時候,因為執行到skip,這個操作的意思就是跳過,下面的都不要執行了,也就是就相當于循環里面的continue,結束本次循環。輸出:A1

第二次forEach執行的時候,會回溯peek操作,然后peek會回溯更上一步的skip操作,skip回溯到上一步的peek操作,頂層沒有操作了,開始自上向下開始執行,執行到skip的時候,發現這是第二次skip,結束本次循環。輸出:A2

...

第七次forEach執行的時候,會回溯peek操作,然后peek會回溯更上一步的skip操作,skip回溯到上一步的peek操作,頂層沒有操作了,開始自上向下開始執行,執行到skip的時候,發現這是第七次skip,已經大于6了,它已經執行完了skip(6)的job了。這次skip就直接跳過,繼續執行下面的操作。輸出:A7B7C7

...直到循環結束。

面試題推薦:100期面試題匯總

一種直白的實現方式

仍然考慮上述求最長字符串的程序,一種直白的流水線實現方式是為每一次函數調用都執一次迭代,并將處理中間結果放到某種數據結構中(比如數組,容器等)。

具體說來,就是調用filter()方法后立即執行,選出所有以A開頭的字符串并放到一個列表list1中,之后讓list1傳遞給mapToInt()方法并立即執行,生成的結果放到list2中,最后遍歷list2找出最大的數字作為最終結果。程序的執行流程如如所示:

這樣做實現起來非常簡單直觀,但有兩個明顯的弊端:

  1.  迭代次數多。迭代次數跟函數調用的次數相等。
  2.  頻繁產生中間結果。每次函數調用都產生一次中間結果,存儲開銷無法接受。

這些弊端使得效率底下,根本無法接受。如果不使用Stream API我們都知道上述代碼該如何在一次迭代中完成,大致是如下形式: 

  1. int longest = 0 
  2. for(String str : strings){  
  3.     if(str.startsWith("A")){// 1. filter(), 保留以A開頭的字符串  
  4.         int len = str.length();// 2. mapToInt(), 轉換成長度  
  5.         longest = Math.max(len, longest);// 3. max(), 保留最長的長度  
  6.     }  

采用這種方式我們不但減少了迭代次數,也避免了存儲中間結果,顯然這就是流水線,因為我們把三個操作放在了一次迭代當中。只要我們事先知道用戶意圖,總是能夠采用上述方式實現跟Stream API等價的功能,但問題是Stream類庫的設計者并不知道用戶的意圖是什么。

如何在無法假設用戶行為的前提下實現流水線,是類庫的設計者要考慮的問題。

面試題推薦:100期面試題匯總

Stream流水線解決方案

我們大致能夠想到,應該采用某種方式記錄用戶每一步的操作,當用戶調用結束操作時將之前記錄的操作疊加到一起在一次迭代中全部執行掉。沿著這個思路,有幾個問題需要解決:

  1.  用戶的操作如何記錄?
  2.  操作如何疊加?
  3.  疊加之后的操作如何執行?
  4.  執行后的結果(如果有)在哪里?

>> 操作如何記錄

注意這里使用的是“操作(operation)”一詞,指的是“Stream中間操作”的操作,很多Stream操作會需要一個回調函數(Lambda表達式),因此一個完整的操作是<數據來源,操作,回調函數>構成的三元組。

Stream中使用Stage的概念來描述一個完整的操作,并用某種實例化后的PipelineHelper來代表Stage,將具有先后順序的各個Stage連到一起,就構成了整個流水線。跟Stream相關類和接口的繼承關系圖示。

還有IntPipeline, LongPipeline, DoublePipeline沒在圖中畫出,這三個類專門為三種基本類型(不是包裝類型)而定制的,跟ReferencePipeline是并列關系。

圖中Head用于表示第一個Stage,即調用調用諸如Collection.stream()方法產生的Stage,很顯然這個Stage里不包含任何操作;StatelessOp和StatefulOp分別表示無狀態和有狀態的Stage,對應于無狀態和有狀態的中間操作。

Stream流水線組織結構示意圖如下:

圖中通過Collection.stream()方法得到Head也就是stage0,緊接著調用一系列的中間操作,不斷產生新的Stream。這些Stream對象以雙向鏈表的形式組織在一起,構成整個流水線,由于每個Stage都記錄了前一個Stage和本次的操作以及回調函數,依靠這種結構就能建立起對數據源的所有操作。這就是Stream記錄操作的方式。

>> 操作如何疊加

以上只是解決了操作記錄的問題,要想讓流水線起到應有的作用我們需要一種將所有操作疊加到一起的方案。你可能會覺得這很簡單,只需要從流水線的head開始依次執行每一步的操作(包括回調函數)就行了。

這聽起來似乎是可行的,但是你忽略了前面的Stage并不知道后面Stage到底執行了哪種操作,以及回調函數是哪種形式。換句話說,只有當前Stage本身才知道該如何執行自己包含的動作。這就需要有某種協議來協調相鄰Stage之間的調用關系。

這種協議由Sink接口完成,Sink接口包含的方法如下表所示:

方法名 作用
void begin(long size) 開始遍歷元素之前調用該方法,通知Sink做好準備。
void end() 所有元素遍歷完成之后調用,通知Sink沒有更多的元素了。
boolean cancellationRequested() 是否可以結束操作,可以讓短路操作盡早結束。
void accept(T t) 遍歷元素時調用,接受一個待處理元素,并對元素進行處理。Stage把自己包含的操作和回調方法封裝到該方法里,前一個Stage只需要調用當前Stage.accept(T t)方法就行了。

有了上面的協議,相鄰Stage之間調用就很方便了,每個Stage都會將自己的操作封裝到一個Sink里,前一個Stage只需調用后一個Stage的accept()方法即可,并不需要知道其內部是如何處理的。

當然對于有狀態的操作,Sink的begin()和end()方法也是必須實現的。比如Stream.sorted()是一個有狀態的中間操作,其對應的Sink.begin()方法可能創建一個盛放結果的容器,而accept()方法負責將元素添加到該容器,最后end()負責對容器進行排序。

對于短路操作,Sink.cancellationRequested()也是必須實現的,比如Stream.findFirst()是短路操作,只要找到一個元素,cancellationRequested()就應該返回true,以便調用者盡快結束查找。Sink的四個接口方法常常相互協作,共同完成計算任務。

實際上Stream API內部實現的的本質,就是如何重寫Sink的這四個接口方法。

有了Sink對操作的包裝,Stage之間的調用問題就解決了,執行時只需要從流水線的head開始對數據源依次調用每個Stage對應的Sink.{begin(), accept(), cancellationRequested(), end()}方法就可以了。一種可能的Sink.accept()方法流程是這樣的: 

  1. void accept(U u){  
  2.     1. 使用當前Sink包裝的回調函數處理u  
  3.     2. 將處理結果傳遞給流水線下游的Sink  

Sink接口的其他幾個方法也是按照這種[處理->轉發]的模型實現。

下面我們結合具體例子看看Stream的中間操作是如何將自身的操作包裝成Sink以及Sink是如何將處理結果轉發給下一個Sink的。先看Stream.map()方法: 

  1. // Stream.map(),調用該方法將產生一個新的Stream  
  2. public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {  
  3.     ...  
  4.     return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,  
  5.                                  StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {  
  6.         @Override /*opWripSink()方法返回由回調函數包裝而成Sink*/  
  7.         Sink<P_OUT> opWrapSink(int flags, Sink<R> downstream) {  
  8.             return new Sink.ChainedReference<P_OUT, R>(downstream) { 
  9.                  @Override  
  10.                 public void accept(P_OUT u) {  
  11.                     R r = mapper.apply(u);// 1. 使用當前Sink包裝的回調函數mapper處理u  
  12.                     downstream.accept(r);// 2. 將處理結果傳遞給流水線下游的Sink  
  13.                 }  
  14.             };  
  15.         }  
  16.     };  

上述代碼看似復雜,其實邏輯很簡單,就是將回調函數mapper包裝到一個Sink當中。由于Stream.map()是一個無狀態的中間操作,所以map()方法返回了一個StatelessOp內部類對象(一個新的Stream),調用這個新Stream的opWripSink()方法將得到一個包裝了當前回調函數的Sink。

再來看一個復雜一點的例子。Stream.sorted()方法將對Stream中的元素進行排序,顯然這是一個有狀態的中間操作,因為讀取所有元素之前是沒法得到最終順序的。拋開模板代碼直接進入問題本質,sorted()方法是如何將操作封裝成Sink的呢?sorted()一種可能封裝的Sink代碼如下: 

  1. // Stream.sort()方法用到的Sink實現  
  2. class RefSortingSink<T> extends AbstractRefSortingSink<T> {  
  3.     private ArrayList<T> list;// 存放用于排序的元素  
  4.     RefSortingSink(Sink<? super T> downstream, Comparator<? super T> comparator) {  
  5.         super(downstream, comparator);  
  6.     }  
  7.     @Override  
  8.     public void begin(long size) {  
  9.         ...  
  10.         // 創建一個存放排序元素的列表  
  11.         list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();  
  12.     }  
  13.     @Override  
  14.     public void end() {  
  15.         list.sort(comparator);// 只有元素全部接收之后才能開始排序  
  16.         downstream.begin(list.size());  
  17.         if (!cancellationWasRequested) {// 下游Sink不包含短路操作  
  18.             list.forEach(downstream::accept);// 2. 將處理結果傳遞給流水線下游的Sink  
  19.         }  
  20.         else {// 下游Sink包含短路操作  
  21.             for (T t : list) {// 每次都調用cancellationRequested()詢問是否可以結束處理。  
  22.                 if (downstream.cancellationRequested()) break;  
  23.                 downstream.accept(t);// 2. 將處理結果傳遞給流水線下游的Sink 
  24.              }  
  25.         }  
  26.         downstream.end();  
  27.         list = null 
  28.     }  
  29.     @Override  
  30.     public void accept(T t) {  
  31.         list.add(t);// 1. 使用當前Sink包裝動作處理t,只是簡單的將元素添加到中間列表當中  
  32.     }  

上述代碼完美的展現了Sink的四個接口方法是如何協同工作的:

  1.  首先begin()方法告訴Sink參與排序的元素個數,方便確定中間結果容器的的大小;
  2.  之后通過accept()方法將元素添加到中間結果當中,最終執行時調用者會不斷調用該方法,直到遍歷所有元素;
  3.  最后end()方法告訴Sink所有元素遍歷完畢,啟動排序步驟,排序完成后將結果傳遞給下游的Sink;
  4.  如果下游的Sink是短路操作,將結果傳遞給下游時不斷詢問下游cancellationRequested()是否可以結束處理。

>> 疊加之后的操作如何執行

Sink完美封裝了Stream每一步操作,并給出了[處理->轉發]的模式來疊加操作。這一連串的齒輪已經咬合,就差最后一步撥動齒輪啟動執行。

是什么啟動這一連串的操作呢?也許你已經想到了啟動的原始動力就是結束操作(Terminal Operation),一旦調用某個結束操作,就會觸發整個流水線的執行。

結束操作之后不能再有別的操作,所以結束操作不會創建新的流水線階段(Stage),直觀的說就是流水線的鏈表不會在往后延伸了。

結束操作會創建一個包裝了自己操作的Sink,這也是流水線中最后一個Sink,這個Sink只需要處理數據而不需要將結果傳遞給下游的Sink(因為沒有下游)。對于Sink的[處理->轉發]模型,結束操作的Sink就是調用鏈的出口。

我們再來考察一下上游的Sink是如何找到下游Sink的。一種可選的方案是在PipelineHelper中設置一個Sink字段,在流水線中找到下游Stage并訪問Sink字段即可。

但Stream類庫的設計者沒有這么做,而是設置了一個Sink AbstractPipeline.opWrapSink(int flags, Sink downstream)方法來得到Sink,該方法的作用是返回一個新的包含了當前Stage代表的操作以及能夠將結果傳遞給downstream的Sink對象。為什么要產生一個新對象而不是返回一個Sink字段?

這是因為使用opWrapSink()可以將當前操作與下游Sink(上文中的downstream參數)結合成新Sink。試想只要從流水線的最后一個Stage開始,不斷調用上一個Stage的opWrapSink()方法直到最開始(不包括stage0,因為stage0代表數據源,不包含操作),就可以得到一個代表了流水線上所有操作的Sink,用代碼表示就是這樣: 

  1. // AbstractPipeline.wrapSink()  
  2. // 從下游向上游不斷包裝Sink。如果最初傳入的sink代表結束操作,  
  3. // 函數返回時就可以得到一個代表了流水線上所有操作的Sink。  
  4. final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {  
  5.     ...  
  6.     for (AbstractPipeline p=AbstractPipeline.this; p.depth > 0; pp=p.previousStage) {  
  7.         sink = p.opWrapSink(p.previousStage.combinedFlags, sink);  
  8.     }  
  9.     return (Sink<P_IN>) sink;  

現在流水線上從開始到結束的所有的操作都被包裝到了一個Sink里,執行這個Sink就相當于執行整個流水線,執行Sink的代碼如下: 

  1. // AbstractPipeline.copyInto(), 對spliterator代表的數據執行wrappedSink代表的操作。  
  2. final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {  
  3.     ...  
  4.     if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {  
  5.         wrappedSink.begin(spliterator.getExactSizeIfKnown());// 通知開始遍歷  
  6.         spliterator.forEachRemaining(wrappedSink);// 迭代  
  7.         wrappedSink.end();// 通知遍歷結束  
  8.     }  
  9.     ...  

上述代碼首先調用wrappedSink.begin()方法告訴Sink數據即將到來,然后調用spliterator.forEachRemaining()方法對數據進行迭代,最后調用wrappedSink.end()方法通知Sink數據處理結束。邏輯如此清晰。

>> 執行后的結果在哪里

最后一個問題是流水線上所有操作都執行后,用戶所需要的結果(如果有)在哪里?首先要說明的是不是所有的Stream結束操作都需要返回結果,有些操作只是為了使用其副作用(Side-effects),比如使用Stream.forEach()方法將結果打印出來就是常見的使用副作用的場景(事實上,除了打印之外其他場景都應避免使用副作用),對于真正需要返回結果的結束操作結果存在哪里呢?

特別說明:副作用不應該被濫用,也許你會覺得在Stream.forEach()里進行元素收集是個不錯的選擇,就像下面代碼中那樣,但遺憾的是這樣使用的正確性和效率都無法保證,因為Stream可能會并行執行。大多數使用副作用的地方都可以使用歸約操作更安全和有效的完成。 

  1. // 錯誤的收集方式  
  2. ArrayList<String> results = new ArrayList<>();  
  3. stream.filter(s -> pattern.matcher(s).matches())  
  4.       .forEach(s -> results.add(s));  // Unnecessary use of side-effects!  
  5. // 正確的收集方式  
  6. List<String>results =  
  7.      stream.filter(s -> pattern.matcher(s).matches()) 
  8.              .collect(Collectors.toList());  // No side-effects! 

回到流水線執行結果的問題上來,需要返回結果的流水線結果存在哪里呢?這要分不同的情況討論,下表給出了各種有返回結果的Stream結束操作。

返回類型 對應的結束操作
boolean anyMatch() allMatch() noneMatch()
Optional findFirst() findAny()
歸約結果 reduce() collect()
數組 toArray()
  1.  對于表中返回boolean或者Optional的操作(Optional是存放 一個 值的容器)的操作,由于值返回一個值,只需要在對應的Sink中記錄這個值,等到執行結束時返回就可以了。
  2.   對于歸約操作,最終結果放在用戶調用時指定的容器中(容器類型通過收集器指定)。collect(), reduce(), max(), min()都是歸約操作,雖然max()和min()也是返回一個Optional,但事實上底層是通過調用reduce()方法實現的。

      3.   對于返回是數組的情況,毫無疑問的結果會放在數組當中。這么說當然是對的,但在最終返回數組之前,結果其實是存儲在一種叫做Node的數據結構中的。Node是一種多叉樹結構,元素存儲在樹的葉子當中,并且一個葉子節點可以存放多個元素。這樣做是為了并行執行方便。關于Node的具體結構,我們會在下一節探究Stream如何并行執行時給出詳細說明。

結語

本文詳細介紹了Stream流水線的組織方式和執行過程,學習本文將有助于理解原理并寫出正確的Stream代碼,同時打消你對Stream API效率方面的顧慮。如你所見,Stream API實現如此巧妙,即使我們使用外部迭代手動編寫等價代碼,也未必更加高效。

注:留下本文所用的JDK版本,以便有考究癖的人考證: 

  1. $ java -version  
  2. java version "1.8.0_101" 
  3. Java(TM) SE Runtime Environment (build 1.8.0_101-b13)  
  4. Java HotSpot(TM) Server VM (build 25.101-b13, mixed mode)  

 

責任編輯:龐桂玉 來源: Java知音
相關推薦

2023-01-13 16:53:17

Annotation底層元注解

2023-06-01 08:15:04

CentOS紅帽

2021-02-18 16:06:43

JavaStream代碼

2023-07-27 07:35:55

HTTP持久化服務器

2019-03-27 14:20:27

大數據核心價值數據分析

2023-09-13 08:08:41

Redis消息隊列

2024-01-05 08:30:21

懶加載lazy-initSpring框架

2023-04-28 07:42:02

2022-09-05 22:22:00

Stream操作對象

2023-10-10 14:03:47

swap排序解法

2018-10-17 09:25:22

2024-04-08 08:37:41

代碼githook

2024-04-19 08:32:07

Redis緩存數據庫

2021-03-01 08:03:26

Node.jsStream模塊

2023-02-24 15:14:19

6G6G技術6G網絡

2018-08-14 14:20:40

MongoDBStream數據遷移

2016-03-11 16:15:14

2020-08-23 10:03:51

SynchronizeJava

2009-12-16 15:04:26

Ruby實現strea

2010-03-10 18:42:30

Python性能
點贊
收藏

51CTO技術棧公眾號

亚洲午夜免费电影| 韩日视频一区| 91精品国产入口| 欧美性猛交内射兽交老熟妇| 色一情一乱一区二区三区| 久久精品免费| 久久av红桃一区二区小说| 东京热av一区| 国产精品亚洲d| 一区二区三区四区在线播放| 免费精品视频一区二区三区| 一级片视频播放| 亚洲三级国产| 色综合伊人色综合网| 久久无码专区国产精品s| 神马久久资源| 亚洲一级在线观看| 色中色综合成人| 黄色一级大片在线免费看国产| 欧美亚洲一区二区三区| 久久国产精品99国产精| 香蕉视频黄色在线观看| 精品国模一区二区三区欧美| 欧美视频在线观看免费| 蜜臀在线免费观看| 99riav在线| 成人黄色在线网站| 92看片淫黄大片看国产片| 国产精品视频123| 1024精品一区二区三区| 久久网福利资源网站| 男人天堂av电影| 国产精品qvod| 777奇米四色成人影色区| 久久久久免费精品| 国产在线精彩视频| 亚洲亚洲人成综合网络| 色乱码一区二区三区熟女| 国产香蕉视频在线看| 99精品国产视频| 国产欧美日韩伦理| 精品人妻少妇AV无码专区| 久久精品国产亚洲一区二区三区| 91sa在线看| 日韩三级视频在线| 国产一区亚洲| 欧美激情影音先锋| 久久精品视频日本| 欧美黄色一区二区| 大胆人体色综合| www.av视频| 欧美在线黄色| 欧美国产第一页| 五月天婷婷色综合| 最新精品国产| 欧美乱妇40p| 色欲人妻综合网| 综合视频在线| 色综合久久悠悠| 久久国产精品波多野结衣| 亚洲香蕉av| 欧美成人精品不卡视频在线观看| 亚洲精品久久久久久国| 99久久婷婷国产综合精品电影√| 中文字幕欧美精品日韩中文字幕| 神马久久久久久久久久久| 精品一级毛片| 色午夜这里只有精品| 日本一级特级毛片视频| 一区二区三区四区电影| 欧美国产日本高清在线| 日韩和一区二区| 玖玖视频精品| 国产在线观看精品| 国产精品欧美激情在线| 国产精品一区免费视频| www.久久草| 亚洲 另类 春色 国产| 久久先锋影音av鲁色资源网| 欧美久久久久久| 一级日本在线| 亚洲最大成人综合| 丰满少妇被猛烈进入高清播放| 在线日本欧美| 日韩一区二区在线观看视频| 日本少妇xxxx软件| 中文字幕精品影院| 两个人的视频www国产精品| 久久亚洲av午夜福利精品一区| 亚洲国产日韩欧美一区二区三区| 青青草99啪国产免费| 中文字幕日本人妻久久久免费 | 久久久精品影视| 亚洲欧美久久234| 欧美亚洲天堂| 在线观看av不卡| 亚洲少妇一区二区| 欧美一站二站| 欧美精品videossex性护士| 波多野结衣视频网站| 狠狠色狠狠色综合| 久久久久久久久一区| 黄a在线观看| 一本色道久久加勒比精品| 欧美又黄又嫩大片a级| 精品在线观看入口| 久久人人爽人人爽爽久久| 国产精品久久久久久久久久久久久久久久久 | 91中文字幕在线视频| 不卡视频一二三| 亚洲综合首页| 亚洲福利影院| 欧美成人vps| 天堂网中文在线观看| 国产精品久久久一区二区| 成人精品视频久久久久 | 国产亚洲欧美aaaa| 国产小视频在线观看免费| 奇米影视7777精品一区二区| 国产91视觉| 黄页视频在线播放| 欧美在线一二三四区| 免费看毛片的网站| 欧美一区综合| 91免费的视频在线播放| seseavlu视频在线| 一本到不卡精品视频在线观看| 制服.丝袜.亚洲.中文.综合懂| 日韩成人激情| 日韩免费在线视频| 天天综合网在线| 亚洲伊人色欲综合网| 免费黄频在线观看| 91麻豆国产自产在线观看亚洲| 国产91精品最新在线播放| 日韩永久免费视频| 亚洲sss视频在线视频| 青青草原播放器| 久久精品国产www456c0m| 欧洲永久精品大片ww免费漫画| 黄色福利在线观看| 亚洲国产成人porn| 亚洲高清无码久久| 亚洲一级一区| 国产精品夜夜夜一区二区三区尤| av网址在线播放| 欧美一区二区三区视频免费播放 | 国产在线一二| 色狠狠综合天天综合综合| 极品白嫩丰满美女无套| 老鸭窝亚洲一区二区三区| 久久久av水蜜桃| 欧美xxxxxx| 国产一区二区三区在线播放免费观看| 一级一片免费看| 久久精品视频在线免费观看 | 豆国产97在线 | 亚洲| 国产传媒日韩欧美成人| 免费的av在线| 波多野结衣在线一区二区| 欧美激情一区二区三区高清视频| 刘亦菲毛片一区二区三区| 亚洲成av人影院在线观看网| 第四色在线视频| 午夜综合激情| 亚洲成人在线视频网站| 亚洲tv在线| 欧美激情第6页| 午夜av免费在线观看| 午夜成人免费电影| www.狠狠爱| 免费观看成人av| 日本一二三区视频在线| 福利在线一区| 国产91色在线|免| 男女啪啪在线观看| 欧美成人一区二区三区片免费| 国产第一页在线播放| 久久综合一区二区| 成年网站免费在线观看| 韩国在线视频一区| 欧美大香线蕉线伊人久久国产精品| 亚洲综合av一区二区三区| 免费91麻豆精品国产自产在线观看| 亚洲第一页在线观看| 欧美日韩久久久久| www.xx日本| 99久久综合精品| 视频在线观看免费高清| 国精品一区二区三区| 欧美精品人人做人人爱视频| 国产精品一区二区精品| 18性欧美xxxⅹ性满足| 在线观看免费黄视频| 亚洲第一中文字幕在线观看| 69视频免费看| 亚洲成人精品一区| 黑人と日本人の交わりビデオ| 粉嫩久久99精品久久久久久夜| 国产熟女高潮视频| 欧美高清一区| 日韩高清国产精品| 国产精品tv| 成人免费激情视频| 欧美1级2级| 国a精品视频大全| 欧美高清视频| 亚洲欧美另类在线观看| www.日本在线观看| 欧美日韩亚洲综合一区二区三区| 日本一级淫片色费放| 国产精品麻豆久久久| 亚洲制服丝袜在线播放| 国产曰批免费观看久久久| 国产无套粉嫩白浆内谢的出处| 国产一区日韩一区| 小说区视频区图片区| 免费看成人吃奶视频在线| 97碰碰视频| 四虎影视精品永久在线观看| 国产成人高清激情视频在线观看 | 91久久久免费一区二区| 久久久久99精品| 亚洲视频在线一区观看| www.99热| 久久综合精品国产一区二区三区 | 欧美精品电影在线| 日本在线免费| 永久免费精品影视网站| 欧美日韩国产综合视频| 亚洲国产精品成人av| www日本视频| 日韩视频永久免费| 国产精品热久久| 欧美精品tushy高清| 中文字幕无码乱码人妻日韩精品| 色综合夜色一区| 国产在线观看黄色| 欧美日韩精品中文字幕| www.国产成人| 精品久久久久久久久久久久久久| 国产在线一区视频| 亚洲国产一区二区三区青草影视 | 色婷婷av一区二区三区软件| 亚洲欧美在线视频免费| 午夜精品在线看| 在线观看免费国产视频| 亚洲成人av一区二区| 国产精品50页| 五月综合激情婷婷六月色窝| 国产精品成人av久久| 亚洲第一在线综合网站| 国产精品第56页| 狠狠躁夜夜躁人人躁婷婷91| 中文字幕超碰在线| 日本乱码高清不卡字幕| 波多野结衣一区二区在线| 在线观看日韩毛片| 欧美激情一区二区三区免费观看| 欧美日韩dvd在线观看| 91资源在线视频| 欧美成人福利视频| 天堂av网在线| 尤物yw午夜国产精品视频| 欧美性videos| 欧美国产精品va在线观看| 97人人爽人人澡人人精品| 久久久久国产视频| 天堂8中文在线最新版在线| 日本午夜精品理论片a级appf发布| 成人短视频app| 国产在线98福利播放视频| 精品91福利视频| 国内精品久久国产| 图片婷婷一区| 日韩av影视| 欧美在线亚洲| 国产超级av在线| 麻豆精品在线视频| 又色又爽又黄18网站| 91在线视频观看| 刘亦菲国产毛片bd| 亚洲午夜av在线| 欧美一级做a爰片免费视频| 欧美高清激情brazzers| 国产欧美久久久精品免费| 精品88久久久久88久久久| 日本福利在线观看| 久久中文字幕国产| 美女的胸无遮挡在线观看| 国产精品丝袜高跟| 国产精品17p| 永久久久久久| 中文在线不卡| www.51色.com| 久久亚洲春色中文字幕久久久| 97成人资源站| 91国偷自产一区二区三区观看 | 日韩精品视频免费专区在线播放| 高清av电影在线观看| 欧美高清视频免费观看| 国产成人福利夜色影视| 国产精品一区二区三区在线观 | 97品白浆高清久久久久久| 日韩在线导航| 亚洲无吗在线| 国产三级三级三级看三级| 国产成人午夜精品影院观看视频| 韩国三级hd中文字幕| 亚洲国产一二三| 一本色道久久综合熟妇| 日韩av一区在线| 2024短剧网剧在线观看| 国产精品国语对白| 青青草久久爱| 嫩草影院中文字幕| 精品午夜久久福利影院| 韩国女同性做爰三级| 亚洲高清免费视频| 国产成人精品a视频| 中文字幕日韩欧美在线| 激情国产在线| 国产一区不卡在线观看| 一区二区三区毛片免费| 中文字幕成人免费视频| 国产日韩三级在线| 特黄视频免费看| 日韩电影免费观看中文字幕| 男女视频在线| 97伦理在线四区| 国产精品多人| 国产a√精品区二区三区四区| 亚洲视频在线一区二区| 夜夜躁很很躁日日躁麻豆| 国产一区二区激情| 欧美电影免费看| 久久精品一二三区| 国产欧美一区二区色老头 | 国产熟女一区二区三区五月婷| 国产午夜精品一区理论片飘花| 欧美激情网站| 麻豆久久久9性大片| 一区二区三区福利| 污片免费在线观看| 精品免费在线视频| 国模无码一区二区三区| 欧美激情一区二区三区高清视频| 6080成人| 黄色大片中文字幕| www.亚洲色图| 黄色在线视频网址| 一区二区av在线| 黄色日韩网站| dy888午夜| 国产suv精品一区二区三区| 久久免费播放视频| 亚洲国产精品久久久久秋霞蜜臀 | 麻豆传媒视频在线观看免费| 国产日韩欧美视频在线| 亚洲区综合中文字幕日日| 久久久九九九热| 一区二区成人在线视频 | ww亚洲ww在线观看国产| 国产免费一级视频| 伊人伊成久久人综合网小说| 97色婷婷成人综合在线观看| 成年丰满熟妇午夜免费视频| 国产白丝精品91爽爽久久| 国产一级做a爱片久久毛片a| 亚洲欧美一区二区三区久久| 日本肉肉一区| a级片一区二区| 99久久精品国产毛片| 精品无码一区二区三区的天堂| 日韩中文字幕精品| 大奶在线精品| 亚洲人成无码www久久久| 亚洲欧洲精品一区二区三区 | 日韩综合在线观看| 日韩一区在线视频| 福利电影一区| 亚洲国产高清av| 亚洲国产va精品久久久不卡综合| 可以直接在线观看的av| 91精品国产综合久久香蕉最新版 | 五月天网站亚洲| jizz在线观看中文| 操人视频欧美| 久久亚洲综合| 美国黄色小视频| 亚洲精品中文字幕av| 日韩在线电影| 国产a级一级片| 亚洲乱码中文字幕| 色视频在线观看| 91久久精品国产91久久| 久久都是精品| 久久网一区二区| 最近2019好看的中文字幕免费| 丁香5月婷婷久久|