当前位置:

首页 > 编程开发 > 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–当前任务将被取消,但其余任务将继续运行。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

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

    Windows
    Windows

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

    PDF教程
    PDF教程

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

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。