概念

防抖(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版的防抖(debounce)和节流(throttle)的更多相关文章

  1. 防抖debounce和节流throttle

    大纲 一.出现缘由 二.什么是防抖debounce和节流throttle 三.应用场景 3.1防抖 3.2节流 一.出现缘由 前端开发中,有一部分用户行为会频繁触发事件,而对于DOM操作,资源加载等耗 ...

  2. js 函数的防抖(debounce)与节流(throttle) 带 插件完整解析版 [helpers.js]

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         函数防抖与节流是做什么的?下面进行通俗的讲解. 本文借鉴:h ...

  3. js 函数的防抖(debounce)与节流(throttle)

    原文:函数防抖和节流: 序言: 我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll等等,有些时候,我们并不能或者不想频 ...

  4. js 防抖 debounce 与 节流 throttle

    debounce(防抖) 与 throttle(节流) 主要是用于用户交互处理过程中的性能优化.都是为了避免在短时间内重复触发(比如scrollTop等导致的回流.http请求等)导致的资源浪费问题. ...

  5. 防抖(Debounce)与节流( throttle)区别

    http://www.cnblogs.com/ShadowLoki/p/3712048.html http://blog.csdn.net/tina_ttl/article/details/51830 ...

  6. JavaScript 防抖(debounce)和节流(throttle)

    防抖函数 触发高频事件后,n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 /** * * @param {*} fn :callback function * @param {* ...

  7. C#.Net下的防抖-Debounce和节流阀-Throttle功能实现

    C#下的防抖-Debounce.节流阀-Throttle功能实现 防抖-Debounce 连续的多次调用,只有在调用停止之后的一段时间内不再调用,然后才执行一次处理过程. 节流阀-Throttle 连 ...

  8. 节流throttle和防抖debounce

    underscore.js提供了很多很有用的函数,今天想说说其中的两个.这两个函数都用于限制函数的执行. debounce 在解释这个函数前,我们先从一个例子看下这个函数的使用场景.假设我们网站有个搜 ...

  9. [JavaScript] 函数节流(throttle)和函数防抖(debounce)

    js 的函数节流(throttle)和函数防抖(debounce)概述 函数防抖(debounce) 一个事件频繁触发,但是我们不想让他触发的这么频繁,于是我们就设置一个定时器让这个事件在 xxx 秒 ...

随机推荐

  1. 个人冲刺(三)——体温上报app(一阶段)

    任务:完成了app第二页面的页面布局 activity_second.xml <?xml version="1.0" encoding="utf-8"?& ...

  2. python初识数据类型(字典、集合、元组、布尔)与运算符

    目录 python数据类型(dict.tuple.set.bool) 字典 集合 元组 布尔值 用户交互与输出 获取用户输入 输出信息 格式化输出 基本运算符 算术运算符 比较运算符 逻辑运算符 赋值 ...

  3. 【Unity Shader学习笔记】Unity基础纹理-渐变纹理

    纹理可以用来存储任何表面属性. 可以通过使用渐变纹理来实现插画风格的渲染效果. 这项技术是由Valve公司提出的.Valve使用它来渲染游戏中具有插画风格的角色. 我们使用半兰伯特模型计算漫反射. 因 ...

  4. mac上使用Vmware Fusion虚拟机配置Centos的静态ip

    一.背景 本文简单记录一下,在mac arm 架构下使用 Vmware Fusion虚拟机下Centos7下如何配置静态ip地址.如果使用dhcp静态ip地址的动态分配,那么可能ip地址会发生变化,因 ...

  5. SAP Web Dynpro-版本管理

    您可以使用版本管理来管理对象的旧版本,比较版本,也可以重置它们. 在版本管理中,您可以存储ABAP开发对象的不同版本. 在ABAP工作台中,您可以比较不同版本的- 视图 视窗 控制器 您也可以存储对象 ...

  6. ms08-067漏洞复现

    一.环境说明 kali linux 靶机是 XP (xp启动445) 二.nmap扫描主机存在的漏洞 root@kali:~# nmap --script smb-vuln* 192.168.244. ...

  7. 来用python自己做一个闹钟吧

    闹钟 是一种具有可以在预先设定的时间被激活以响铃的功能的时钟,用于唤醒打工人们. 使用Python中的DateTime模块来创建闹钟,并用Python中的playsound库来播放闹钟声音.~~~## ...

  8. identityserver4 (ids4)中如何获取refresh_token刷新令牌token 使用offline_access作用域

    ids4默认自带的api接口/api/connect/token 调用这个接口的时候,需要在body里面的 x-www-form-urlencoded模式下写 {     grant_type: &q ...

  9. 还在因为部署 Kubernetes 时,无法拉取 k8s.gcr.io/*** 镜像而头疼吗

    拉取外网 Kubernetes 镜像 还在因为部署 Kubernetes 时,无法拉取 k8s.gcr.io/*** 镜像而头疼吗? 传送门 https://github.com/liamhao/pu ...

  10. halcon数组的一些使用

    没啥好讲的,这里对于不是数组部分的东西就不进行讲解了. area_center(RegionOpening,Area, Row, Column).使用area_center来求区域的中心和面积时,返回 ...