在JAVA中如何實現Debounce去抖,防止頻繁調用
1. 概述
在JS中有很好的Debounce庫,可以用來去抖,防止一個接口在短時間內頻繁調用,可以抑制住相關的調用。
最近翻閱了下JAVA相關資料,但是沒找到一個簡單的可以用於去抖的類。爲此,我專門封裝了一個簡單的去抖實用類,可以輕鬆地實現Debounce相關的功能SimpleDebounceCallable
類。
http://www.easysb.cn/2019/04/310.html
2. SimpleDebounceCallable使用
爲解決此問題,我專門封裝了SimpleDebounceCallable
類,具體可以參考第三節實現部門。其使用方法有兩種,下面分別介紹下。
2.1 自動debounce抑制
自動監測是否處於抑制狀態,如果不是就會調用函數。
// 定義成員表變量或者靜態變量, 設置抑制時間 5秒
private final SimpleDebounceCallable<Integer> debounce = new SimpleDebounceCallable<>(1000 * 5);
.....
String key = "hello-call"
Optional<Integer> ret = debounce.debounce(() -> {
int c = 0;
(處理代碼)....
return c;
}, key);
如果只提供給唯一的地方調用,那麼我就不需要用key
來區分,可以直接用默認的key
即可,如下:
// 定義成員表變量或者靜態變量, 設置抑制時間 5秒
private final SimpleDebounceCallable<Integer> debounce = new SimpleDebounceCallable<>(1000 * 5);
.....
// 此時不需要key,直接用默認的即可
Optional<Integer> ret = debounce.debounce(() -> {
int c = 0;
(處理代碼)....
return c;
});
2.2 主動debounce抑制
主動監測是否處於抑制狀態,然後覺得是否去調用函數。
// 定義成員表變量或者靜態變量, 設置抑制時間 5秒
private final SimpleDebounceCallable<Integer> debounce = new SimpleDebounceCallable<>(1000 * 5);
...
String key = "hello-call"
if (debounce.tick(key) > 0) {
Optional<Integer> ret = debounce.debounce(() -> {
int c = 0;
(處理代碼)....
return c;
});
}
如果只提供給唯一的地方調用,那麼我就不需要用key
來區分,可以直接用默認的key
即可,如下:
// 定義成員表變量或者靜態變量, 設置抑制時間 5秒
private final SimpleDebounceCallable<Integer> debounce = new SimpleDebounceCallable<>(1000 * 5);
...
// 此時不需要key,直接用默認即可
if (debounce.tick() > 0) {
Optional<Integer> ret = debounce.debounce(() -> {
int c = 0;
(處理代碼)....
return c;
});
}
3. SimpleDebounceCallable實現
接口Ticking
的定義如下:
// http://www.easysb.cn/2019/04/310.html
public interface Ticking {
int tick();
}
SimpleDebounceCallable
的具體實現如下:
// http://www.easysb.cn/2019/04/310.html
@Slf4j
public class SimpleDebounceCallable<T> implements Ticking {
final private static String DEFAULT_KEY = "__default";
final private static String REMOVE_TIMEOUT_KEY = "__clear_time_out";
// default timeout is 10 minutes
final private static long DEFAULT_TIMEOUT = DateTimeUtils.MS_PER_MINUTE * 10;
final private Map<String, Long> lastTickTimeMap = Maps.newConcurrentMap();
@Setter
@Getter
private long timeout = 0;
public SimpleDebounceCallable() {
this(DEFAULT_TIMEOUT);
}
public SimpleDebounceCallable(long timeout) {
this.timeout = timeout;
}
@Override
public int tick() {
return replaceIfTimeout(DEFAULT_KEY) ? 1 : 0;
}
public int tick(String key) {
return replaceIfTimeout(key) ? 1 : 0;
}
private boolean replaceIfTimeout(String key) {
boolean replace = false;
synchronized (lastTickTimeMap) {
if (isExpiredValue(lastTickTimeMap.get(key))) {
lastTickTimeMap.put(key, System.currentTimeMillis());
replace = true;
}
}
return replace;
}
private boolean isExpiredValue(Long val) {
return val == null || Math.abs(System.currentTimeMillis() - val.longValue()) >= timeout;
}
private void removeTimeoutItems() {
if (MapUtils.isEmpty(lastTickTimeMap)) {
return;
}
synchronized (lastTickTimeMap) {
Iterator<Map.Entry<String, Long>> iterator = lastTickTimeMap.entrySet().iterator();
while (iterator.hasNext()) {
if (isExpiredValue(iterator.next().getValue())) {
iterator.remove();
}
}
}
}
/**
* debounce to execute specified lambda function
*
* @param callable lambda function to be call if available
* @return Optional<T>
*/
public Optional<T> debounce(Callable<T> callable) {
try {
if (replaceIfTimeout(REMOVE_TIMEOUT_KEY)) {
removeTimeoutItems();
}
return Optional.ofNullable(callable.call());
} catch (Exception e) {
log.error("debounce", e);
}
return Optional.empty();
}
public Optional<T> debounce(Callable<T> callable, String key) {
return replaceIfTimeout(key) ? debounce(callable) : Optional.empty();
}
}