概念防抖(debounce)当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。
防抖,即如果短时间内大量触发同一事件,都会重置计时器,等到事件不触发了,再等待规定的事件,才会执行函数。而这整个过程就触发了一次点赞函数到服务器。原理:设置一个定时器,设置在规定的时间后触发事件处理,每次触发事件都会重置计时器。
举例:很简单的例子,就是如果你疯狂的给朋友圈点赞再取消点赞,这个过程都会把计时器清空,等到你点累了不点了,等待0.5秒,才会触发函数,把你最终结果传给服务器。
问题1:那既然是这样,让前端做防抖不就好了嘛。答案是可以,但是会失去用户体验。本来有的用户点赞就是为了玩,现在你前端直接提示操作太快~请歇会。用户是不是就失去了乐趣,这一点还得参考qq空间的点赞,虽然我不知道它是不是用了防抖,但是他把点赞,取消点赞做成了动画,这样每次用户操作的时候,都会跳出执行动画,大大增加了用户的体验性。
问题2:那么问题来了,在一定时间内,一直点击,就会重置计时器。那要是点击一天一夜,是不是他就不会在执行了呢。理论上是这样,但是人会累的嘛。总不能一直战斗是吧。所以人做不到,只能是机器、脚本来处理了,那也正好,防抖还能用来阻挡部分脚本攻击。
节流(throttle)当持续触发事件时,保证在一定时间内只调用一次事件处理函数,意思就是说,假设一个用户一直触发这个函数,且每次触发小于既定值,函数节流会每隔这个时间调用一次。
想到这里,很多人就会想到一个作用,没错,就是防重复提交。但是这个业务时间比较难把控,所以还是建议用它来做一些无状态的操作比较好。比如说:刷新排行榜,前端看似一直在点击,其实后端为了防止接口崩掉,每1秒才执行真正的一次刷新。
区别防抖是将多次执行变为指定时间内不在触发之后,执行一次。
节流是将多次执行变为指定时间不论触发多少次,时间一到就执行一次
java实现java实现防抖和节流的关键是timer类和runnable接口。
其中,timer中关键方法cancel() 实现防抖 schedule() 实现节流。下面简单介绍一下这两个方法。
timer##cancel():timer.cancel() 被调用之后整个timer 的 线程都会结束掉。
这就很好理解了,既然是做防抖,只要你在指定时间内触发,我直接 cancel()掉,就是取消掉,不让他执行。
timer##schedule():用户调用 schedule() 方法后,要等待n秒的时间才可以第一次执行 run() 方法。
这个n是我们根据业务评估出来的时间,作为参数传进去。
防抖(debounce)package com.example.test01.zhangch;import java.util.timer;import java.util.timertask;/** * @author zhangch * @description java 防抖 * @date 2022/8/4 18:18 * @version 1.0 */@suppresswarnings("all")public class debouncetask { /** * 防抖实现关键类 */ private timer timer; /** * 防抖时间:根据业务评估 */ private long delay; /** * 开启线程执行任务 */ private runnable runnable; public debouncetask(runnable runnable, long delay) { this.runnable = runnable; this.delay = delay; } /** * * @param runnable 要执行的任务 * @param delay 执行时间 * @return 初始化 debouncetask 对象 */ public static debouncetask build(runnable runnable, long delay){ return new debouncetask(runnable, delay); } //timer类执行:cancel()-->取消操作;schedule()-->执行操作 public void timerrun(){ //如果有任务,则取消不执行(防抖实现的关键) if(timer!=null){ timer.cancel(); } timer = new timer(); timer.schedule(new timertask() { @override public void run() { //把 timer 设置为空,这样下次判断它就不会执行了 timer=null; //执行 runnable 中的 run()方法 runnable.run(); } }, delay); }}
防抖测试1可以看到,测试中,我 1 毫秒请求一次,这样的话,1秒内都存在连续请求,防抖操作永远不会执行。
public static void main(string[] args){ //构建对象,1000l: 1秒执行-->1秒内没有请求,在执行防抖操作 debouncetask task = debouncetask.build(new runnable() { @override public void run() { system.out.println("防抖操作执行了:do task: "+system.currenttimemillis()); } },1000l); long delay = 100; while (true){ system.out.println("请求执行:call task: "+system.currenttimemillis()); task.timerrun(); try { //休眠1毫秒在请求 thread.sleep(delay); } catch (interruptedexception e) { e.printstacktrace(); } }}
结果如我们所料:
connected to the target vm, address: '127.0.0.1:5437', transport: 'socket'
请求执行:call task: 1659609433021
请求执行:call task: 1659609433138
请求执行:call task: 1659609433243
请求执行:call task: 1659609433350
请求执行:call task: 1659609433462
请求执行:call task: 1659609433572
请求执行:call task: 1659609433681
请求执行:call task: 1659609433787
请求执行:call task: 1659609433893
请求执行:call task: 1659609433999
请求执行:call task: 1659609434106
请求执行:call task: 1659609434215
请求执行:call task: 1659609434321
请求执行:call task: 1659609434425
请求执行:call task: 1659609434534
防抖测试2测试2中,我们在请求了2秒之后,让主线程休息2秒,这个时候,防抖在1秒内没有在次触发,所以就会执行一次防抖操作。
public static void main(string[] args){ //构建对象,1000l:1秒执行 debouncetask task = debouncetask.build(new runnable() { @override public void run() { system.out.println("防抖操作执行了:do task: "+system.currenttimemillis()); } },1000l); long delay = 100; long doudelay = 0; while (true){ system.out.println("请求执行:call task: "+system.currenttimemillis()); task.timerrun(); doudelay = doudelay+100; try { //如果请求执行了两秒,我们让他先休息两秒,在接着请求 if (doudelay == 2000){ thread.sleep(doudelay); } thread.sleep(delay); } catch (interruptedexception e) { e.printstacktrace(); } }}
结果如我们所料,防抖任务触发了一次,休眠结束之后,请求就会在1毫秒内连续触发,防抖也就不会在次触发了。
请求执行:call task: 1659609961816
请求执行:call task: 1659609961924
请求执行:call task: 1659609962031
请求执行:call task: 1659609962138
请求执行:call task: 1659609962245
请求执行:call task: 1659609962353
防抖操作执行了:do task: 1659609963355
请求执行:call task: 1659609964464
请求执行:call task: 1659609964569
请求执行:call task: 1659609964678
请求执行:call task: 1659609964784
防抖测试简易版简易版:根据新手写代码习惯,对代码写法做了调整,但是不影响整体功能。这种写法更加符合我这种新手小白的写法。
public static void main(string[] args){ //要执行的任务,因为 runnable 是接口,所以 new 对象的时候要实现它的 run方法 runnable runnable = new runnable() { @override public void run() { //执行打印,真实开发中,是这些我们的业务代码。 system.out.println("防抖操作执行了:do task: "+system.currenttimemillis()); } }; //runnable:要执行的任务,通过参数传递进去。1000l:1秒执行内没有请求,就执行一次防抖操作 debouncetask task = debouncetask.build(runnable,1000l); //请求持续时间 long delay = 100; //休眠时间,为了让防抖任务执行 long doudelay = 0; //while 死循环,请求一直执行 while (true){ system.out.println("请求执行:call task: "+system.currenttimemillis()); //调用 debouncetask 防抖类中的 timerrun() 方法, 执行防抖任务 task.timerrun(); doudelay = doudelay+100; try { //如果请求执行了两秒,我们让他先休息两秒,在接着请求 if (doudelay == 2000){ thread.sleep(doudelay); } thread.sleep(delay); } catch (interruptedexception e) { e.printstacktrace(); } }}
节流(throttle)package com.example.test01.zhangch;import java.util.timer;import java.util.timertask;/** * @author zhangch * @description 节流 * @date 2022/8/6 15:41 * @version 1.0 */public class throttletask { /** * 节流实现关键类 */ private timer timer; private long delay; private runnable runnable; private boolean needwait=false; /** * 有参构造函数 * @param runnable 要启动的定时任务 * @param delay 延迟时间 */ public throttletask(runnable runnable, long delay) { this.runnable = runnable; this.delay = delay; this.timer = new timer(); } /** * build 创建对象,相当于 throttletask task = new throttletask(); * @param runnable 要执行的节流任务 * @param delay 延迟时间 * @return throttletask 对象 */ public static throttletask build(runnable runnable, long delay){ return new throttletask(runnable, delay); } public void taskrun(){ //如果 needwait 为 false,结果取反,表达式为 true。执行 if 语句 if(!needwait){ //设置为 true,这样下次就不会再执行 needwait=true; //执行节流方法 timer.schedule(new timertask() { @override public void run() { //执行完成,设置为 false,让下次操作再进入 if 语句中 needwait=false; //开启多线程执行 run() 方法 runnable.run(); } }, delay); } }}
节流测试1节流测试,每 2ms 请求一次,节流任务是每 1s 执行一次。真实效果应该是 1s 内前端发起了五次请求,但是后端只执行了一次操作
public static void main(string[] args){ //创建节流要执行的对象,并把要执行的任务传入进去 throttletask task = throttletask.build(new runnable() { @override public void run() { system.out.println("节流任务执行:do task: "+system.currenttimemillis()); } },1000l); //while一直执行,模拟前端用户一直请求后端 while (true){ system.out.println("前端请求后端:call task: "+system.currenttimemillis()); task.taskrun(); try { thread.sleep(200); } catch (interruptedexception e) { e.printstacktrace(); } }}
结果如我们所料
前端请求后端:call task: 1659772459363
前端请求后端:call task: 1659772459574
前端请求后端:call task: 1659772459780
前端请求后端:call task: 1659772459995
前端请求后端:call task: 1659772460205
节流任务执行:do task: 1659772460377
前端请求后端:call task: 1659772460409
前端请求后端:call task: 1659772460610
前端请求后端:call task: 1659772460812
前端请求后端:call task: 1659772461027
前端请求后端:call task: 1659772461230
节流任务执行:do task: 1659772461417
彩蛋idea 爆红线了,强迫症的我受不了,肯定要解决它
解决方法1脑子第一时间冒出来的是 @suppresswarnings("all") 注解,跟所有的警告说拜拜~瞬间就清爽了
解决方法2算了,压制警告总感觉是不负责任。总不能这样草草了事,那就来直面这个爆红。既然让我用 scheduledexecutorservice ,那简单,直接替换
public class throttletask { /** * 节流实现关键类: */ private scheduledexecutorservice timer; private long delay; private runnable runnable; private boolean needwait=false; /** * 有参构造函数 * @param runnable 要启动的定时任务 * @param delay 延迟时间 */ public throttletask(runnable runnable, long delay) { this.runnable = runnable; this.delay = delay; this.timer = executors.newsinglethreadscheduledexecutor(); } /** * build 创建对象,相当于 throttletask task = new throttletask(); * @param runnable 要执行的节流任务 * @param delay 延迟时间 * @return throttletask 对象 */ public static throttletask build(runnable runnable, long delay){ return new throttletask(runnable, delay); } public void taskrun(){ //如果 needwait 为 false,结果取反,表达式为 true。执行 if 语句 if(!needwait){ //设置为 true,这样下次就不会再执行 needwait=true; //执行节流方法 timer.schedule(new timertask() { @override public void run() { //执行完成,设置为 false,让下次操作再进入 if 语句中 needwait=false; //开启多线程执行 run() 方法 runnable.run(); } }, delay,timeunit.milliseconds); } }}
那么定时器 timer 和 scheduledthreadpoolexecutor 解决方案之间的主要区别是什么,我总结了三点...
定时器对系统时钟的变化敏感;scheduledthreadpoolexecutor并不会。
定时器只有一个执行线程;scheduledthreadpoolexecutor可以配置任意数量的线程。
timertask中抛出的运行时异常会杀死线程,因此后续的计划任务不会继续运行;使用scheduledthreadexecutor–当前任务将被取消,但其余任务将继续运行。
以上就是java中的防抖和节流如何实现的详细内容。