当前位置: 首页 > article >正文

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言

Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(1920×1080×4字节)。据统计,超过60%的应用OOM崩溃与Bitmap的不合理使用直接相关。本文将从Bitmap的内存计算原理出发,结合字节码操作实现自动化监控,深入讲解超大Bitmap加载优化内存复用泄漏防控的核心技术,并通过代码示例演示完整治理流程。

一、Bitmap内存占用的计算与影响

理解Bitmap的内存占用是治理的基础。其内存大小由像素总数像素格式共同决定。

1.1 内存计算公式

内存占用(字节)= 图片宽度 × 图片高度 × 单像素字节数

1.2 像素格式与内存的关系

Android支持多种像素格式,常见格式的单像素字节数如下:

格式描述单像素字节数适用场景
ARGB_888832位(4字节),支持透明度4高质量图片(如详情页)
RGB_56516位(2字节),无透明度2无透明需求的图片(如列表)
ARGB_444416位(2字节),低质量透明度2已废弃(Android 13+不推荐)
ALPHA_88位(1字节),仅透明度1仅需透明度的特殊效果

示例:加载一张2048×2048的ARGB_8888图片,内存占用为:
2048 × 2048 × 4 = 16,777,216字节(约16MB)

1.3 不同Android版本的内存分配差异

  • Android 8.0之前:Bitmap内存存储在Native堆(C/C++层),GC无法直接回收,需手动调用recycle()释放;
  • Android 8.0及之后:Bitmap内存迁移到Java堆,由GC自动管理,但大内存对象仍可能触发频繁GC,导致界面卡顿。

二、字节码操作:自动化监控Bitmap的创建与回收

通过字节码插桩技术,可在编译期监控Bitmap的构造与回收,记录创建位置、内存大小及回收状态,快速定位不合理的Bitmap使用。

2.1 字节码插桩原理

利用ASM(Java字节码操作库)或AGP(Android Gradle Plugin)的Transform API,在Bitmap的构造函数和recycle()方法中插入监控代码。

2.2 关键实现步骤(基于ASM)

(1)监控Bitmap构造函数

Bitmap.createBitmap()等创建方法中插入代码,记录创建时的堆栈信息和内存大小。

ASM插桩示例

// 自定义ClassVisitor,修改Bitmap的构造函数
public class BitmapClassVisitor extends ClassVisitor {public BitmapClassVisitor(ClassVisitor cv) {super(Opcodes.ASM9, cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {// 匹配Bitmap的构造函数(如createBitmap)if (name.equals("createBitmap") && descriptor.contains("IILandroid/graphics/Bitmap$Config;")) {return new BitmapMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions));}return super.visitMethod(access, name, descriptor, signature, exceptions);}private static class BitmapMethodVisitor extends MethodVisitor {public BitmapMethodVisitor(MethodVisitor mv) {super(Opcodes.ASM9, mv);}@Overridepublic void visitInsn(int opcode) {if (opcode == Opcodes.ARETURN) { // 在方法返回前插入监控代码// 调用监控工具类记录Bitmap创建信息mv.visitVarInsn(Opcodes.ALOAD, 0); // Bitmap对象mv.visitMethodInsn(Opcodes.INVOKESTATIC,"com/example/BitmapMonitor","onBitmapCreated","(Landroid/graphics/Bitmap;)V",false);}super.visitInsn(opcode);}}
}
(2)监控Bitmap回收

Bitmap.recycle()方法中插入代码,标记该Bitmap已回收,并统计存活时间。

监控工具类示例

public class BitmapMonitor {private static final Map<Bitmap, BitmapInfo> sBitmapMap = new HashMap<>();public static void onBitmapCreated(Bitmap bitmap) {if (bitmap == null) return;// 记录Bitmap的宽、高、格式、内存大小及创建堆栈BitmapInfo info = new BitmapInfo(bitmap.getWidth(),bitmap.getHeight(),bitmap.getConfig(),getStackTrace() // 获取当前堆栈信息);sBitmapMap.put(bitmap, info);Log.d("BitmapMonitor", "Created: " + info);}public static void onBitmapRecycled(Bitmap bitmap) {if (bitmap == null) return;BitmapInfo info = sBitmapMap.remove(bitmap);if (info != null) {long duration = System.currentTimeMillis() - info.createTime;Log.d("BitmapMonitor", "Recycled: " + info + ", 存活时间: " + duration + "ms");}}private static String getStackTrace() {StackTraceElement[] stack = new Throwable().getStackTrace();StringBuilder sb = new StringBuilder();for (int i = 2; i < Math.min(stack.length, 8); i++) { // 跳过前两层(监控方法自身)sb.append(stack[i].toString()).append("\n");}return sb.toString();}static class BitmapInfo {int width, height;Bitmap.Config config;long createTime;String stackTrace;// 构造函数...}
}

2.3 集成到Gradle构建

通过AGP的Transform API注册自定义字节码处理器,实现自动化插桩:

build.gradle配置

android {buildFeatures {buildConfig true}applicationVariants.all { variant ->variant.transforms.add(new BitmapTransform(variant))}
}

三、超大Bitmap优化:从加载到显示的全链路管控

超大Bitmap(如4K图片、未压缩的相机原图)是OOM的主因。需通过采样率加载压缩动态分辨率等技术降低内存占用。

3.1 采样率加载(inSampleSize)

通过BitmapFactory.OptionsinSampleSize参数,按比例缩小图片分辨率,减少像素总数。

代码示例:计算最优采样率

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {// 第一步:仅获取图片尺寸(不加载内存)BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(res, resId, options);// 计算采样率options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);// 第二步:加载压缩后的图片options.inJustDecodeBounds = false;return BitmapFactory.decodeResource(res, resId, options);
}private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {int height = options.outHeight;int width = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {// 计算宽高的缩放比例int heightRatio = Math.round((float) height / (float) reqHeight);int widthRatio = Math.round((float) width / (float) reqWidth);inSampleSize = Math.min(heightRatio, widthRatio); // 取较小值避免过采样}return inSampleSize;
}// 使用示例:加载100x100的缩略图
Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), R.drawable.large_image, 100, 100);

3.2 压缩优化

  • 质量压缩:通过Bitmap.compress()调整JPEG/WebP的压缩质量(仅影响文件大小,不影响内存占用);
  • 格式压缩:优先使用WebP格式(相同质量下比JPEG小25%-35%);
  • 分辨率压缩:通过createScaledBitmap按比例缩放图片。

示例:WebP压缩

public static byte[] compressToWebP(Bitmap bitmap, int quality) {ByteArrayOutputStream outputStream = new ByteArrayOutputStream();bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSY, quality, outputStream); // 有损压缩return outputStream.toByteArray();
}// 使用:将Bitmap压缩为质量80%的WebP
byte[] webpData = compressToWebP(bitmap, 80);

3.3 动态分辨率加载(根据设备屏幕适配)

根据设备屏幕的DPI和尺寸,动态加载不同分辨率的图片(如hdpi/xhdpi/xxhdpi),避免加载过高分辨率的图片。

资源目录适配

  • 将不同分辨率的图片放在drawable-hdpidrawable-xhdpi等目录;
  • 系统会自动根据设备DPI选择最接近的资源(如xxhdpi设备优先加载drawable-xxhdpi的图片)。

3.4 内存复用(BitmapPool)

通过复用已释放的Bitmap内存,减少内存分配次数,降低GC压力。

示例:基于LruCache的BitmapPool

public class BitmapPool {private final LruCache<String, Bitmap> mCache;public BitmapPool(int maxSize) {mCache = new LruCache<String, Bitmap>(maxSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount(); // 以内存大小为缓存单位}};}public void put(String key, Bitmap bitmap) {if (bitmap != null && !bitmap.isRecycled()) {mCache.put(key, bitmap);}}public Bitmap get(String key, int reqWidth, int reqHeight, Bitmap.Config config) {Bitmap bitmap = mCache.get(key);if (bitmap != null && bitmap.getWidth() == reqWidth && bitmap.getHeight() == reqHeight && bitmap.getConfig() == config) {return bitmap;}return null;}public void clear() {mCache.evictAll();}
}

四、Bitmap泄漏优化:生命周期与引用链的精准管控

Bitmap泄漏通常由长生命周期对象持有短生命周期Bitmap导致(如Activity被静态变量引用,Bitmap未及时回收)。需结合生命周期管理和工具检测,避免泄漏。

4.1 常见泄漏场景与修复

(1)Activity/Fragment被Bitmap持有

泄漏代码

public class ImageManager {private static ImageManager sInstance;private Bitmap mBitmap;public static ImageManager getInstance() {if (sInstance == null) {sInstance = new ImageManager();}return sInstance;}public void setBitmap(Bitmap bitmap) {mBitmap = bitmap; // Bitmap可能持有Activity的Context(如通过ImageView加载)}
}

修复方案
使用WeakReference持有Bitmap,避免长生命周期对象强引用短生命周期资源:

public class ImageManager {private static ImageManager sInstance;private WeakReference<Bitmap> mBitmapRef; // 弱引用public void setBitmap(Bitmap bitmap) {mBitmapRef = new WeakReference<>(bitmap); // 仅弱引用,Bitmap可被GC回收}public Bitmap getBitmap() {return mBitmapRef != null ? mBitmapRef.get() : null;}
}
(2)未及时回收的Bitmap

泄漏代码

public class ImageActivity extends Activity {private Bitmap mBitmap;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image);}// 未在onDestroy中回收Bitmap(Android 8.0前需手动调用)
}

修复方案
在Activity/Fragment的onDestroy()中回收Bitmap(Android 8.0前):

@Override
protected void onDestroy() {super.onDestroy();if (mBitmap != null && !mBitmap.isRecycled()) {mBitmap.recycle(); // 释放Native内存(仅Android 8.0前有效)mBitmap = null;}
}

4.2 工具检测:LeakCanary与Android Profiler

  • LeakCanary:通过弱引用监控Bitmap的生命周期,检测未被回收的实例;
  • Android Profiler:实时监控内存占用,定位大内存Bitmap的创建位置。

LeakCanary自定义监控示例

public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();if (LeakCanary.isInAnalyzerProcess(this)) {return;}// 监控Bitmap泄漏RefWatcher refWatcher = LeakCanary.install(this);Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image);refWatcher.watch(bitmap, "Large Bitmap Leak");}
}

五、Bitmap治理的最佳实践

5.1 开发阶段

  • 统一图片加载框架:使用Glide、Coil等框架自动处理采样率、缓存和内存复用;
  • 禁止直接加载本地大图:通过BitmapRegionDecoder加载长图(如海报、地图)的局部;
  • 启用AndroidX的ImageDecoder(API 28+):替代BitmapFactory,支持更安全的图片解码(自动处理Exif方向、避免OOM)。

5.2 测试阶段

  • 内存压力测试:通过adb shell am kill强制杀死应用,观察Bitmap内存是否完全释放;
  • LeakCanary集成:在Debug包中监控Bitmap泄漏;
  • Android Profiler分析:检查Bitmap的创建频率和内存峰值。

5.3 线上阶段

  • 埋点监控:记录Bitmap的平均内存、加载耗时和泄漏率;
  • 动态降级策略:检测到内存不足时,加载低分辨率图片或显示占位图;
  • 热修复:通过字节码修复工具(如Sophix)快速修复线上泄漏问题。

六、总结

Bitmap治理需从加载优化内存复用泄漏防控三个维度入手,结合字节码插桩实现自动化监控,通过采样率压缩动态适配降低内存占用,利用生命周期管理弱引用避免泄漏。从开发到线上的全链路管控,是保障应用内存健康、提升用户体验的核心策略。

相关文章:

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问&#xff1a;说说对 IP 数据报中 TTL 的理解&#xff1f;我们都知道&#xff0c;IP 数据报由首部和数据两部分组成&#xff0c;首部又分为两部分&#xff1a;固定部分和可变部分&#xff0c;共占 20 字节&#xff0c;而即将讨论的 TTL 就位于首…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...