展开菜单
首页 精品内容 本月促销 装机必备 Windows macOS软件 IOS软件 Android AI PDF教程 专题
全部分类

当前位置:

首页 > 编程开发 > Java中的防抖和节流如何实现

Java中的防抖和节流如何实现

概念防抖(debounce)当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。防抖,即如果短时间内大量触发同一事件,都会重置计时器,等到事件不触发了,再等待规定的事件,才会执行函数。而这整个过程就触发了一次点赞函数到服务器。原理:设置一个定时器,设置在规定的时间后触发事件处理,每次触发事件都会重置计时器。举例:很简单的例子,就是如果你疯狂的给朋友圈点赞再取消点赞,这个过程都会把计时器清空,等到你点累了不点了,等待0.5秒,才会触发函

概念

防抖(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 爆红线了,强迫症的我受不了,肯定要解决它

Java中的防抖和节流如何实现

解决方法1

脑子第一时间冒出来的是 @SuppressWarnings("all") 注解,跟所有的警告说拜拜~瞬间就清爽了

Java中的防抖和节流如何实现

解决方法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
相关文章 更多
精品专题 更多
本月促销

正软商城本月促销专区,汇集办公、设计、安全、影音、系统工具及AI软件等正版软件优惠活动,提供限时折扣、特价授权和优惠购买信息,活动库存及价格以页面实时展示为准。

装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

Mac软件 更多
灵活计算器
灵活计算器

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

图几
图几

图几是一款适用于 macOS 的截图、标注与美化工具,支持离线操作保障隐私。界面整理和高频系统操作被放到一起考虑,桌面或窗口内容一多时,管理起来会更省心。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Menuist
Menuist

Menuist 是一款面向 macOS 的 Finder 右键菜单增强工具,主要用来补充新建文件、快捷导航等常用操作,让日常文件管理和访问路径时更高效、更顺手。

Mole
Mole

Mole 是一款专为 Mac 设计的深度清理优化工具,涵盖缓存清理、应用管理及实时状态监控等功能。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

WINDOWS 更多
Windows 10
Windows 10

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

傲梅轻松备份
傲梅轻松备份

傲梅轻松备份是一款专业易用的数据备份软件,为重要数据提供安全保障。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Wise Folder Hider Pro
Wise Folder Hider Pro

Wise Folder Hider Pro 是一款专业级文件和文件夹隐藏加密软件,为私密数据添加多重保护。高频操作更强调就近处理,浏览、整理和跨目录移动文件时,来回切换和重复点击都会少很多。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

PinStack
PinStack

PinStack是一款轻量级的Windows平台剪贴板管理工具,优化您的剪贴板使用体验。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Seer
Seer

Seer是一款在Win平台下的空格键功能增强效率工具,只需轻敲空格键,就能预览几乎任何格式的文件。它更适合把零散的小功能集中起来使用,处理高频琐碎任务时会更省事。