2011-01-20 97 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);