目錄
2、sample()-定期採樣「throttleFirst() + throttleLast()」
1、背景
在異步處理的前提下, 我們的Observable.create()發佈數據的數據有可能快於Observer處理的速度。這就導致 Observer來不及接收所有事件,從而導致Observer無法及時響應 / 處理所有發送過來事件的問題,最終導致緩存區溢出、事件丟失 & OOM。
例如:點擊按鈕事件:連續過快的點擊按鈕10次,則只會造成點擊2次的效果;
解釋:因爲點擊速度太快了,所以按鈕來不及響應。
在RxJava實現回壓之前,生產者的生產速度超過消費者的消費速度一直很難解決。爲此,RxJava發明了很多操作符來控制生產者的推送數據的量。從而避免消費者出現處理不了的問題。
2、sample()-定期採樣「throttleFirst() + throttleLast()」
- sample()操作符會定期從上游Observable獲取數據,就比如每隔一秒從上面取一次數據,但是這一秒中間的數據將會被丟棄;
- throttleLast()和sample()是一樣效果,都會取每個時間段的最後一個值;
- throttleFirst()與throttleLast()相反,它會取每個時間段第一個值。
sample()有個重載函數可以傳入一個Observable作爲參數,動態改變監控時間的長短,就比如有些心跳,他們在白天和晚上監控的時間間隔是不一樣的。
注意:如果你用main函數測試下面的代碼的時候,需要加上Thread.sleep(),不然沒有輸出。
/**
* 1、interval()-從0開始,每一毫秒+1;
* 2、sample()每隔1000毫秒從上游取一次值;
* 預期結果:1000、2000、3000.......
* 實際結果:1002、2000、3001、4000..... 和預期結果相符,每隔1000毫秒取一次值
*/
private static void testSample() {
Observable.interval(1, TimeUnit.MILLISECONDS)
.sample(1,TimeUnit.SECONDS)
.take(10)
.subscribe(System.out::println);
}
/**
* 1、throttleLast()和sample()是一樣效果,都會取每個時間段的最後一個值;
* 2、就比如:該例子,是從0開始遞增的,但是第一次取的值,是等待一秒以後取的值。
*/
private static void testThrottleLast() {
Observable.interval(1, TimeUnit.MILLISECONDS)
.throttleLast(1,TimeUnit.SECONDS)
.take(10)
.subscribe(System.out::println);
}
/**
* 1、throttleFirst()與throttleLast()相反,它會取每個時間段第一個值。
* 2、所以這裏的結果應該是:0、1000、2000、3000.......
*/
private static void testThrottleFirst() {
Observable.interval(1, TimeUnit.MILLISECONDS)
.throttleFirst(1,TimeUnit.SECONDS)
.take(10)
.subscribe(System.out::println);
}
public static void main(String[] args) {
testSample();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
3、buffer()-批量處理
buffer()操作符會將指定數量的數據聚合成一個List然後發佈給Observer。
實現原理:buffer()會不斷接受上游的事件,並在內部對他們進行緩衝,直到緩衝區的大小(List<Integer> .size() )達到3,此時整個緩衝區(List<Integer>)會被推送下去。如果完成通知出現時,如果內部的緩衝區大小沒達到3,依然會被推送至下游。
適用場景:適用buffer()操作符,可以將細顆粒度的事件替換爲數量更少,但規模更大的批處理。例如:如果你想減少數據庫的負載,那麼就可以將每個事件單獨存儲的方案,替換爲批量存儲。
上面將smaple()函數會每隔一段事件從上游取一個數據;buffer()的一個重載版本,可以將某一個時間段的所有數據打包成List發佈給下游,如下面的例子3。
/**
* buffer(3)
* 輸出結果:
* [1, 2, 3]
* [4, 5, 6]
* [7, 8, 9]
* [10]
*/
private static void testbuffer() {
Observable.range(1, 10)
.buffer(3)
.subscribe((List<Integer> list) -> {
System.out.println(list);
});
}
/**
* buffer(1,2) 每次跳過元素的數量
* 輸出結果:
* [1]
* [3]
* [5]
* [7]
* [9]
*/
private static void testbuffer1() {
Observable.range(1, 10)
.buffer(1,2)
.subscribe((List<Integer> list) -> {
System.out.println(list);
});
}
/**
* buffer(1,TimeUnit.SECONDS) 1秒內緩衝的元素轉換成List。
* 輸出結果:
* [0, 1, 2]
* [3, 4, 5]
* [6, 7, 8]
* [9, 10, 11, 12]
* [13, 14, 15]
*/
private static void testbuffer2() {
Observable.interval(300, TimeUnit.MILLISECONDS)
.buffer(1,TimeUnit.SECONDS)
.take(5)
.subscribe((List<Long> list) -> {
System.out.println(list);
});
}
buffer還有一個很厲害的重載版本,它可以控制何時開始緩衝事件,何時停止緩衝事件。
假設現在有這麼個一個業務,服務端不停地給客戶端推送數據,這個數據非常的頻繁。爲了減少客戶端的壓力,客戶端想要每隔一段時間,處理一部分數據就可以了,不需要處理所有的數據。而且在白天業務繁忙階段,加快接受數據的頻率,晚上減少頻率。
- 9:00-17:00 ,客戶端每隔5秒 截取1秒的數據;
- 其他的時間,客戶端每隔10秒,截取1秒的數據;
我們的數據是每500毫秒發送一個數據,截取1秒的數據,大約是兩條數據,輸出結果符合我們的預測。
private static long startTime=System.currentTimeMillis();
private static void testBuffer3() {
Observable<Duration> insideBusinessHours=Observable.interval(5,TimeUnit.SECONDS)
.filter(x->isBusinessHour())
.map(x->Duration.ofMillis(1000));
Observable<Duration> outsideBusinessHours=Observable.interval(10,TimeUnit.SECONDS)
.filter(x->!isBusinessHour())
.map(x->Duration.ofMillis(1000));
Observable<Duration> openings=Observable.merge(insideBusinessHours,outsideBusinessHours)
.doOnNext(s-> System.out.println("時間間隔:"+s.toMillis()+"毫秒"));
Observable.interval(500, TimeUnit.MILLISECONDS)
.buffer(openings,duration -> Observable.empty().delay(duration.toMillis(),TimeUnit.MILLISECONDS))
.subscribe((List<Long> list) -> {
long endTime=System.currentTimeMillis();
long result=endTime-startTime;
System.out.println("time="+(result)+"---"+list);
startTime=endTime;
});
}
/**
* 判斷是否是 9:00 --17:00 之內的時間段
*/
private static final LocalTime BUSINESS_START = LocalTime.of(9, 0);
private static final LocalTime BUSINESS_END = LocalTime.of(17, 0);
private static boolean isBusinessHour(){
ZonedDateTime zdt=ZonedDateTime.now();
LocalTime localTime=zdt.toLocalTime();
return !localTime.isBefore(BUSINESS_START)&&!localTime.isAfter(BUSINESS_END);
}
//輸出結果
時間間隔:1000毫秒
time=6229---[10, 11]
時間間隔:1000毫秒
time=4981---[20, 21]
時間間隔:1000毫秒
time=5000---[30, 31]
時間間隔:1000毫秒
time=4997---[40, 41]
時間間隔:1000毫秒
time=5002---[49, 50, 51]
時間間隔:1000毫秒
time=5002---[60, 61]
時間間隔:1000毫秒
time=4996---[69, 70, 71]
時間間隔:1000毫秒
time=5000---[80, 81]
時間間隔:1000毫秒
time=4998---[90, 91]
4、window()-批量處理
window() 和buffer()的功能幾乎是完全一樣,上面buffer()能實現的功能,window()操作符也都可以實現。
buffer()操作符會在緩衝的時候,不斷創建List對象,這在內存消耗方面是難以預料;window()操作符則不會進行中間的緩存,而是採取直接消費的方式。
buffer()的返回結果是:Observable<List<Integer>>
window()的返回結果是:Observable<Observable<Integer>>
5、debounce()-拋棄頻繁變動
debounce()會對上游數據的發佈頻率進行監聽,比如:debounce(1秒) ,如果兩條數據發佈的時間間隔小於1秒,那麼這兩條數據都不會發布,這時候debounce()會監聽第三個數據,如果第三個數據從發佈到1秒後,都沒有其他數據發佈,那麼這第三條數據就會被髮布。
對於下面第二種沒有輸出的情況,我們可以適用timeout()操作符來報TimeOutException異常來防止無限地靜默狀態。
還有一種解決方案就是利用timeout()提供備用Observable流。
/**
* 每個值都會輸出
*/
Observable.interval(500, TimeUnit.MILLISECONDS)
.debounce(100,TimeUnit.MILLISECONDS)
.subscribe(s-> System.out.println("輸出1="+s));
/**
* 沒有任何值輸出
*/
Observable.interval(50, TimeUnit.MILLISECONDS)
.debounce(100,TimeUnit.MILLISECONDS)
.subscribe(s-> System.out.println("輸出2="+s));
------------------------解決方案---遞歸------------------------
timeDebounce(Observable.interval(50, TimeUnit.MILLISECONDS))
.subscribe(s-> System.out.println("輸出3="+s)) ;
private static Observable<Long> timeDebounce(Observable<Long> upStream){
Observable<Long> onTimeout=upStream
.take(1)
.concatWith(Observable.defer(()->timeDebounce(upStream)));
return upStream.debounce(100,TimeUnit.MILLISECONDS)
.timeout(1,TimeUnit.SECONDS,onTimeout);
}
//輸出結果:
輸出3=0
輸出3=0
輸出3=0
輸出3=0
輸出3=0
輸出3=0