2011-01-20 100 views
16

对于我正在写的一些代码,我可以在Java中使用一个很好的通用实现debounce在Java中实现去抖动

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

call()被多次调用在interval毫秒具有相同参数的回调函数应调用一次。

的可视化:

Debouncer#call xxx x xxxxxxx  xxxxxxxxxxxxxxx 
Callback#call  x   x      x (interval is 2) 
  • 不(像)这个已经存在的一些Java标准库?
  • 你将如何实现?
+0

看起来像[java.util.concurrency](http://download.oracle.com/javase/1.5 .0/docs/api/java/util/concurrent/package-summary.html)提供了积木 – levinalex 2011-01-20 00:38:35

+2

我知道这是一个老问题,但几个月前我在这里发布了一个类似的问题:http:// stackoverflow。 COM /问题/ 18723112 /取消法通话-WH en-the-same-method-is-called-multiple-time/18758408#18758408并在GitHub上提供了一个可能有兴趣的可重用实现 – ARRG 2014-01-07 07:41:08

回答

19

请考虑以下线程安全的解决方案。请注意,锁粒度在密钥级别上,因此只有同一个密钥上的调用彼此阻塞。它还处理呼叫(K)被调用时发生的密钥K到期的情况。

public class Debouncer <T> { 
    private final ScheduledExecutorService sched = Executors.newScheduledThreadPool(1); 
    private final ConcurrentHashMap<T, TimerTask> delayedMap = new ConcurrentHashMap<T, TimerTask>(); 
    private final Callback<T> callback; 
    private final int interval; 

    public Debouncer(Callback<T> 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); 
      } 
     } 
     } 
    } 
    } 
4

我不知道它是否存在,但它应该很容易实现。

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<Object,long>而不只是一个long来跟踪最后的执行时间。

+0

我需要的是相反的。应该在每一串呼叫的_end_处调用回调。 (我想用它来实现[this](http://stackoverflow.com/questions/4742017/avoid-detecting-incomplete-files-when-watching-a-directory-for-changes-in-java))似乎需要线程/超时 – levinalex 2011-01-20 00:30:16

+1

@levinalex我仍然认为你可以使它以这种方式工作,但如果你不这样做,不要使用线程,而是使用`Timer`或`ScheduledExecutorService`,它会更清洁和更安全。 – biziclop 2011-01-20 00:35:33

0

这看起来像它可以工作:

class Debouncer implements Callback { 
    private Callback callback; 
    private Map<Integer, Timer> scheduled = new HashMap<Integer, Timer>(); 
    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); 
    } 
} 
1

以下实现对基于处理程序的线程(例如主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; 
     } 
    } 
} 
6

这里是我的实现:

public class Debouncer { 
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); 
    private final ConcurrentHashMap<Object, Future<?>> delayedMap = new ConcurrentHashMap<>(); 

    /** 
    * Debounces {@code callable} by {@code delay}, i.e., 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);