当前位置:

首页 > 编程开发 > C++之高性能跨平台日志库spdlog

C++之高性能跨平台日志库spdlog

spdlog是C++高性能日志库,异步模式可达每秒百万条消息。采用无锁队列、环形队列与零拷贝设计,支持同步/异步双模式。核心模块包括Logger、Sink、Formatter、Registry、线程池及MPMC队列,通过预分配与模板策略实现极致性能。

spdlog 是目前 C++ 社区里相当热门的一个日志框架,核心卖点就是快——快到什么程度?异步模式下每秒百万条消息的吞吐量,比 glog、log4cpp 快了不止一个量级。 它的几个关键特性值得一说:异步模式采用无锁队列配合线程池,主线程把日志消息丢进队列就返回,几乎不产生阻塞;预分配的环形队列和零拷贝设计,在高并发场景下内存分配压力极低;支持同步/异步双模式切换,同步模式直接写 I/O,简单直接,异步模式后台线程统一处理,业务逻辑完全不卡。

核心流程

核心模块

要理解 spdlog 的设计,先看它的几个核心模块各自扮演什么角色:
模块核心职责/作用输入/输出
Logger日志级别过滤、消息分发、错误处理,统一入口,支持多 sink 组合格式化字符串 + 参数,输出 log_msg 对象
Sink实际 I/O 操作、格式化输出,可扩展输出目标,单一职责输入 log_msg 对象,输出字节流到目标
Formatter消息格式化、时间戳处理、颜色标记,支持自定义格式,缓存优化输入 log_msg 对象,输出格式化字符串
Registry全局 logger 管理、配置分发、生命周期,集中管理,避免全局变量污染输入 logger 注册请求,输出 logger 引用
Thread Pool异步消息处理、后台线程调度,解耦业务线程与 I/O 线程输入 async_msg 对象,调用 logger sink_it_
MPMC Queue线程安全消息队列、阻塞/非阻塞策略,生产者-消费者解耦,零分配输入 log_msg 对象,输出出队消息
从应用角度来看,不同场景下各模块的适用性也有所不同:
能力适用场景不适用场景
同步日志单线程应用、调试阶段、低频日志高并发生产环境、性能敏感路径
异步日志高并发服务、游戏引擎、实时系统需要严格顺序保证、崩溃时日志不能丢失
环形缓冲固定内存预算、可容忍消息丢失需要持久化所有日志、审计场景
多 Sink 组合同时输出到文件/控制台/网络单一输出目标、极简场景
自定义格式结构化日志、日志分析系统标准格式即可满足需求

核心执行时序

同步日志执行流程

异步日志执行流程

原理与设计

spdlog 能在保持接口简洁的同时实现极致性能,靠的是预分配与零拷贝设计这套组合拳: 1. 环形队列:牺牲部分灵活性(固定大小),换来零分配与缓存友好 2. 模板策略:在编译期决定线程安全策略,运行时零开销 3. 异步解耦:业务线程只负责入队,I/O 延迟被隔离在后台,不干扰核心路径

关键抽象与机制

环形队列(Circular Queue)

环形队列是 spdlog 性能优化的基石,实现位于 `circular_q.h`: ```cpp template class circular_q { size_t max_items_ = 0; typename std::vector::size_type head_ = 0; typename std::vector::size_type tail_ = 0; size_t overrun_counter_ = 0; std::vector v_; void push_back(T &&item) { if (max_items_ > 0) { v_[tail_] = std::move(item); tail_ = (tail_ + 1) % max_items_; if (tail_ == head_) { // 队列满,覆盖最旧消息 head_ = (head_ + 1) % max_items_; ++overrun_counter_; } } } }; ``` 几个关键设计点值得注意: - 预分配:构造时一次性分配 `max_items + 1` 个元素,运行时不做任何内存分配 - 覆盖策略:队列满了怎么办?直接覆盖最旧的消息,保证写入操作永不阻塞 - 零拷贝:`std::move` 转移所有权,避免深拷贝带来的性能损耗 - 计数器:`overrun_counter_` 记录被覆盖的消息数量,方便监控

MPMC 阻塞队列

多生产者-多消费者队列位于 `mpmc_blocking_q.h`,在环形队列基础上封装了线程安全机制: ```cpp template class mpmc_blocking_queue { std::mutex queue_mutex_; std::condition_variable push_cv_; std::condition_variable pop_cv_; spdlog::details::circular_q q_; std::atomic discard_counter_{0}; void enqueue(T &&item) { { std::unique_lock lock(queue_mutex_); pop_cv_.wait(lock, [this] { return !this->q_.full(); }); q_.push_back(std::move(item)); } push_cv_.notify_one(); } bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) { { std::unique_lock lock(queue_mutex_); if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) { return false; } popped_item = std::move(q_.front()); q_.pop_front(); } pop_cv_.notify_one(); return true; } }; ``` 并发控制方面有几个亮点: - 双条件变量:`push_cv_` 负责唤醒消费者线程,`pop_cv_` 唤醒生产者线程,避免盲目轮询 - 细粒度锁:锁的持有时间只覆盖队列操作,不包含 I/O 等耗时步骤 - 超时机制:`dequeue_for` 支持超时返回,防止死锁 - 丢弃计数:`discard_counter_` 记录 `enqueue_if_ha ve_room` 失败的次数

Sink 抽象与线程安全

Sink 接口定义在 `sink.h`: ```cpp class sink { public: virtual ~sink() = default; virtual void log(const details::log_msg &msg) = 0; virtual void flush() = 0; virtual void set_pattern(const std::string &pattern) = 0; virtual void set_formatter(std::unique_ptr sink_formatter) = 0; void set_level(level::level_enum log_level); level::level_enum level() const; bool should_log(level::level_enum msg_level) const; protected: level_t level_{level::trace}; }; ``` 线程安全是通过模板策略实现的,位于 `base_sink.h`: ```cpp template class base_sink : public sink { public: void log(const details::log_msg &msg) final override { std::lock_guard lock(mutex_); sink_it_(msg); } void flush() final override { std::lock_guard lock(mutex_); flush_(); } protected: std::unique_ptr formatter_; Mutex mutex_; virtual void sink_it_(const details::log_msg &msg) = 0; virtual void flush_() = 0; }; ``` 这个设计有几个明显优势: - 编译期多态:通过模板参数选择 `std::mutex` 或者 `details::null_mutex` - 零运行时开销:单线程场景下使用 `null_mutex`,锁操作被编译器完全优化掉 - CRTP 模式:基类控制整体流程,派生类只需要实现具体的 I/O 逻辑

格式化器与时间戳缓存

Pattern Formatter 位于 `pattern_formatter.h`: ```cpp class pattern_formatter final : public formatter { private: std::string pattern_; std::string eol_; pattern_time_type pattern_time_type_; bool need_localtime_; std::tm cached_tm_; std::chrono::seconds last_log_secs_; std::vector> formatters_; std::tm get_time_(const details::log_msg &msg) { if (need_localtime_) { auto time_now = log_clock::to_time_t(msg.time); if (last_log_secs_ != time_now) { cached_tm_ = localtime(time_now); last_log_secs_ = time_now; } return cached_tm_; } return gmtime(log_clock::to_time_t(msg.time)); } }; ``` 性能优化的几个关键点: - 时间戳缓存:同一秒内的日志共享同一个 `std::tm` 结构,避免重复转换 - 编译期解析:模式字符串在构造时就解析为 `flag_formatter` 链表,运行时无需解析 - 内存复用:`memory_buf_t` 使用 fmt 的 `memory_buffer`,避免反复分配内存

核心设计取舍

性能 vs 易用性

设计决策性能收益易用性代价适用场景
Header-only 模式无编译优化,编译时间长无需构建,集成简单快速原型、小型项目
编译模式编译时间短,二进制体积小需要 CMake 配置生产环境、大型项目
异步模式业务线程零阻塞崩溃时可能丢失日志高并发服务
同步模式日志顺序严格保证I/O 阻塞业务线程调试、审计场景
一个值得注意的权衡是:异步模式下,日志消息在入队时已经完成了格式化,但 `log_msg_buffer` 仍然需要拷贝 payload。这么做的目的是确保业务线程释放原始字符串后,后台线程仍然能安全访问日志内容。

一致性 vs 可用性

```cpp enum class async_overflow_policy { block, // 阻塞直到有空间(强一致性) overrun_oldest, // 覆盖最旧消息(高可用性) discard_new // 丢弃新消息(低延迟优先) }; ``` 不同场景下该选哪种策略? - block:审计日志的首选,不能容忍任何丢失,代价是可能阻塞业务线程 - overrun_oldest:监控日志的好选择,能容忍旧数据丢失,保证实时性 - discard_new:调试日志的理想方案,队列满了直接丢弃新消息,避免阻塞

抽象程度 vs 灵活性

Sink 的继承层级相当清晰: ``` sink (接口) └─ base_sink (模板基类,提供线程安全) ├─ basic_file_sink (单文件) ├─ rotating_file_sink (滚动文件) ├─ daily_file_sink (按日期分割) └─ stdout_sink (控制台) ``` 扩展机制也很灵活: - 自定义 Sink:继承 `base_sink`,实现 `sink_it_()` 和 `flush_()` 即可 - 自定义 Formatter:继承 `custom_flag_formatter` 并注册到 `pattern_formatter` - 自定义错误处理:通过 `set_error_handler()` 注册回调函数

源码地图

``` spdlog-1.15.2/ ├── include/spdlog/ │ ├── spdlog.h # 全局 API 入口,registry 访问 │ ├── logger.h # 核心 logger 类,同步日志实现 │ ├── async_logger.h # 异步 logger,继承 logger │ ├── formatter.h # 格式化器接口 │ ├── pattern_formatter.h # 默认格式化器实现 │ ├── async.h # 异步工厂函数 │ ├── common.h # 公共类型定义,编译配置 │ │ │ ├── details/ │ │ ├── registry.h # 全局 logger 注册表 │ │ ├── thread_pool.h # 异步线程池 │ │ ├── mpmc_blocking_q.h # 多生产者多消费者队列 │ │ ├── circular_q.h # 环形队列底层实现 │ │ ├── log_msg.h # 日志消息结构 │ │ ├── log_msg_buffer.h # 带缓冲的消息,用于异步 │ │ └── file_helper.h # 文件操作辅助类 │ │ │ └── sinks/ │ ├── sink.h # Sink 接口定义 │ ├── base_sink.h # 线程安全模板基类 │ ├── basic_file_sink.h # 基础文件 Sink │ ├── rotating_file_sink.h # 滚动文件 Sink │ ├── daily_file_sink.h # 日期分割 Sink │ ├── stdout_sinks.h # 控制台 Sink │ └── dist_sink.h # 分发 Sink(多目标) │ └── src/ ├── spdlog.cpp # 编译模式实现 ├── async.cpp # 异步相关实现 └── bundled_fmtlib_format.cpp # fmt 库编译实现 ``` 几个核心文件需要重点关注: 1. logger.h:定义了 `logger` 类,涵盖日志级别过滤、错误处理、sink 管理 2. async_logger.h:重写了 `sink_it_()` 和 `flush_()`,负责把消息推送到线程池 3. mpmc_blocking_q.h:核心并发数据结构,直接决定异步性能的上限 4. pattern_formatter.h:格式化逻辑,时间戳缓存优化是性能关键 5. registry.h:全局状态管理,logger 的整个生命周期都靠它掌控

API 使用

常用 API

全局 API

通过 `spdlog::` 命名空间可以直接访问:
API参数说明功能说明
spdlog::info(fmt, args...)fmt: 格式化字符串,args: 参数使用默认 logger 输出 INFO 级别日志
spdlog::set_level(level)level: level::tracelevel::off设置全局日志级别
spdlog::set_pattern(pattern)pattern: 格式模式字符串设置全局格式模式
spdlog::get(name)name: logger 名称获取已注册的 logger,不存在返回 nullptr
spdlog::drop(name)name: logger 名称从 registry 移除 logger
spdlog::shutdown()停止所有线程,清理资源
spdlog::flush_on(level)level: 触发 flush 的级别设置自动 flush 级别
spdlog::flush_every(interval)interval: 时间间隔启动周期性 flush 线程

Logger API

API参数说明功能说明
logger->log(level, fmt, args...)level: 日志级别输出指定级别日志
logger->set_level(level)level: 日志级别设置该 logger 的日志级别
logger->flush()手动 flush 所有 sink
logger->sinks()返回 sink 列表引用,可动态添加 sink
logger->set_formatter(formatter)formatter: 格式化器指针设置自定义格式化器
logger->error_handler()获取当前错误处理器
logger->set_error_handler(handler)handler: 错误处理函数设置错误处理回调

Sink API

API参数说明功能说明
sink->set_level(level)level: 日志级别设置 sink 级别过滤
sink->set_pattern(pattern)pattern: 格式模式设置 sink 格式
sink->flush()flush 该 sink

异步 API

API参数说明功能说明
spdlog::create_async(name, args...)Sink: sink 类型,name: logger 名称创建异步 logger
spdlog::init_thread_pool(q_size, n_threads)q_size: 队列大小,n_threads: 线程数初始化全局线程池
async_logger(logger_name, sink, tp, policy)tp: 线程池,policy: 溢出策略构造异步 logger

样例 Demo

下面是一个完整的示例,涵盖了错误处理、多 sink、异步模式和资源清理: ```cpp #include #include #include #include #include #include #include class GameLogger { public: static bool Initialize() { try { // 初始化线程池,队列大小8192,工作线程数1 spdlog::init_thread_pool(8192, 1); // 创建控制台 sink(彩色输出) auto console_sink = std::make_shared(); console_sink->set_level(spdlog::level::debug); console_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%t] %v"); // 创建滚动文件 sink(单个文件5MB,保留3个备份) auto file_sink = std::make_shared( "logs/game.log", 1024 * 1024 * 5, 3); file_sink->set_level(spdlog::level::info); file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] [%s:%#] %v"); // 组合多个 sink std::vector sinks{console_sink, file_sink}; // 创建异步 logger auto logger = std::make_shared( "game_logger", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block ); logger->set_level(spdlog::level::debug); logger->flush_on(spdlog::level::warn); // 设置错误处理器 logger->set_error_handler([](const std::string &msg) { std::cerr << "Logger error: " << msg << std::endl; }); // 注册为默认 logger spdlog::register_logger(logger); spdlog::set_default_logger(logger); // 启用回溯,存储最近32条日志 spdlog::enable_backtrace(32); SPDLOG_INFO("GameLogger initialized successfully"); return true; } catch (const spdlog::spdlog_ex &ex) { std::cerr << "Logger initialization failed: " << ex.what() << std::endl; return false; } } static void Shutdown() { SPDLOG_INFO("Shutting down GameLogger"); // 如果有错误,dumo 回溯信息 spdlog::dump_backtrace(); // flush 所有 logger spdlog::apply_all([](std::shared_ptr l) { l->flush(); }); // 释放所有 logger 并停止线程 spdlog::shutdown(); } static void LogGameEvent(const std::string &event_name, int player_id, float value) { SPDLOG_INFO("GameEvent: {} [player={}, value={:.2f}]", event_name, player_id, value); } static void LogPerformanceMetric(const std::string &metric, double ms) { if (ms > 16.67) { // 超过60FPS的帧时间阈值 SPDLOG_WARN("Performance warning: {} took {:.2f}ms", metric, ms); } else { SPDLOG_DEBUG("Performance: {} = {:.2f}ms", metric, ms); } } }; int main() { if (!GameLogger::Initialize()) { return 1; } try { // 基础日志 SPDLOG_INFO("Game started"); SPDLOG_DEBUG("Debug message (only visible in debug builds)"); // 格式化日志 GameLogger::LogGameEvent("PlayerJump", 12345, 98.5f); GameLogger::LogPerformanceMetric("RenderFrame", 14.2); GameLogger::LogPerformanceMetric("PhysicsUpdate", 18.5); // 错误处理 SPDLOG_ERROR("Simulated error with code {}", 404); // 手动 flush spdlog::default_logger()->flush(); } catch (const std::exception &ex) { SPDLOG_CRITICAL("Exception: {}", ex.what()); GameLogger::Shutdown(); return 1; } GameLogger::Shutdown(); return 0; } ``` 这个示例体现了几条工程实践: 1. 初始化顺序:先创建线程池,再创建 logger,最后注册 2. 错误处理:捕获 `spdlog::spdlog_ex` 异常,设置错误处理器 3. 资源清理:`shutdown()` 必须调用,否则异步线程不会主动停止 4. Flush 策略:`flush_on(warn)` 确保 warning 及以上级别立即写入 5. Backtrace:存储最近 N 条日志,崩溃时 dump 出来用于调试

场景建议

配置管理

```cpp // config/logger_config.h struct LoggerConfig { std::string log_file_path = "logs/app.log"; size_t max_file_size = 5 * 1024 * 1024; // 5MB int max_files = 3; spdlog::level::level_enum level = spdlog::level::info; bool async_mode = true; size_t queue_size = 8192; std::string pattern = "[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v"; static LoggerConfig LoadFromFile(const std::string &config_path); }; ```

日志追踪

```cpp // 使用 MDC(Mapped Diagnostic Context)进行请求追踪 #include void HandleHttpRequest(const HttpRequest &req) { // 为当前作用域内的所有日志设置 trace ID spdlog::mdc::put("trace_id", req.trace_id()); spdlog::mdc::put("user_id", std::to_string(req.user_id())); SPDLOG_INFO("Processing request"); // ... 业务逻辑 ... spdlog::mdc::remove("trace_id"); spdlog::mdc::remove("user_id"); } // 配合 MDC 的格式化模式 // Pattern: [%Y-%m-%d %H:%M:%S.%e] [%l] [trace:%X{trace_id}] %v ```

性能调优

```cpp // 监控异步队列健康状态 void MonitorLoggerQueue() { auto tp = spdlog::thread_pool(); if (tp) { size_t overrun = tp->overrun_counter(); size_t discard = tp->discard_counter(); size_t queue_size = tp->queue_size(); if (overrun > 0 || discard > 0) { SPDLOG_WARN("Logger queue issues: overrun={}, discard={}, size={}", overrun, discard, queue_size); } // 监控完后重置计数器 tp->reset_overrun_counter(); tp->reset_discard_counter(); } } // 编译期日志级别控制,release 版本屏蔽 debug 日志 #ifndef NDEBUG #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG #else #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO #endif // 使用宏代替函数调用,编译期自动过滤 SPDLOG_INFO("This call may be optimized out in release"); ```

监控集成

```cpp // 自定义 sink 收集指标 class MetricsSink : public spdlog::sinks::base_sink { protected: void sink_it_(const spdlog::details::log_msg &msg) override { // 增加指标计数器 metrics::LogCounter(msg.level).Increment(); // 追踪错误率 if (msg.level >= spdlog::level::err) { metrics::ErrorRate.Mark(); } } void flush_() override { // flush 指标到后端 metrics::Flush(); } }; // 注册指标 sink auto metrics_sink = std::make_shared(); spdlog::default_logger()->sinks().push_back(metrics_sink);
本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
好衣库售后怎么样?把保障体系拆开看
好衣库售后怎么样?把保障体系拆开看

好衣库售后全程由平台统一受理,绝不甩给店主,店主零售后负担。物流合作顺丰、京东、菜鸟三大优质快递,退货运费险由人保、众安保承保。消费者全面享直购标准,退换货由平台全额兜底,买卖双方均安心省心无忧。

全电作业新标杆!2026款长安猎手K50上市,14.19万起重塑皮卡体验
全电作业新标杆!2026款长安猎手K50上市,14.19万起重塑皮卡体验

2026款长安猎手K50增程皮卡上市,售价14.19万至17.89万元。搭载2.0T超级大增程系统,双电机峰值功率240kW,百公里加速5.9秒。配备宁德时代2C电池,纯电续航192公里,总电力储备239kWh,支持30kW+3.3kW双功率供电,实现全场景用电覆盖。

2026年车市新势力崛起:新车型成销量担当,零跑A10单月破2万领跑
2026年车市新势力崛起:新车型成销量担当,零跑A10单月破2万领跑

2026年5月,国内乘用车出口78.4万辆,同比增长75.1%,占厂商总销量35%。新车型成为增长核心引擎,零跑A10以2.2万辆登顶新车型冠军。消费者需求加速从功能满足转向体验升级,汽车产业进入高速迭代周期,推动行业洗牌。

十七载深耕智能领域,长安“天枢领航”亮相车展引领安全出行新体验
十七载深耕智能领域,长安“天枢领航”亮相车展引领安全出行新体验

长安汽车在重庆车展发布“天枢领航”辅助驾驶系统,从安全防护、减轻压力到智能交互三个维度升级出行体验。该系统基于十七年技术深耕,依托天枢智能实验中心积累超40万虚拟场景,实测500万公里覆盖185类典型场景,形成技术-场景-用户闭环优化机制。

《2025年度北京历史文化名城保护大事记》发布
《2025年度北京历史文化名城保护大事记》发布

2025年度北京历史文化名城保护大事记发布,收录94项代表性事件。关键进展包括《北京中轴线世界文化遗产保护条例》施行、先农坛校址腾退实现空间贯通、长辛店老镇启动区开街、路县故城遗址公园试运行等,标志保护工作从被动抢救转向主动规划。

DBeaver 25.3.3 发布
DBeaver 25.3.3 发布

DBeaver25.3.3发布,优化SQL编辑器响应速度与资源占用,AI辅助自动禁用不兼容模型的temperature参数。数据编辑器默认对SELECT查询自动统计行数,修复数据迁移对话框尺寸异常。重构自动提交逻辑,升级UI布局,修正事务状态显示及macOS欢迎画面。数据库适配增强:ClickHouse修复空格解析,Sybase修复双击表打开,DuckDB新

REDMI 17与Note 17系列通过认证 Note 17系列或7、8月发布
REDMI 17与Note 17系列通过认证 Note 17系列或7、8月发布

红米十七与Note十七系列已经通过多项国际认证。红米十七搭载骁龙六sGen二四G版,含日本专属版本和POCO联名版C九五Pro。Note十七系列由三款机型组成,均基于骁龙平台,预计二零二六年七至八月率先在中国上市。

美股科技板块剧烈震荡:心片股集体承压 ARM跌超6%
美股科技板块剧烈震荡:心片股集体承压 ARM跌超6%

当地时间6月9日,美股三大指数走势分化,纳指与标普500双双收跌。科技板块调整明显,半导体ETF盘中最大跌幅超7%,费城半导体指数一度大跌逾8.6%。ARM跌超6%,Coherent跌逾11%,部分AI相关个股也显著回调。

macOS Golden Gate调整菜单设计 移除菜单项图标
macOS Golden Gate调整菜单设计 移除菜单项图标

macOSGoldenGate更新后移除了菜单项图标,回归简洁纯文字布局。此前Tahoe版本引入图标但辨识度低,反而拖慢浏览效率。此次调整遵循简洁原则,显著提升可读性与操作效率,是对用户反馈的理性精简。这一改动减少视觉干扰,菜单栏更清晰,用户可快速定位功能。

新款奔驰GLC中期改款谍照来袭,设计革新,2027年发布引期待
新款奔驰GLC中期改款谍照来袭,设计革新,2027年发布引期待

新款奔驰GLC中期改款谍照曝光,前脸采用三叉星徽LED大灯,内饰升级MBUX超联屏及MB.OS系统,科技感显著提升。国产版或与科技企业合作优化智能驾驶,动力延续2.0T轻混与插混,兼顾性能与油耗,2027年全球发布。

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

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

Windows
Windows

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

macOS软件
macOS软件

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

Mac软件 更多
灵活计算器
灵活计算器
macOS/iOS/Android

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

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

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

极度公式
极度公式
Windows/macOS/Linux

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

WINDOWS 更多
Windows 10
Windows 10
Windows

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

极度公式
极度公式
Windows/macOS/Linux

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

密码键盘
密码键盘
Windows/macOS/iOS/Android

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