Rust的高效易用日志库—tklog
很多人习惯于python,go等语言基础工具库的简单易用;在使用rust时,可能感觉比较麻烦,类似日志库这样的基础性工具库。tklog提供用法上,非常类似python等Logger的日志库用法,用法简洁;基于rust的高效性和一些优化策略,tklog的性能非常好,在压测中,可以达到 3-4 µs/op (微妙/次),这个效率比go最高的性能时候都高一些;在相同的环境下,对go进行无格式日志输出压测,可以达到 3-4µs/op,如果是格式化日志输出,则为4µs/op以上。(可以参考《 高性能日志库go-logger v2.0.3》中对各日志库的压测数据)。
在异步场景中,tklog提供了对应的方法,支持异步调用。异步方法最大的好处并非在性能上,而是它不会阻塞所在线程。但是由于 tklog的常规日志方法默认使用延迟策略,实际上也不会阻塞所在线程,或者准确说影响非常小,可以忽略不计。所以,异步场景一般也可以直接使用常规日志打印方法。
相关连接
- 项目源码
- 官网
- 仓库
项目引入
[dependencies]
tklog = "0.0.2" #使用时的实际最新版本;当前0.0.2版本
tklog是rust高性能结构化日志库
易用,高效,结构化,控制台日志,文件日志,文件切割,文件压缩,同步打印,异步打印
功能
- 功能支持:控制台日志,文件日志,同步日志,异步日志
- 日志级别设置:提供与标准库同级别日志打印: trace,debug,info,warn,error,fatal
- 格式化输出:支持自定义日志的输出格式,包括日志级别标识、格式化时间、日志文件位置 等元素,并支持自定义格式调整。
- 按时间文件切割:按小时,天,月份切割日志文件
- 按大小文件切割:按指定大小切割日志文件
- 文件数回滚:指定最大备份文件数,支持自动删除旧日志文件,并防止日志文件数过多。
- 文件压缩:支持压缩归档备份日志文件。
使用方法简述
最简单常用的方法:直接调用
use tklog::{trace,debug, error, fatal, info,warn}
fn testlog() {trace!("trace>>>>", "aaaaaaaaa", 1, 2, 3, 4);debug!("debug>>>>", "bbbbbbbbb", 1, 2, 3, 5);info!("info>>>>", "ccccccccc", 1, 2, 3, 5);warn!("warn>>>>", "dddddddddd", 1, 2, 3, 6);error!("error>>>>", "eeeeeeee", 1, 2, 3, 7);fatal!("fatal>>>>", "ffffffff", 1, 2, 3, 8);
}
说明:默认打开控制台日志,没有写日志文件。打印结果:
[TRACE] 2024-05-26 11:47:22 testlog.rs 27:trace>>>>,aaaaaaaaa,1,2,3,4
[DEBUG] 2024-05-26 11:47:22 testlog.rs 28:debug>>>>,bbbbbbbbb,1,2,3,5
[INFO] 2024-05-26 11:47:22 testlog.rs 29:info>>>>,ccccccccc,1,2,3,5
[WARN] 2024-05-26 11:47:22 testlog.rs 30:warn>>>>,dddddddddd,1,2,3,6
[ERROR] 2024-05-26 11:47:22 testlog.rs 31:error>>>>,eeeeeeee,1,2,3,7
[FATAL] 2024-05-26 11:47:22 testlog.rs 32:fatal>>>>,ffffffff,1,2,3,8
说明:直接调用 debug!等宏进行打印,默认调用全局静态LOG对象。LOG对象支持初始化。
use tklog::{sync::Logger,LEVEL, LOG,Format,MODE,
};fn log_init() {LOG.set_console(true) //设置控制台日志.set_level(LEVEL::Info) //日志级别,默认Debug.set_format(Format::LevelFlag | Format::Time | Format::ShortFileName) //结构化日志,定义输出的日志信息.set_cutmode_by_size("tklogsize.txt", 1<<20, 10, true) //日志文件切割模式为文件大小,每1M文件切分一次,保留10个备份日志文件,并压缩备份日志.set_formatter("{level}{time} {file}:{message}\n") //自定义日志输出格式。默认:{level}{time} {file}:{message}
}fn testlog() {log_init() //调用初始化方法trace!("trace>>>>", "aaaaaaaaa", 1, 2, 3, 4); //track日志级别小于设置的LEVEL::Info ,故无输出info!("info>>>>", "ccccccccc", 1, 2, 3, 5);
}
以上是全局单实例打印的示例。tklog支持自定义多实例打印。多实例一般应用在系统要求不同打印结构的场景中。
多实例打印
use tklog::{debugs, errors, fatals, infos,sync::Logger,LEVEL, LOG,traces, warns, Format, MODE,
};
fn testmutlilog() {let mut log = Logger::new();log.set_console(true).set_level(LEVEL::Debug) //定义日志级别为Debug.set_cutmode_by_time("tklogs.log", MODE::DAY, 10, true) //分割日志文件的方式为按天分割,保留最多10个备份,并压缩备份文件.set_formatter("{message} | {time} {file}{level}\n") //自定义日志结构信息的输入顺序与附加内容let mut logger = Arc::clone(&Arc::new(Mutex::new(log)));let log = logger.borrow_mut();traces!(log, "traces>>>>", "AAAAAAAAA", 1, 2, 3, 4);debugs!(log, "debugs>>>>", "BBBBBBBBB", 1, 2, 3, 5);infos!(log, "infos>>>>", "CCCCCCCCC", 1, 2, 3, 5);warns!(log, "warns>>>>", "DDDDDDDDDD", 1, 2, 3, 6);errors!(log, "errors>>>>", "EEEEEEEE", 1, 2, 3, 7);fatals!(log, "fatals>>>>", "FFFFFFFF", 1, 2, 3, 8);thread::sleep(Duration::from_secs(1))
}
执行结果:
debugs>>>>,BBBBBBBBB,1,2,3,5 | 2024-05-26 14:13:25 testlog.rs 70[DEBUG]
infos>>>>,CCCCCCCCC,1,2,3,5 | 2024-05-26 14:13:25 testlog.rs 71[INFO]
warns>>>>,DDDDDDDDDD,1,2,3,6 | 2024-05-26 14:13:25 testlog.rs 72[WARN]
errors>>>>,EEEEEEEE,1,2,3,7 | 2024-05-26 14:13:25 testlog.rs 73[ERROR]
fatals>>>>,FFFFFFFF,1,2,3,8 | 2024-05-26 14:13:25 testlog.rs 74[FATAL]
注意:以上输入结构化信息由 "{message} | {time} {file}{level}\n" formatter决定。formatter中除了关键标识 {message} {time} {file} {level} 外,其他内容原样输出,如 | , 空格,换行 等。
tklog使用详细说明
1. 日志级别 : Trace < Debug < Info < Warn < Error < Fatal
示例:
LOG.set_level(LEVEL::Info) //日志级别,设置为Info
2. 控制台日志
调用 .set_console(bool) 函数
LOG.set_console(false) // false表示不打印控制台日志。默认为true
3. 日志格式
- Format::Nano 无格式
- Format::Date 输出日期 :2024-05-26
- Format::Time 输出时间,精确到秒:14:13:25
- Format::Microseconds 输出时间,精确到微妙:18:09:17.462245
- Format::LongFileName 长文件信息+行号:tests estlog.rs 25
- Format::ShortFileName 短文件信息+行号:testlog.rs 25
- Format::LevelFlag 日志级别信息: [Debug]
LOG.set_format(Format::LevelFlag | Format::Time | Format::ShortFileName)
4.自定义格式输出
默认:"{level}{time} {file}:{message} \n"
- {level} 日志级别信息:如[Debug]
- {time} 日志时间信息
- {file} 文件位置行号信息
- {message} 日志内容
LOG.set_formatter("{message} | {time} {file}{level}\n"); //自定义日志结构信息的输入顺序与附加内容
说明:除了关键标识 {message} {time} {file} {level} 外,其他内容原样输出,如 | , 空格,换行 等。
5.按时间分割日志文件
时间标识:MODE::HOUR,MODE::DAY,MODE::MONTH
分别是:小时,天,月份
调用 .set_cutmode_by_time() 函数,参数:
- 文件路径
- 时间模式
- 最大备份日志文件数
- 是否压缩备份的日志文件
示例
let mut log = Logger::new();
log.set_cutmode_by_time("/usr/local/tklogs.log", MODE::DAY, 0, false)
说明:备份文件路径为: /usr/local/tklogs.log ,时间模式为:按天备份,参数0表示不限制备份文件数,false表示不压缩备份的日志文件
备份的文件格式:
- 按天备份日期文件,如:
- tklogs_20240521.log
- tklogs_20240522.log
- 按小时备份日志文件,如:
- tklogs_2024052110.log
- tklogs_2024052211.log
- 按月份备份日志文件,如:
- tklogs_202403.log
- tklogs_202404.log
6.按大小分割日志文件
调用 .set_cutmode_by_size() 函数,参数:
- 文件路径
- 指定文件滚动大小
- 最大备份日志文件数
- 是否压缩备份的日志文件
示例
let mut log = Logger::new();
log.set_cutmode_by_time("tklogs.log", 100<<20, 10, true)
说明:备份文件路径为:tklogs.log ,按100M大小备份文件,参数10表示只保留最新10个备份文件,true表示压缩备份的日志文件
备份的文件格式:
- tklogs_1.log.gz
- tklogs_2.log.gz
- tklogs_3.log.gz
tklog提供常规日志打印 方法为:
- 全局单例打印
- trace!
- debug!
- info!
- warn!
- error!
- fatal!
- 多实例打印
- traces!
- debugs!
- infos!
- warns!
- errors!
- fatals!
异步日志
- 全局异步单例打印
- async_trace!
- async_debug!
- async_info!
- async_warn!
- async_error!
- async_fatal!
- 多实例异步打印
- async_traces!
- async_debugs!
- async_infos!
- async_warns!
- async_errors!
- async_fatals!
异步方法使用示例
全局单例异步调用
use tklog::{async_debug, async_error, async_fatal, async_info, async_trace, async_warn, LEVEL, Format, ASYNC_LOG};async fn async_log_init() {// 全局单例设置参数ASYNC_LOG.set_console(false) //控制台.set_level(LEVEL::Trace) //日志级别.set_format(Format::LevelFlag | Format::Time | Format::ShortFileName) //结构化日志,定义输出的日志信息.set_cutmode_by_size("tklog_async.txt", 10000, 10, false).await; //日志文件切割模式为文件大小,每10000字节切割一次,保留10个备份日志文件#[tokio::test]
async fn testlog() {async_log_init().await; //参数设置async_trace!("trace>>>>", "aaaaaaa", 1, 2, 3);async_debug!("debug>>>>", "aaaaaaa", 1, 2, 3);async_info!("info>>>>", "bbbbbbbbb", 1, 2, 3);async_warn!("warn>>>>", "cccccccccc", 1, 2, 3);async_error!("error>>>>", "ddddddddddddd", 1, 2, 3);async_fatal("fatal>>>>", "eeeeeeeeeeeeee", 1, 2, 3);tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
}
输出结果:
[TRACE] 20:03:32 testasynclog.rs 20:trace>>>>,aaaaaaa,1,2,3
[DEBUG] 20:03:32 testasynclog.rs 21:debug>>>>,aaaaaaa,1,2,3
[INFO] 20:03:32 testasynclog.rs 22:info>>>>,bbbbbbbbb,1,2,3
[WARN] 20:03:32 testasynclog.rs 23:warn>>>>,cccccccccc,1,2,3
[ERROR] 20:03:32 testasynclog.rs 24:error>>>>,ddddddddddddd,1,2,3
[FATAL] 20:03:32 testasynclog.rs 25:fatal>>>>,eeeeeeeeeeeeee,1,2,3
多实例异步
use std::sync::Arc;use tklog::{async_debugs, async_errors, async_fatals, async_infos, async_traces, async_warns, LEVEL, Format, ASYNC_LOG, MODE
};
#[tokio::test]
async fn testmultilogs() {//新建 Async::Logger 对象let mut log = tklog::Async::Logger::new();log.set_console(false).set_level(LEVEL::Debug).set_cutmode_by_time("tklogasync.log", MODE::DAY, 10, true) .await.set_formatter("{message} | {time} {file}{level}");let mut logger = Arc::clone(&Arc::new(Mutex::new(log)));let log = logger.borrow_mut();async_traces!(log, "async_traces>>>>", "AAAAAAAAAA", 1, 2, 3);async_debugs!(log, "async_debugs>>>>", "BBBBBBBBBB", 1, 2, 3);async_infos!(log, "async_infos>>>>", "CCCCCCCCCC", 1, 2, 3);async_warns!(log, "async_warns>>>>", "DDDDDDDDDD", 1, 2, 3);async_errors!(log, "async_errors>>>>", "EEEEEEEEEEE", 1, 2, 3);async_fatals!(log, "async_fatals>>>>", "FFFFFFFFFFFF", 1, 2, 3);tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
}
输出结果:
async_debugs>>>>,BBBBBBBBBB,1,2,3 | 2024-05-26 20:10:24 testasynclog.rs 45[DEBUG]
async_infos>>>>,CCCCCCCCCC,1,2,3 | 2024-05-26 20:10:24 testasynclog.rs 46[INFO]
async_warns>>>>,DDDDDDDDDD,1,2,3 | 2024-05-26 20:10:24 testasynclog.rs 47[WARN]
async_errors>>>>,EEEEEEEEEEE,1,2,3 | 2024-05-26 20:10:24 testasynclog.rs 48[ERROR]
async_fatals>>>>,FFFFFFFFFFFF,1,2,3 | 2024-05-26 20:10:24 testasynclog.rs 49[FATAL]
基准压力测试
test_debug time: [3.3747 µs 3.4599 µs 3.5367 µs]change: [-69.185% -68.009% -66.664%] (p = 0.00 < 0.05)Performance has improved.
Found 9 outliers among 100 measurements (9.00%)6 (6.00%) high mild3 (3.00%) high severe
说明:时间范围给出了三个数据点,分别代表了测试执行时间的最小值(3.3747微秒)、平均值附近的值(3.4599微秒)、以及最大值(3.5367微秒)
test_debug time: [3.8377 µs 3.8881 µs 3.9408 µs]change: [-66.044% -65.200% -64.363%] (p = 0.00 < 0.05)Performance has improved.
Found 2 outliers among 100 measurements (2.00%)2 (2.00%) high mild
说明:测试运行的时间范围是从3.8377微秒到3.9408微秒,覆盖了一个大概的分布情况,其中3.8881微秒大约是这段时间内的平均或中位数执行时间
结论:日志打印函数性能:3 µs/op — 4 µs/op (微妙/次)
相关文章:
Rust的高效易用日志库—tklog
很多人习惯于python,go等语言基础工具库的简单易用;在使用rust时,可能感觉比较麻烦,类似日志库这样的基础性工具库。tklog提供用法上,非常类似python等Logger的日志库用法,用法简洁;基于rust的高…...
LabVIEW调用外部DLL(动态链接库)
LabVIEW调用外部DLL(动态链接库) LabVIEW调用外部DLL(动态链接库)可以扩展其功能,使用外部库实现复杂计算、硬件控制等任务。通过调用节点(Call Library Function Node)配置DLL路径、函数名称和…...
Python图形界面(GUI)Tkinter笔记(十六):Radiobutton选项功能按钮(单选按钮)
在tkinter库中,选项功能按钮Radiobutton是一个常用的控件,用于从多个选项中选择一个,从而实现相关的交互功能。 其余笔记:【Python图形界面(GUI)Tkinter笔记(总目录)】 【一】书写:tkinter.Radiobutton(父窗口对象,参数1,参数2,...) 【二】Radiobutton控件常用参数…...
静态路由原理与配置
文章目录 路由器的工作原理路由根据路由表转发数据 路由表的形成路由表路由表的形成 静态路由和默认路由静态路由默认路由 路由器转发数据包的封装过程源目地址变化 交换与路由对比路由工作在网络层交换工作在数据链路层 静态路由和默认路由的配置 路由器的工作原理 路由 路由…...
Android 开机动画的启动过程BootAnimation(基于Android10.0.0-r41)
文章目录 Android 开机动画的启动过程BootAnimation(基于Android10.0.0-r41)1.开机动画的启动过程概述2.为什么设置了属性之后就会播放? Android 开机动画的启动过程BootAnimation(基于Android10.0.0-r41) 1.开机动画的启动过程概述 下面就是BootAnimation的重要部…...
Redis 中的 Zset 数据结构详解
目录 用法 1. 增 2. 删 3. 查 4. 交,并 编码方式 应用场景 Redis 中的 Zset(有序集合)是一种将元素按照分数进行排序的数据结构。与上篇写的SetRedis 中的 Set 数据结构详解不同,Zset 中的每个元素都关联一个浮点数类型的…...
Python网页处理与爬虫实战:使用Requests库进行网页数据抓取
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
HOW - vscode 使用指南
目录 一、基本介绍1. 安装 VS Code2. 界面介绍3. 扩展和插件4. 设置和自定义 二、常用界面功能和快捷操作(重点)常用界面功能快捷操作 三、资源和支持 Visual Studio Code(VS Code)是一款由微软开发的免费、开源的代码编辑器&…...
刚刚!《国家科学技术奖励条例》迎来最新修订
【SciencePub学术】《国务院关于修改〈国家科学技术奖励条例〉的决定》已经于2024年5月11日国务院第32次常务会议通过,现予公布: 国务院决定对《国家科学技术奖励条例》作如下修改: 一、将第二条修改为:“国家设立下列国家科学技术…...
MySQL -- SQL笔试题相关
1.银行代缴花费bank_bill 字段名描述serno流水号date交易日期accno账号name姓名amount金额brno缴费网点 serno: 一个 BIGINT UNSIGNED 类型的列,作为主键,且不为空。该列是自动增量的,每次插入新行时,都会自动递增生成一个唯一的…...
VB6 MQTT为什么在物联网应用中使用 MQTT 而不是 HTTP?
有需要VBA,VB6,VB.NET等方面的MQTT的可以找我 一、MQTT简介 MQTT被广泛用于物联网(IoT:Internet of Things)领域,其中大量的设备需要进行实时通信和数据交换。它采用了一种发布/订阅(publish/subscribe)模型,其中消息的发送者(发布者&#…...
软设之希尔排序
假设有n个元素,先取一个小于n的整数d1作为一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组中进行直接插入排序;然后,取第二个增量d2<d1重复上诉的分组和排序,直到所取得增量dt1&#…...
WPF Binding对象
在WinForm中,我们要想对控件赋值,需要在后台代码中拿到控件对象进行操作,这种赋值形式,从根本上是无法实现界面与逻辑分离的。 在WPF中,微软引入了Binding对象,通过Binding,我们可以直接将控件与…...
Educational Codeforces Round 127 D. Insert a Progression
Insert a Progression time limit per test: 2 second memory limit per test: 256 megabytes input: standard input output: standard output You are given a sequence of n n n integers a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,…,an. You are also giv…...
树莓集团:构筑全国数字影像生态链
在数字化浪潮席卷全球的今天,数字影像技术正以前所未有的速度改变着我们的生活。成都树莓集团以远见卓识和坚定步伐,专注于全国数字影像生态链的建设,不断推动着文创产业的创新与发展。 树莓集团致力于打造一个完整的数字影像生态链ÿ…...
物联网——TIM定时器、PWM驱动呼吸灯、舵机和直流电机
定时器概念(常用于输出PWM波形,驱动电机) 时间脉冲数时钟周期; 这里的脉冲数6553665536,支持定时器级联,从而延长定时 定时器类型 基本定时器原理图(UI:更新中断, U:更新事件&#…...
Elasticsearch 认证模拟题 -2
一、题目 有一个索引 task3,其中有 fielda,fieldb,fieldc,fielde 现要求对 task3 重建索引,重建后的索引新增一个字段 fieldg 其值是fielda,fieldb,fieldc,fielde 的值拼接而成。 …...
Java-----Comparable接口和Comparator接口
在Java中,我们会经常使用到自定义类,那我们如何进行自定义类的比较呢? 1.Comparable接口 普通数据的比较 int a10;int b91;System.out.println(a<b); 那自定义类型可不可以这样比较呢?看一下代码 我们发现会报错,因为自定义…...
通信技术体会
比如 pcie可以看成是全连接的ahb bus,但又不是。 因为pcie还是axi(神似split/cutthrough)。(axi更多是接口而不是bus)。 pcie虽然物理层和usb都是serdes,但transaction layer就是上面这样的,也就…...
Linux系统安全及其应用
文章目录 一、用户账号安全管理1.1 系统账号的清理1.2 对用户账号的操作1.2.1 锁定和解锁用户1.2.2 删除无用账号 1.3 对重要文件进行锁定1.4 密码安全控制1.4.1 新建用户1.4.2 已有用户 二、历史命令管理2.1 历史命令限制2.2 自动清空历史命令 三、设置终端登录的安全管理3.1 …...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
