在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> delayedMap = new ConcurrentHashMap<>(); /** * Debounces {@code callable} by {@code delay}, ie, schedules it to be executed after {@code delay}, * or cancels its execution if the method is called with the same key within the {@code delay} again. */ public void debounce(final Object key, final Runnable runnable, long delay, TimeUnit unit) { final Future prev = delayedMap.put(key, scheduler.schedule(new Runnable() { @Override public void run() { try { runnable.run(); } finally { delayedMap.remove(key); } } }, delay, unit)); if (prev != null) { prev.cancel(true); } } public void shutdown() { scheduler.shutdownNow(); } } 

用法示例:

 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); } }