java接口限流详解

目录

  • 1.简介
    • 1.1.为什么需要限流?
    • 1.2.限流和熔断有什么区别?
    • 1.3.限流和削峰有什么区别?
    • 1.4 缓存,降级,限流简介
  • 2.应用级限流
    • 2.1 控制并发数量
    • 2.2 控制访问速率
      • 2.2.1 令牌桶算法
      • 2.2.2 漏桶算法
  • 3.分布式限流
  • 4.交流群

1.简介

接口限流是对某一时间窗口内的请求数进行限制,以保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机。此外,接口限流也可以通过限制每个用户或每个接口调用的频率和并发数,来控制对服务资源的访问。

1.1.为什么需要限流?

大量正常用户高频访问导致服务器宕机
恶意用户高频访问导致服务器宕机
网页爬虫 ,对于这些情况我们需要对用户的访问进行限流访问

1.2.限流和熔断有什么区别?

限流发生在流量进来之前,超过的流量进行限制。

熔断是一种应对故障的机制,发生在流量进来之后,如果系统发生故障或者异常,熔断会自动切断请求,防止故障进一步扩展,导致服务雪崩。

1.3.限流和削峰有什么区别?

削峰是对流量的平滑处理,通过缓慢地增加请求的处理速率来避免系统瞬时过载。

削峰大概就是水库,把流量储存起来,慢慢流,限流大概就是闸口,拒绝超出的流量。

1.4 缓存,降级,限流简介

  • 缓存 缓存的目的是提升系统访问速度和增大系统处理容量
  • 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开
  • 限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理

2.应用级限流

2.1 控制并发数量

在Java中,可以使用信号量(Semaphore)机制来控制并发数量。信号量是一个计数器,用于限制对共享资源的访问。以下是一个使用信号量机制控制并发数量的示例:

import java.util.concurrent.Semaphore;  public class ConcurrencyControlExample {  private static final int MAX_CONCURRENT_THREADS = 5; // 最大并发线程数  private static Semaphore semaphore = new Semaphore(MAX_CONCURRENT_THREADS);  public static void main(String[] args) {  for (int i = 0; i < 10; i++) {  new Thread(new WorkerThread("" + i)).start();  }  }  static class WorkerThread implements Runnable {  private String command;  public WorkerThread(String s) {  this.command = s;  }  @Override  public void run() {  try {  // 获取信号量,如果信号量为0,则当前线程需要等待  semaphore.acquire();  // 执行任务  System.out.println(Thread.currentThread().getName() + "开始处理:" + command);  processCommand();  System.out.println(Thread.currentThread().getName() + "结束处理:" + command);  } catch (InterruptedException e) {  e.printStackTrace();  } finally {  // 释放信号量,允许其他线程获取信号量并执行任务  semaphore.release();  }  }  private void processCommand() {  try {  Thread.sleep(2000); // 模拟耗时任务  } catch (InterruptedException e) {  e.printStackTrace();  }  }  }  
}

2.2 控制访问速率

在工程实践中,常见的是使用令牌桶算法来实现这种模式,常用的限流算法有两种:漏桶算法和令牌桶算法。

2.2.1 令牌桶算法

如图所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务,令牌桶算法通过发放令牌,根据令牌的rate频率做请求频率限制,容量限制等。
在这里插入图片描述

在Wikipedia上,令牌桶算法是这么描述的:

1、每过1/r秒桶中增加一个令牌。
2、桶中最多存放b个令牌,如果桶满了,新放入的令牌会被丢弃。
3、当一个n字节的数据包到达时,消耗n个令牌,然后发送该数据包。
4、如果桶中可用令牌小于n,则该数据包将被缓存或丢弃。
令牌桶控制的是一个时间窗口内通过的数据量,在API层面我们常说的QPS、TPS,正好是一个时间窗口内的请求量或者事务量,只不过时间窗口限定在1s罢了。以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。令牌桶的另外一个好处是可以方便的改变速度,一旦需要提高速率,则按需提高放入桶中的令牌的速率。
在我们的工程实践中,通常使用Google开源工具包Guava提供的限流工具类RateLimiter来实现控制速率,该类基于令牌桶算法来完成限流,非常易于使用,而且非常高效。如我们不希望每秒的任务提交超过1个

  public static void main(String[] args) {String start = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());RateLimiter limiter = RateLimiter.create(1.0); // 这里的1表示每秒允许处理的量为1个for (int i = 1; i <= 10; i++) {double waitTime = limiter.acquire(i);// 请求RateLimiter, 超过permits会被阻塞System.out.println("cutTime=" + System.currentTimeMillis() + " call execute:" + i + " waitTime:" + waitTime);}String end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());System.out.println("start time:" + start);System.out.println("end time:" + end);}

首先通过RateLimiter.create(1.0);创建一个限流器,参数代表每秒生成的令牌数,通过limiter.acquire(i);来以阻塞的方式获取令牌,令牌桶算法允许一定程度的突发(允许消费未来的令牌),所以可以一次性消费i个令牌;当然也可以通过tryAcquire(int permits, long timeout, TimeUnit unit)来设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回,支持阻塞或可超时的令牌消费。

从输出来看,RateLimiter支持预消费,比如在acquire(5)时,等待时间是4秒,是上一个获取令牌时预消费了3个两排,固需要等待3*1秒,然后又预消费了5个令牌,以此类推。

ateLimiter通过限制后面请求的等待时间,来支持一定程度的突发请求(预消费),在使用过程中需要注意这一点,Guava有两种限流模式,一种为稳定模式(SmoothBursty:令牌生成速度恒定,平滑突发限流),一种为渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值,平滑预热限流) 两种模式实现思路类似,主要区别在等待时间的计算上。

  • SmoothBursty 模式:RateLimiter limiter = RateLimiter.create(5);
    RateLimiter.create(5)表示桶容量为5且每秒新增5个令牌,即每隔200毫秒新增一个令牌;limiter.acquire()表示消费一个令牌,如果当前桶中有足够令牌则成功(返回值为0),如果桶中没有令牌则暂停一段时间,比如发令牌间隔是200毫秒,则等待200毫秒后再去消费令牌,这种实现将突发请求速率平均为了固定请求速率。
  • SmoothWarmingUp模式:RateLimiter limiter = RateLimiter.create(5,1000, TimeUnit.MILLISECONDS);
    创建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit),permitsPerSecond表示每秒新增的令牌数,warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔。速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节warmupPeriod参数实现一开始就是平滑固定速率。

注:RateLimiter控制的是速率,Samephore控制的是并发量。RateLimiter的原理就是令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑地分发,若请求超过permitsPerSecond则RateLimiter按照每秒 1/permitsPerSecond 的速率释放许可。注意:RateLimiter适用于单体应用,且RateLimiter不保证公平性访问。

使用上述方式使用RateLimiter的方式不够优雅,自定义注解+AOP的方式实现(适用于单体应用),详细见下面代码:

自定义注解:

import java.lang.annotation.*;/*** 自定义注解可以不包含属性,成为一个标识注解*/
@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitAspect {}

自定义切面类

import com.google.common.util.concurrent.RateLimiter;
import com.test.cn.springbootdemo.util.ResultUtil;
import net.sf.json.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
@Scope
@Aspect
public class RateLimitAop {@Autowiredprivate HttpServletResponse response;private RateLimiter rateLimiter = RateLimiter.create(5.0); //比如说,我这里设置"并发数"为5@Pointcut("@annotation(com.test.cn.springbootdemo.aspect.RateLimitAspect)")public void serviceLimit() {}@Around("serviceLimit()")public Object around(ProceedingJoinPoint joinPoint) {Boolean flag = rateLimiter.tryAcquire();Object obj = null;try {if (flag) {obj = joinPoint.proceed();//这个方法用于执行原来的方法或继续原来的控制流程。}else{String result = JSONObject.fromObject(ResultUtil.success1(100, "failure")).toString();output(response, result);}} catch (Throwable e) {e.printStackTrace();}System.out.println("flag=" + flag + ",obj=" + obj);return obj;}public void output(HttpServletResponse response, String msg) throws IOException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = null;try {outputStream = response.getOutputStream();    //这行代码获取了与当前HTTP响应关联的输出流,并将其赋值给outputStream变量。outputStream.write(msg.getBytes("UTF-8"));//这部分将转换后的字节数组写入到之前获取的输出流中,这意味着数据将被发送到客户端。} catch (IOException e) {e.printStackTrace();} finally {outputStream.flush();outputStream.close();}}
}

测试controller

import com.test.cn.springbootdemo.aspect.RateLimitAspect;
import com.test.cn.springbootdemo.util.ResultUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
public class TestController {@ResponseBody@RateLimitAspect@RequestMapping("/test")public String test(){return ResultUtil.success1(1001, "success").toString();}

2.2.2 漏桶算法

漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
在这里插入图片描述
对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。
算法实现:

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;// 漏桶 限流
@Slf4j
public class LeakBucketLimiter {// 计算的起始时间private static long lastOutTime = System.currentTimeMillis();// 流出速率 每秒 2 次private static int leakRate = 2;// 桶的容量private static int capacity = 2;//剩余的水量private static AtomicInteger water = new AtomicInteger(0);//返回值说明:// false 没有被限制到// true 被限流public static synchronized boolean isLimit(long taskId, int turn) {// 如果是空桶,就当前时间作为漏出的时间if (water.get() == 0) {lastOutTime = System.currentTimeMillis();water.addAndGet(1);return false;}// 执行漏水int waterLeaked = ((int) ((System.currentTimeMillis() - lastOutTime) / 1000)) * leakRate;// 计算剩余水量int waterLeft = water.get() - waterLeaked;water.set(Math.max(0, waterLeft));// 重新更新leakTimeStamplastOutTime = System.currentTimeMillis();// 尝试加水,并且水还未满 ,放行if ((water.get()) < capacity) {water.addAndGet(1);return false;} else {// 水满,拒绝加水, 限流return true;}}//线程池,用于多线程模拟测试private ExecutorService pool = Executors.newFixedThreadPool(10);@Testpublic void testLimit() {// 被限制的次数AtomicInteger limited = new AtomicInteger(0);// 线程数final int threads = 2;// 每条线程的执行轮数final int turns = 20;// 线程同步器CountDownLatch countDownLatch = new CountDownLatch(threads);long start = System.currentTimeMillis();for (int i = 0; i < threads; i++) {pool.submit(() -> {try {for (int j = 0; j < turns; j++) {long taskId = Thread.currentThread().getId();boolean intercepted = isLimit(taskId, j);if (intercepted) {// 被限制的次数累积limited.getAndIncrement();}Thread.sleep(200);}} catch (Exception e) {e.printStackTrace();}//等待所有线程结束countDownLatch.countDown();});}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}float time = (System.currentTimeMillis() - start) / 1000F;//输出统计结果log.info("限制的次数为:" + limited.get() + ",通过的次数为:" + (threads * turns - limited.get()));log.info("限制的比例为:" + (float) limited.get() / (float) (threads * turns));log.info("运行的时长为:" + time);}
}

3.分布式限流

分布式限流
自定义注解+拦截器+Redis实现限流 (单体和分布式均适用,全局限流)
自定义注解:

@Inherited
@Documented
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {int limit() default 5;  int sec() default 5;
}

拦截器:

public class AccessLimitInterceptor implements HandlerInterceptor {@Autowiredprivate RedisTemplate<String, Integer> redisTemplate;  //使用RedisTemplate操作redis@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();if (!method.isAnnotationPresent(AccessLimit.class)) {return true;}AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);if (accessLimit == null) {return true;}int limit = accessLimit.limit();int sec = accessLimit.sec();String key = IPUtil.getIpAddr(request) + request.getRequestURI();Integer maxLimit = redisTemplate.opsForValue().get(key);if (maxLimit == null) {redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);  //set时一定要加过期时间} else if (maxLimit < limit) {redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);} else {output(response, "请求太频繁!");return false;}}return true;}public void output(HttpServletResponse response, String msg) throws IOException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = null;try {outputStream = response.getOutputStream();outputStream.write(msg.getBytes("UTF-8"));} catch (IOException e) {e.printStackTrace();} finally {outputStream.flush();outputStream.close();}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

controller:

@Controller
@RequestMapping("/activity")
public class AopController {@ResponseBody@RequestMapping("/seckill")@AccessLimit(limit = 4,sec = 10)  //加上自定义注解即可public String test (HttpServletRequest request,@RequestParam(value = "username",required = false) String userName){//TODO somethings……return   "hello world !";}
}

配置文件:

/*springmvc的配置文件中加入自定义拦截器*/
<mvc:interceptors><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.pptv.activityapi.controller.pointsmall.AccessLimitInterceptor"/></mvc:interceptor>
</mvc:interceptors>

4.交流群

后端专属技术群
我建了一个后端专属技术群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!
文明发言,以交流技术、职位内推、行业探讨为主

图片

资源共享,共同进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/577148.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

漏洞复现-红帆OA iorepsavexml.aspx文件上传漏洞(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

浅谈师范双非普本工科专业的秋招历程

本人普通师范院校通信工程专业&#xff0c;于秋招历程之中四处碰壁&#xff0c;迫于家庭等各种因素考虑&#xff0c;最终选择移动的偏远县城岗位的OFFER&#xff01;本人秋招历程之中&#xff0c;屡屡碰壁&#xff0c;也算得上“收获满满”&#xff01;我简单给各位浅谈一下我的…

气动冷凝水回收泵机械浮球泵的特点工作原理介绍 不需要电源

​ 1&#xff1a;气动凝水回收泵机械式介绍 气动冷凝水回收泵是一种设计用于不使用电力来泵送冷凝液、油和其他高温液体等的设备。它无需维护&#xff0c;能将大量凝结水和其它液体从低位、低压或真空场所泵送到高处及高压区域。与传统电泵相比&#xff0c;气动冷凝水回收泵可…

放大电路的静态分析和动态分析例题

(一) &#xff08;二&#xff09; (三) &#xff08;四&#xff09; &#xff08;五&#xff09;

Spring Boot学习随笔- 第一个Thymeleaf应用(基础语法th:,request、session作用域取值)

学习视频&#xff1a;【编程不良人】2021年SpringBoot最新最全教程 第十五章、Thymeleaf Thymeleaf是一种现代化的服务器端Java模板引擎&#xff0c;专门用于Web和独立环境。Thymeleaf在有网络和无网络的环境下皆可运行&#xff0c;即可以让美工在浏览器查看页面的静态效果&am…

智能优化算法应用:基于向量加权平均算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于向量加权平均算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于向量加权平均算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.向量加权平均算法4.实验参数设定…

《C++避坑神器·二十五》简单搞懂json文件的读写之遍历json文件读写

json.hpp库放在文章末尾 1、遍历json文件读写 &#xff08;1&#xff09;插入新键值对到json之情形1 原来json文件如下所示&#xff1a; {"Connection": {"IpAddress": "192.168.20.1","Rock": 0,"Solt": 1}, "Data…

opencv入门到精通——形态学转换

目录 目标 理论 1. 侵蚀 2. 扩张 3. 开运算 4. 闭运算 5. 形态学梯度 6. 顶帽 7. 黑帽 结构元素 目标 在这一章当中&#xff0c; 我们将学习不同的形态学操作&#xff0c;例如侵蚀&#xff0c;膨胀&#xff0c;开运算&#xff0c;闭运算等。我们将看到不同的功能&…

前端---html 的介绍

1. 网页效果图 --CSDN 2. html的定义 HTML 的全称为&#xff1a;HyperText Mark-up Language, 指的是超文本标记语言。 标记&#xff1a;就是标签, <标签名称> </标签名称>, 比如: <html></html>、<h1></h1> 等&#xff0c;标签大多数都是…

【数据结构】查找与排序

要查询信息&#xff0c;涉及两个问题&#xff1a; 在哪里查&#xff1f;——查找表 怎么查&#xff1f;——查找方法 一.查找 1.查找表的定义&#xff1a; 查找表是由同类型的数据元素构成的集合 2.对查找表的基本操作&#xff1a; 1&#xff09;查询某个数据元素是否在查…

MY FILE SERVER: 1

下载地址 https://download.vulnhub.com/myfileserver/My_file_server_1.ova 首先我们需要发现ip 我的kali是59.162所以167就是靶机的 然后我们拿nmap扫一下端口 nmap -sV -p- 192.168.59.167 扫完发现有七个端口开放 按照习惯先看80 没看到有啥有用信息,用nikto扫一下 nik…

20231225使用荣耀HONOR70的手机通过BLE调试助手接收BLE广播

20231225使用荣耀HONOR70的手机通过BLE调试助手接收BLE广播 2023/12/25 16:33 结论&#xff1a;我买的 荣耀HONOR70的手机&#xff08;CPU型号&#xff1a;骁龙778G&#xff09; 的蓝牙BLE的连接速度明显弱于 Redmi Note12Pro 5G&#xff08;CPU型号&#xff1a;天玑1080&#…

【网安 | 网络协议】ARP协议(地址解析协议)

前言 在使用nmap时可以使用-PR指令&#xff0c;通过发送ARP请求包进行主机存活探测。 那么这一过程的原理是什么呢&#xff1f; 在了解什么是ARP协议后&#xff0c;问题就迎刃而解了。 概念 地址解析协议&#xff0c;即ARP&#xff08;Address Resolution Protocol&#xf…

基于Hexo+GitHub Pages 的个人博客搭建

基于HexoGitHub Pages 的个人博客搭建 步骤一&#xff1a;安装 Node.js 和 Git步骤二&#xff1a;创建Github Pages 仓库步骤二&#xff1a;安装 Hexo步骤三&#xff1a;创建 Hexo 项目步骤四&#xff1a;配置 Hexo步骤五&#xff1a;创建新文章步骤六&#xff1a;生成静态文件…

git命令和docker命令

1、git git是分布式的版本控制工具 git可以通过本地仓库管理文件的历史版本记录 # 本地仓库操作的命令 # 初始化本地库 git init # 添加文件到暂存区 git add . git checkout 暂存区要撤销的文件名称 # 提交暂存区文件 git commit -m 注释# 版本穿梭 # 查看提交记录 git log…

dpdk原理概述及核心源码剖析

dpdk原理 1、操作系统、计算机网络诞生已经几十年了&#xff0c;部分功能不再能满足现在的业务需求。如果对操作系统做更改&#xff0c;成本非常高&#xff0c;所以部分问题是在应用层想办法解决的&#xff0c;比如前面介绍的协程、quic等&#xff0c;都是在应用层重新开发的框…

MongoDB数据库本地部署并结合内网穿透实现navicat公网访问

文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 前言 MongoDB是一个基于分布式文件存储的数…

Jave EE 网络原理之网络层与数据链路层

文章目录 1. 网络层1.1 IP 协议1.1.1 协议头格式1.1.2 地址管理1.1.2.1 认识 IP 地址 1.1.3 路由选择 2. 数据链路层2.1 认识以太网2.1.1 以太网帧格式2.1.2 DNS 应用层协议 1. 网络层 网络层要做的事情&#xff0c;主要是两个方面 地址管理 &#xff08;制定一系列的规则&am…

解决ELement-UI三级联动数据不回显

目录 一.处理数据时使用this.$set方法来动态地设置实例中的属性&#xff0c;以确保其响应式。 二.检查数据格式是否正确 三.绑定v-if 确保每次执行 四.完整代码 一.处理数据时使用this.$set方法来动态地设置实例中的属性&#xff0c;以确保其响应式。 二.检查数据格式是否正确…

BFS解决FloodFill算法相关leetcode算法题

文章目录 1.图像渲染2.岛屿数量3.岛屿的最大面积4.被围绕的区域 1.图像渲染 图像渲染 class Solution {int dx[4] {0,0,1,-1};int dy[4] {1,-1,0,0}; public:vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int…