精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

神马影院我不卡| 手机亚洲手机国产手机日韩| 亚洲精品日韩久久| 欧美色爱综合网| 久久精品国产一区二区三区不卡| 三级黄色在线观看| 你懂的视频在线| www.国产精品一区| 中文字幕在线观看不卡| 日本午夜在线亚洲.国产| 无码国产精品久久一区免费| 日本在线视频站| 青草国产精品久久久久久| 精品亚洲国产视频| 国产欧美精品aaaaaa片| av资源免费看| 亚洲自拍偷拍网| 91精品国产综合久久香蕉的特点| 色涩成人影视在线播放| av在线免费在线观看| 亚洲日本免费| 久久精品国产欧美激情| 潘金莲激情呻吟欲求不满视频| 精品资源在线看| 久久精品五月| 国产午夜精品全部视频在线播放| aaaaaa亚洲| 国产小视频在线| 国产欧美午夜| 亚洲国产精品热久久| 欧美精品卡一卡二| 男人天堂网在线视频| 海角社区69精品视频| 日韩精品一区二区三区视频播放 | 极品粉嫩国产18尤物| 性做久久久久久久| 黄色欧美日韩| 精品国产一区二区在线| 久久黄色片网站| 欧美特大特白屁股xxxx| 欧美激情一区二区三区不卡| 国产精品永久免费| 91插插插插插插| youjizzjizz亚洲| 91超碰这里只有精品国产| 精品视频一区二区在线| av色图一区| 国产一区二区三区精品视频| 欧美精品18videos性欧美| 国产国语性生话播放| 二吊插入一穴一区二区| 国产精品久久久久影院亚瑟| 97久草视频| 天堂中文在线网| 久久在线视频| 在线视频免费一区二区| 日日夜夜精品视频免费观看| 暧暧视频在线免费观看| 久久久精品综合| 91久久精品国产91性色| 欧美不卡视频在线观看| 成人在线免费观看91| 日韩免费性生活视频播放| 亚洲理论中文字幕| 大菠萝精品导航| 国产精品久久三| 亚洲午夜精品国产| 好吊色一区二区| 久久av资源站| 国产91成人在在线播放| 日本少妇aaa| 欧美在线关看| 日韩欧美国产精品一区| 免费黄色av网址| 国产精品视频3p| 欧美日韩免费观看一区二区三区| 日韩精品一区在线视频| 成人观看网址| 色哟哟国产精品| 日韩专区第三页| eeuss影院www在线播放| 99精品国产99久久久久久白柏| 国产裸体写真av一区二区| 亚洲一区二区人妻| 国产亚洲在线观看| 日本一欧美一欧美一亚洲视频| 中国精品一区二区| 在线综合亚洲| 久久久久久久一区二区| 一区二区三区视频免费看| 日韩国产欧美在线观看| 2024亚洲男人天堂| 在线观看国产区| 亚洲欧美久久| 亚洲**2019国产| 国产一级久久久| 亚洲精品成人无限看| 综合国产在线视频| 欧美成人精品欧美一级私黄| 色一区二区三区四区| 亚洲男人天堂视频| 亚州av综合色区无码一区| 亚洲精品影片| 欧美一级生活片| 91pony九色| 婷婷久久综合九色综合99蜜桃| 日本精品视频一区二区| 久久无码高潮喷水| 国产免费拔擦拔擦8x在线播放 | 26uuu国产一区二区三区| 操人视频欧美| 性一交一乱一乱一视频| 久久久久久久久久久99999| 免费看啪啪网站| 色大18成网站www在线观看| 亚洲高清免费视频| 99热久久这里只有精品| jizzjizz少妇亚洲水多| 欧美色图在线观看| 欧产日产国产精品98| 国产精品99在线观看| 久久精品这里热有精品| 在线精品免费视| 久久久亚洲人| 国产精品国产三级国产专播精品人| 中文字幕日韩免费| 日韩精彩视频在线观看| 国产精品国色综合久久| 婷婷五月综合激情| 2022国产精品视频| 视频一区二区在线观看| a在线视频v视频| 精品久久久久久国产| 欧美精品一区免费| 九九热线视频只有这里最精品| 欧美大片国产精品| 日韩精品一区二区亚洲av性色 | 欧美精选在线播放| 国产一区二区三区毛片| 国产探花在线视频| 日韩极品在线观看| 免费精品视频一区二区三区| 国家队第一季免费高清在线观看| 一区二区三区在线视频观看| 欧美日韩激情四射| av在线精品| 精品国产一二三| 三级电影在线看| 亚洲天堂偷拍| 91免费在线观看网站| 国产剧情在线| 午夜影视日本亚洲欧洲精品| 波多野结衣50连登视频| gogo人体一区| 欧美激情区在线播放| 男人舔女人下面高潮视频| 欧美国产不卡| 欧美一区二区三区免费视| 欧美一区,二区| 午夜免费久久看| 日韩少妇一区二区| 精品国产一区二区三区久久久蜜臀 | 国产成人精品一区二区三区视频| 亚洲人成五月天| 国产精品一区二区亚洲| 久久国内精品视频| 国产伦精品一区二区三| 成年人国产在线观看| 亚洲福利视频网| 日韩av片在线免费观看| 亚洲性感美女99在线| 国产成人精品优优av| 精品欧美在线观看| 欧美激情综合五月色丁香| 99视频在线免费| 成人激情自拍| 97视频在线观看播放| 日韩av资源| 一区二区在线电影| 国产av一区二区三区传媒| 欧美日韩激情在线一区二区三区| 国产精品久久久久久婷婷天堂| 黄色av小说在线观看| 精品国产户外野外| 久久久久久九九九九九| 亚洲第一伊人| 欧美日韩亚洲在线| av人人综合网| 亚洲人成毛片在线播放| 国产精品自产拍| 欧美激情在线观看视频免费| 亚洲欧美日韩精品一区| 国内精品久久久久久久影视蜜臀 | 国内免费精品永久在线视频| 97在线播放免费观看| 亚洲少妇最新在线视频| 午夜精品在线免费观看| 宅男在线一区| 97精品国产aⅴ7777| aaa在线观看| 337p日本欧洲亚洲大胆精品| 一级一片免费看| 亚洲精品写真福利| 亚洲理论中文字幕| 99热免费精品| 一区二区三区偷拍| 嫩草国产精品入口| 成人免费看吃奶视频网站| 天天在线视频色| 亚洲精品国产成人| 国产美女自慰在线观看| 亚洲视频精选在线| 亚洲永久无码7777kkk| 国内精品第一页| 成年人黄色在线观看| 伊人久久大香伊蕉在人线观看热v| 久久久久久国产精品| 午夜激情在线观看| 亚洲免费视频观看| 成人免费观看在线视频| 在线免费精品视频| av片在线免费看| 26uuu色噜噜精品一区二区| 国产乱码一区二区三区四区| 久久午夜精品一区二区| 我的公把我弄高潮了视频| 天天射成人网| 色综合电影网| 国产成人手机高清在线观看网站| 国产精品777| 91黄页在线观看| 欧美另类暴力丝袜| 免费看黄网站在线观看| 制服丝袜中文字幕亚洲| 国产偷人爽久久久久久老妇app | 亚洲毛片一区二区三区| 精品毛片三在线观看| 欧美xxxx黑人xyx性爽| 中文字幕一区视频| 久久日免费视频| 久久99九九99精品| www.色偷偷.com| 久久精品一区| 成年人观看网站| 亚洲精品麻豆| 91专区在线观看| 国内精品久久久久久久影视简单 | 久久不见久久见中文字幕免费 | √资源天堂中文在线| 亚洲一二三四区| 波多野吉衣中文字幕| 日日摸夜夜添夜夜添亚洲女人| 97国产精东麻豆人妻电影| 精品99视频| 亚洲熟妇av日韩熟妇在线 | 能在线观看的av| 乱人伦精品视频在线观看| 男人天堂999| 水蜜桃久久夜色精品一区的特点| 日本新janpanese乱熟| 日本vs亚洲vs韩国一区三区二区| 日韩手机在线观看视频| 久久综合九色| 亚洲最大综合网| 狠狠爱综合网| 阿v天堂2018| 亚洲影音先锋| 人妻丰满熟妇av无码区app| 日本在线播放一区二区三区| 色呦色呦色精品| 成人性生交大片免费看中文 | 欧美成人女星排行榜| 特级毛片www| 欧美在线不卡一区| 亚欧洲精品在线视频| 亚洲欧美中日韩| 亚洲区自拍偷拍| 国产精品福利影院| 青青草原在线免费观看视频| 欧美激情一区二区三区不卡 | 四虎成人精品永久免费av九九| 伊人久久大香线蕉精品| 国产在线成人| 日韩精品一区二区三区色欲av| 蜜臀av性久久久久蜜臀aⅴ流畅| 你懂的av在线| 日韩1区2区日韩1区2区| 国产一级免费大片| 99视频有精品| 成人免费看片载| wwwwww.欧美系列| 青青草自拍偷拍| 亚洲高清不卡在线观看| 欧美a视频在线观看| 7777精品伊人久久久大香线蕉的 | 在线观看黄网站| 一区二区三区.www| 日韩欧美123区| 五月综合激情日本mⅴ| 中文字幕人妻互换av久久| 日韩欧美国产午夜精品| 蝌蚪视频在线播放| 欧美国产日韩在线| 中文字幕av一区二区三区佐山爱| 91精品一区二区| 日韩一级电影| 久久人人九九| 婷婷久久综合| 99re在线视频免费观看| 国产成人激情av| 精品人妻二区中文字幕| 国产亚洲成年网址在线观看| 免费毛片视频网站| 有坂深雪av一区二区精品| 久久永久免费视频| 欧美日韩一区二区三区在线| 好吊色视频一区二区| 日韩中文字幕欧美| 国产不卡网站| 国产精品吊钟奶在线| 97青娱国产盛宴精品视频| 亚洲乱码一区二区三区三上悠亚| 日韩精品二区| 日韩中文字幕在线视频观看| 国产精品白丝av| 稀缺小u女呦精品呦| 亚洲欧洲三级电影| 久草视频在线免费| 亚洲精品999| xxx在线免费观看| 亚洲a一级视频| 成人直播在线观看| 国产又粗又爽又黄的视频| 欧美天堂亚洲电影院在线观看| 人妻激情另类乱人伦人妻| 美女在线观看视频一区二区| 国产综合精品在线| 欧美丝袜一区二区| 中文字幕+乱码+中文字幕明步| 亚洲精品久久久久久下一站| 亚洲www色| 青草热久免费精品视频| 国产91在线播放精品| 欧美一级二级三级| 亚洲最大av| 五月激情五月婷婷| 中文字幕免费不卡| 欧美性猛交xxxxx少妇| 欧美日韩国产在线观看| a天堂中文在线88| 国产91免费看片| sdde在线播放一区二区| 日韩肉感妇bbwbbwbbw| 国产精品午夜春色av| 国产情侣免费视频| 中文字幕精品av| 男人天堂久久| 韩国一区二区三区美女美女秀| 激情婷婷综合| 无码少妇一区二区三区芒果| 中文字幕免费在线观看视频一区| 伊人久久国产精品| 日韩日本欧美亚洲| 精品入口麻豆88视频| 欧美日韩国产三区| 性一交一乱一区二区洋洋av| 91网站免费入口| 欧美久久一区二区| 香蕉成人app免费看片| 国产精品jizz视频| 国产精品黑丝在线播放| www.色就是色.com| 亚洲黄色性网站| 熟妇人妻系列aⅴ无码专区友真希 熟妇人妻av无码一区二区三区 | 91久久国产精品| 国产在线日韩| 成人午夜福利一区二区| 欧美日韩国产系列| 国产深夜视频在线观看| 久久国产精品-国产精品| 天堂va蜜桃一区二区三区| 国内毛片毛片毛片毛片毛片| 精品国产a毛片| 黄色亚洲网站| 国产一二三四区在线观看| 日韩国产欧美在线播放| 成人性生活毛片| 日韩av网址在线观看| 午夜小视频在线观看| 久久精品二区| 精品一区二区三区在线播放 | 日本免费在线视频| 99re在线视频上| 欧美专区一区二区三区| 99久久精品久久亚洲精品| 精品国产91乱码一区二区三区| 欧美黑人粗大| 欧美交换配乱吟粗大25p| 久久女同性恋中文字幕| 国产视频aaa|