在Java中实现去抖动
对于我正在编写的一些代码,我可以在Java中使用很好的debounce
一般实现。
public interface Callback { public void call(Object arg); } class Debouncer implements Callback { public Debouncer(Callback c, int interval) { ... } public void call(Object arg) { // should forward calls with the same arguments to the callback c // but batch multiple calls inside `interval` to a single one } }
当使用相同的参数以interval
毫秒多次call()
应该只调用一次回调函数。
可视化:
Debouncer#call xxx x xxxxxxx xxxxxxxxxxxxxxx Callback#call xxx (interval is 2)
- 某些Java标准库中是否存在(类似的)?
- 你会如何实现?
请考虑以下线程安全解决方案。 请注意,锁粒度在密钥级别上,因此只有相同密钥的调用才会相互阻塞。 它还处理在调用call(K)时发生的密钥K到期的情况。
public class Debouncer { private final ScheduledExecutorService sched = Executors.newScheduledThreadPool(1); private final ConcurrentHashMap delayedMap = new ConcurrentHashMap(); private final Callback callback; private final int interval; public Debouncer(Callback c, int interval) { this.callback = c; this.interval = interval; } public void call(T key) { TimerTask task = new TimerTask(key); TimerTask prev; do { prev = delayedMap.putIfAbsent(key, task); if (prev == null) sched.schedule(task, interval, TimeUnit.MILLISECONDS); } while (prev != null && !prev.extend()); // Exit only if new task was added to map, or existing task was extended successfully } public void terminate() { sched.shutdownNow(); } // The task that wakes up when the wait time elapses private class TimerTask implements Runnable { private final T key; private long dueTime; private final Object lock = new Object(); public TimerTask(T key) { this.key = key; extend(); } public boolean extend() { synchronized (lock) { if (dueTime < 0) // Task has been shutdown return false; dueTime = System.currentTimeMillis() + interval; return true; } } public void run() { synchronized (lock) { long remaining = dueTime - System.currentTimeMillis(); if (remaining > 0) { // Re-schedule task sched.schedule(this, remaining, TimeUnit.MILLISECONDS); } else { // Mark as terminated and invoke callback dueTime = -1; try { callback.call(key); } finally { delayedMap.remove(key); } } } } }
这是我的实现:
public class Debouncer { private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ConcurrentHashMap
用法示例:
final Debouncer debouncer = new Debouncer(); debouncer.debounce(Void.class, new Runnable() { @Override public void run() { // ... } }, 300, TimeUnit.MILLISECONDS);
我不知道它是否存在但应该很容易实现。
class Debouncer implements Callback { private CallBack c; private volatile long lastCalled; private int interval; public Debouncer(Callback c, int interval) { //init fields } public void call(Object arg) { if( lastCalled + interval < System.currentTimeMillis() ) { lastCalled = System.currentTimeMillis(); c.call( arg ); } } }
当然这个例子过于简单了,但这或多或少都是你需要的。 如果要为不同的参数保留单独的超时,则需要一个Map
而不是一个long
来跟踪上次执行时间。
以下实现适用于基于Handler的线程(例如主UI线程或IntentService)。 它只希望从创建它的线程调用,并且它还将在该线程上运行它的操作。
public class Debouncer { private CountDownTimer debounceTimer; private Runnable pendingRunnable; public Debouncer() { } public void debounce(Runnable runnable, long delayMs) { pendingRunnable = runnable; cancelTimer(); startTimer(delayMs); } public void cancel() { cancelTimer(); pendingRunnable = null; } private void startTimer(final long updateIntervalMs) { if (updateIntervalMs > 0) { // Debounce timer debounceTimer = new CountDownTimer(updateIntervalMs, updateIntervalMs) { @Override public void onTick(long millisUntilFinished) { // Do nothing } @Override public void onFinish() { execute(); } }; debounceTimer.start(); } else { // Do immediately execute(); } } private void cancelTimer() { if (debounceTimer != null) { debounceTimer.cancel(); debounceTimer = null; } } private void execute() { if (pendingRunnable != null) { pendingRunnable.run(); pendingRunnable = null; } } }
看起来它可以工作:
class Debouncer implements Callback { private Callback callback; private Map scheduled = new HashMap(); private int delay; public Debouncer(Callback c, int delay) { this.callback = c; this.delay = delay; } public void call(final Object arg) { final int h = arg.hashCode(); Timer task = scheduled.remove(h); if (task != null) { task.cancel(); } task = new Timer(); scheduled.put(h, task); task.schedule(new TimerTask() { @Override public void run() { callback.call(arg); scheduled.remove(h); } }, this.delay); } }