在Java中实现debounce
对于我正在编写的一些代码,我可以使用Java中的在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
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());
}
}