在Java中实现debounce

在Java中实现debounce,java,algorithm,Java,Algorithm,对于我正在编写的一些代码,我可以使用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) { // sho

对于我正在编写的一些代码,我可以使用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      x           x                      x  (interval is 2)
  • 在某些Java标准库中是否已经存在(类似的)这种情况
  • 你将如何实现这一点

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

      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 );
            } 
        }
      }
      
      类去Bouncer实现回调{
      私有回调c;
      私人易失性长;
      私有整数间隔;
      公共去Bouncer(回调c,整数间隔){
      //初始化字段
      }
      公共无效调用(对象arg){
      if(lastCalled+interval

      当然,这个例子过于简化了一点,但这或多或少就是您所需要的。如果要为不同的参数保留单独的超时,则需要一个
      映射
      ,而不仅仅是一个
      长的
      ,以跟踪上一次执行时间。

      这看起来可以工作:

      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);
          }
      }
      
      类去Bouncer实现回调{
      私有回调;
      private Map scheduled=新建HashMap();
      私有整数延迟;
      公共去Bouncer(回调c,整数延迟){
      this.callback=c;
      延迟=延迟;
      }
      公共作废调用(最终对象参数){
      final int h=arg.hashCode();
      计时器任务=已计划。删除(h);
      如果(task!=null){task.cancel();}
      任务=新计时器();
      预定。放置(h,任务);
      task.schedule(新的TimerTask(){
      @凌驾
      公开募捐{
      callback.call(arg);
      预定。移除(h);
      }
      },这是延迟);
      }
      }
      
      <代码> < P>请考虑以下线程安全解决方案。请注意,锁粒度是在密钥级别上的,因此只有在相同的密钥块上相互调用。它还处理调用(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);
                }
              }
            }
          }  
        }
      
      公共类去Bouncer{
      private final ScheduledExecutorService sched=Executors.newScheduledThreadPool(1);
      私有最终ConcurrentHashMap delayedMap=新ConcurrentHashMap();
      私有最终回调;
      私有最终整数间隔;
      公共去Bouncer(回调c,int interval){
      this.callback=c;
      这个。间隔=间隔;
      }
      公共无效调用(T键){
      TimerTask任务=新的TimerTask(键);
      TimerTask prev;
      做{
      prev=delayedMap.putIfAbsent(键,任务);
      if(prev==null)
      计划(任务、间隔、时间单位毫秒);
      }while(prev!=null&&!prev.extend());//仅当新任务添加到映射中或现有任务已成功扩展时退出
      }
      公共无效终止(){
      计划关闭现在();
      }
      //等待时间过后唤醒的任务
      私有类TimerTask实现可运行{
      私钥;
      私人长时间;
      私有最终对象锁=新对象();
      公共时间任务(T密钥){
      this.key=key;
      扩展();
      }
      公共布尔扩展(){
      已同步(锁定){
      if(dueTime<0)//任务已关闭
      返回false;
      dueTime=System.currentTimeMillis()+间隔;
      返回true;
      }
      }
      公开募捐{
      已同步(锁定){
      剩余时间长=dueTime-System.currentTimeMillis();
      如果(剩余>0){//重新安排任务
      sched.schedule(此,剩余,时间单位为毫秒);
      }else{//标记为已终止并调用回调
      dueTime=-1;
      试一试{
      callback.call(键);
      }最后{
      delayedMap.remove(键);
      }
      }
      }
      }  
      }
      
      和回调接口:

      public interface Callback<T> {
          public void call(T t);
      }
      
      公共接口回调{
      公共无效呼叫(T);
      }
      
      以下实现适用于基于处理程序的线程(例如,主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;
              }
          }
      }
      

      以下是我的实现:

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

      我的实现,非常容易使用,2个用于去盎司和节流的util方法,将您的runnable传递给它,以获得去盎司/节流的runnable

      package basic.thread.utils;
      
      public class ThreadUtils {
          /** Make a runnable become debounce
           * 
           * usage: to reduce the real processing for some task
           * 
           * example: the stock price sometimes probably changes 1000 times in 1 second,
           *  but you just want redraw the candlestick of k-line chart after last change+"delay ms"
           * 
           * @param realRunner Runnable that has something real to do
           * @param delay milliseconds that realRunner should wait since last call
           * @return
           */
          public static Runnable debounce (Runnable realRunner, long delay) {
              Runnable debounceRunner = new Runnable() {
                  // whether is waiting to run
                  private boolean _isWaiting = false;
                  // target time to run realRunner
                  private long _timeToRun;
                  // specified delay time to wait
                  private long _delay = delay;
                  // Runnable that has the real task to run
                  private Runnable _realRunner = realRunner;
                  @Override
                  public void run() {
                      // current time
                      long now;
                      synchronized (this) {
                          now = System.currentTimeMillis();
                          // update time to run each time
                          _timeToRun = now+_delay;
                          // another thread is waiting, skip
                          if (_isWaiting) return;
                          // set waiting status
                          _isWaiting = true;
                      }
                      try {
                          // wait until target time
                          while (now < _timeToRun) {
                              Thread.sleep(_timeToRun-now);
                              now = System.currentTimeMillis();
                          }
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      } finally {
                          // clear waiting status before run
                          _isWaiting = false;
                          // do the real task
                          _realRunner.run();
                      }
                  }};
              return debounceRunner;
          }
          /** Make a runnable become throttle
           * 
           * usage: to smoothly reduce running times of some task
           * 
           * example: assume the price of a stock often updated 1000 times per second
           * but you want to redraw the candlestick of k-line at most once per 300ms
           * 
           * @param realRunner
           * @param delay
           * @return
           */
          public static Runnable throttle (Runnable realRunner, long delay) {
              Runnable throttleRunner = new Runnable() {
                  // whether is waiting to run
                  private boolean _isWaiting = false;
                  // target time to run realRunner
                  private long _timeToRun;
                  // specified delay time to wait
                  private long _delay = delay;
                  // Runnable that has the real task to run
                  private Runnable _realRunner = realRunner;
                  @Override
                  public void run() {
                      // current time
                      long now;
                      synchronized (this) {
                          // another thread is waiting, skip
                          if (_isWaiting) return;
                          now = System.currentTimeMillis();
                          // update time to run
                          // do not update it each time since
                          // you do not want to postpone it unlimited
                          _timeToRun = now+_delay;
                          // set waiting status
                          _isWaiting = true;
                      }
                      try {
                          Thread.sleep(_timeToRun-now);
      
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      } finally {
                          // clear waiting status before run
                          _isWaiting = false;
                          // do the real task
                          _realRunner.run();
                      }
                  }};
              return throttleRunner;
          }
      }
      
      包basic.thread.utils;
      公共类ThreadUtils{
      /**使一个可运行的系统变得不稳定
      * 
      *用法:减少某些任务的实际处理
      * 
      *股票价格有时可能在1秒内变化1000次,
      *但你只需要在上次更改后重新绘制k线图的烛台+“延迟毫秒”
      * 
      *@param realRunner Runnable与实际事务有关
      *@param自上次调用后realRunner应等待的延迟毫秒数
      *@返回
      */
      公共静态Runnable debounce(Runnable realRunner,长延迟){
      Runnable debounceRunner=new Runnable(){
      //是否正在等待运行
      私有布尔值_isWaiting=false;
      //运行realRunner的目标时间
      私人长途电话;
      //指定的等待延迟时间
      专用长延时=延时;
      //Runnable,它具有要运行的实际任务
      私有Runnable _realRunner=realRunner;
      @凌驾
      公开募捐{
      //当前时间
      很久以前;
      已同步(此){
      现在=System.currentTimeMillis();
      //每次更新运行时间
      _timeToRun=现在+延迟;
      //另一个线程正在等待,跳过
      如果(正在等待)返回;
      //设置等待状态
      _isWaiting=true;
      }
      
      package basic.thread.utils;
      
      public class ThreadUtils {
          /** Make a runnable become debounce
           * 
           * usage: to reduce the real processing for some task
           * 
           * example: the stock price sometimes probably changes 1000 times in 1 second,
           *  but you just want redraw the candlestick of k-line chart after last change+"delay ms"
           * 
           * @param realRunner Runnable that has something real to do
           * @param delay milliseconds that realRunner should wait since last call
           * @return
           */
          public static Runnable debounce (Runnable realRunner, long delay) {
              Runnable debounceRunner = new Runnable() {
                  // whether is waiting to run
                  private boolean _isWaiting = false;
                  // target time to run realRunner
                  private long _timeToRun;
                  // specified delay time to wait
                  private long _delay = delay;
                  // Runnable that has the real task to run
                  private Runnable _realRunner = realRunner;
                  @Override
                  public void run() {
                      // current time
                      long now;
                      synchronized (this) {
                          now = System.currentTimeMillis();
                          // update time to run each time
                          _timeToRun = now+_delay;
                          // another thread is waiting, skip
                          if (_isWaiting) return;
                          // set waiting status
                          _isWaiting = true;
                      }
                      try {
                          // wait until target time
                          while (now < _timeToRun) {
                              Thread.sleep(_timeToRun-now);
                              now = System.currentTimeMillis();
                          }
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      } finally {
                          // clear waiting status before run
                          _isWaiting = false;
                          // do the real task
                          _realRunner.run();
                      }
                  }};
              return debounceRunner;
          }
          /** Make a runnable become throttle
           * 
           * usage: to smoothly reduce running times of some task
           * 
           * example: assume the price of a stock often updated 1000 times per second
           * but you want to redraw the candlestick of k-line at most once per 300ms
           * 
           * @param realRunner
           * @param delay
           * @return
           */
          public static Runnable throttle (Runnable realRunner, long delay) {
              Runnable throttleRunner = new Runnable() {
                  // whether is waiting to run
                  private boolean _isWaiting = false;
                  // target time to run realRunner
                  private long _timeToRun;
                  // specified delay time to wait
                  private long _delay = delay;
                  // Runnable that has the real task to run
                  private Runnable _realRunner = realRunner;
                  @Override
                  public void run() {
                      // current time
                      long now;
                      synchronized (this) {
                          // another thread is waiting, skip
                          if (_isWaiting) return;
                          now = System.currentTimeMillis();
                          // update time to run
                          // do not update it each time since
                          // you do not want to postpone it unlimited
                          _timeToRun = now+_delay;
                          // set waiting status
                          _isWaiting = true;
                      }
                      try {
                          Thread.sleep(_timeToRun-now);
      
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      } finally {
                          // clear waiting status before run
                          _isWaiting = false;
                          // do the real task
                          _realRunner.run();
                      }
                  }};
              return throttleRunner;
          }
      }
      
      public interface cbDebounce {
      
      void execute();
      
      }
      
      public class Debouncer {
      
      private Timer timer;
      private ConcurrentHashMap<String, TimerTask> delayedTaskMap;
      
      public Debouncer() {
          this.timer = new Timer(true); //run as daemon
          this.delayedTaskMap = new ConcurrentHashMap<>();
      }
      
      public void debounce(final String key, final cbDebounce debounceCallback, final long delay) {
          if (key == null || key.isEmpty() || key.trim().length() < 1 || delay < 0) return;
      
          cancelPreviousTasks(); //if any
      
          TimerTask timerTask = new TimerTask() {
              @Override
              public void run() {
                  debounceCallback.execute();
                  cancelPreviousTasks();
                  delayedTaskMap.clear();
                  if (timer != null) timer.cancel();
              }
          };
      
          scheduleNewTask(key, timerTask, delay);
      }
      
      private void cancelPreviousTasks() {
          if (delayedTaskMap == null) return;
      
          if (!delayedTaskMap.isEmpty()) delayedTaskMap
                  .forEachEntry(1000, entry -> entry.getValue().cancel());
      
          delayedTaskMap.clear();
      }
      
      private void scheduleNewTask(String key, TimerTask timerTask, long delay) {
          if (key == null || key.isEmpty() || key.trim().length() < 1 || timerTask == null || delay < 0) return;
      
          if (delayedTaskMap.containsKey(key)) return;
      
          timer.schedule(timerTask, delay);
      
          delayedTaskMap.put(key, timerTask);
      }
      
      public class Main {
      
      private static Debouncer debouncer;
      
      public static void main(String[] args) throws IOException, InterruptedException {
          debouncer = new Debouncer();
          search("H");
          search("HE");
          search("HEL");
          System.out.println("Waiting for user to finish typing");
          Thread.sleep(2000);
          search("HELL");
          search("HELLO");
      }
      
      private static void search(String searchPhrase) {
          System.out.println("Search for: " + searchPhrase);
          cbDebounce debounceCallback = () -> System.out.println("Now Executing search for: "+searchPhrase);
          debouncer.debounce(searchPhrase, debounceCallback, 4000); //wait 4 seconds after user's last keystroke
      }
      
      }
      
      import java.util.concurrent.ConcurrentHashMap;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledExecutorService;
      import java.util.concurrent.TimeUnit;
      
      public class Debouncer<T> {
      
          private final ScheduledExecutorService sched = Executors.newScheduledThreadPool(1);
          private final ConcurrentHashMap<T, TimerTask> delayedMap = new ConcurrentHashMap<T, TimerTask>();
      
          public Debouncer() {
          }
      
          public void call(T key, Runnable runnable, int interval, TimeUnit timeUnit) {
              TimerTask task = new TimerTask(key, runnable, interval, timeUnit);
      
              TimerTask prev;
              do {
                  prev = delayedMap.putIfAbsent(key, task);
                  if (prev == null)
                      sched.schedule(task, interval, timeUnit);
              } while (prev != null && !prev.extend());
          }
      
          public void terminate() {
              sched.shutdownNow();
          }
      
          private class TimerTask implements Runnable {
              private final T key;
              private final Runnable runnable;
              private final int interval;
              private final TimeUnit timeUnit;
              private long dueTime;
              private final Object lock = new Object();
      
              public TimerTask(T key, Runnable runnable, int interval, TimeUnit timeUnit) {
                  this.key = key;
                  this.runnable = runnable;
                  this.interval = interval;
                  this.timeUnit = timeUnit;
                  extend();
              }
      
              public boolean extend() {
                  synchronized (lock) {
                      if (dueTime < 0)
                          return false;
                      dueTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(interval, timeUnit);
                      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 {
                              runnable.run();
                          } finally {
                              delayedMap.remove(key);
                          }
                      }
                  }
              }
          }
      }
      
      
      import android.os.Handler;
      
      public class SimpleDebounce {
          protected Handler handler;
          protected IAfterDelay iAfterDelay;
          protected long last_time_invoke = 0;
          protected long delay;
      
          public SimpleDebounce() {
              this.handler = new Handler();
          }
      
          public SimpleDebounce(long delay, IAfterDelay iAfterDelay) {
              this();
              this.delay = delay;
              this.iAfterDelay = iAfterDelay;
          }
      
          public void after(long delay, IAfterDelay iAfterDelay) {
              this.delay = delay;
              this.iAfterDelay = iAfterDelay;
              this.iAfterDelay.loading(true);
              this.handler.removeCallbacks(execute);
              this.last_time_invoke = System.currentTimeMillis();
              this.handler.postDelayed(execute, delay);
          }
      
          public void cancelDebounce() {
              if (handler != null && iAfterDelay != null) {
                  handler.removeCallbacks(execute);
                  iAfterDelay.loading(false);
              }
          }
      
          public interface IAfterDelay {
              void fire();
      
              void loading(boolean state);
          }
      
          protected Runnable execute = () -> {
              if (System.currentTimeMillis() > (last_time_invoke + delay - 500)) {
                  if (iAfterDelay != null) {
                      iAfterDelay.loading(false);
                      iAfterDelay.fire();
                  }
              }
          };
      
      }
      
      
      
      public class MainActivity extends AppCompatActivity {
      
         private SimpleDebounce simpleDebounce;
         private long waitForMS = 5000;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_my_stocks);
      
              simpleDebounce = new SimpleDebounce();
      
              // You can click this button as many time as you want
              // It will reset the time and fire after ${waitForMS} milisecons
              // and in case you pressed this in the middle again, it will reset the time
              someButtonWhichStartsThis.setOnClickListener(e -> {
                 simpleDebounce.after(waitForMS, new SimpleDebounce.IAfterDelay() {
                      @Override
                      public void fire() {
                         // Your job...
                      }
      
                      @Override
                      public void loading(boolean state) {
                          if (state) turnOnProgress();
                          else turnOffProgress();
                      }
                });
              });
      
              // stop the future fire in the middle, if you want to
              someButtonWhichStopsThis.setOnClickListener(e -> simpleDebounce.cancelDebounce());
      
          }
         
      
      }