spdlog日志库源码:全局管理类registry

概述

已经有了用于接收前端用户log消息的类logger,代表log消息的类是log_msg,后端写log消息到目标文件的类sink,格式化log消息为最终字符串的类formatter,解析pattern flag的类pattern_formatter等等。

每次使用时,可能需要先创建logger对象,然后通过logger对象来接收用户log消息。在用户具体使用时,通常通过一个注册中心(registry)类来实现。这个类不仅可以维护全局的默认 logger,还可以管理用户自定义的 logger 对象,并提供异步日志记录功能。

registry类数据成员

registry是一个综合性的类,包含多种功能。其核心功能是提供全局logger对象注册表,主要数据成员,分为这几部分:

  • logger对象注册表loggers_,map存放;
  • 缺省的logger对象default_logger_,便捷接口;
  • 缺省的formatter格式器formatter_,通常为pattern_formatter,方便编译pattern字符串;
  • 一个线程池tp_,用于异步写log;
  • 线程安全,3个不同的锁用于保护数据;
  • 异常处理,缺省的全局配置,以及错误回调;
class SPDLOG_API registry
{
private:// logger_map_mutex_是loggers_的互斥锁, flusher_mutex_是periodic_flusher_的互斥锁std::mutex logger_map_mutex_, flusher_mutex_; std::recursive_mutex tp_mutex_;              // 线程池tp_的递归锁std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; // logger对象注册表, 存放(logger name, logger对象共享指针)log_levels log_levels_;                      // 用户事先指定的log等级, map存放(logger name, log level)std::unique_ptr<formatter> formatter_;       // pattern 格式器, 默认为pattern_formatter实例spdlog::level::level_enum global_log_level_ = level::info; // 全局log等级值, 新logger对象缺省log等级level::level_enum flush_level_ = level::off; // flush等级值, 新logger对象缺省flush等级err_handler err_handler_;                    // 错误处理, 发生错误时回调std::shared_ptr<thread_pool> tp_;            // 线程池, 用于异步写log, 但registry并不是线程池创建者std::unique_ptr<periodic_worker> periodic_flusher_;        // 定时flushstd::shared_ptr<logger> default_logger_;     // 默认的logger对象bool automatic_registration_ = true;         // 自动注册logger对象选项size_t backtrace_n_messages_ = 0;            // 回溯消息个数, 每个logger对象都可以设置一个环形缓冲区
};

registry类函数成员

构造与析构

构造函数创建一个默认的logger对象,将stdout作为输出目标。formatter_也是用默认的pattern_formatter类构造,用于所有注册的logger对象作为编译pattern字符串的对象。

// 构造函数
SPDLOG_INLINE registry::registry(): formatter_(new pattern_formatter())
{
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER // 控制是否关闭default logger对象// 针对不同的平台, 创建不同的sink, 作为logger输出目标
#ifdef _WIN32 // Windows平台auto color_sink = std::make_shared<sinks::wincolor_stdout_sink_mt>();
#else         // non-Windows平台auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>();
#endifconst char *default_logger_name = ""; // default logger名称default_logger_ = std::make_shared<spdlog::logger>(default_logger_name,  std::move(color_sink));loggers_[default_logger_name] = default_logger_; // 加入注册表
#endif // SPDLOG_DISABLE_DEFAULT_LOGGER
}
// 默认合成的析构函数
SPDLOG_INLINE registry::~registry() = default;

单例模式

registry提供全局唯一的注册表、默认的logger对象、缺省全局配置、后端线程池等唯一性资源,需要确保registry对象的唯一性。其是是采用典型的惯用实现方法,并不是线程安全的。

// 单例模式惯用法实现registry
class SPDLOG_API registry
{
public:registry(const registry &) = delete;registry &operator=(const registry &) = delete;...static registry &instance();
private:registry();~registry();
...SPDLOG_INLINE registry &registry::instance(){static registry s_instance;return s_instance;}
}

全局注册表

registry通过维护一个map结构的注册表loggers_,存放logger对象,索引key是logger name。当用户新建了一个logger对象,想要在注册表中搜到,先要通过register_logger将其加入注册表;如果后面想要获取,可以通过get接口得到。

public:// 注册一个logger对象到注册表void register_logger(std::shared_ptr<logger> new_logger);std::shared_ptr<logger> get(const std::string &logger_name);
private:std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; // logger对象注册表, 存放(logger name, logger对象)

注册logger对象的实现register_logger很简单,就是先检测注册表中是否存在对应名称的logger对象,如果存在,就抛出异常;如果不存在,就加入。因此,需要十分注意:不能重复注册同一个logger名称的logger对象。

// 注册logger对象
SPDLOG_INLINE void registry::register_logger(std::shared_ptr<logger> new_logger)
{std::lock_guard<std::mutex> lock(logger_map_mutex_); // 由于是public接口,先获得锁,再对loggers_进行操作register_logger_(std::move(new_logger));
}
// private实现
SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger)
{auto logger_name = new_logger->name();throw_if_exists_(logger_name); // 确保logger name唯一loggers_[logger_name] = std::move(new_logger);
}
// 如果logger_name对应项存在, 就抛出异常
SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name)
{if (loggers_.find(logger_name) != loggers_.end()){throw_spdlog_ex("logger with name '" + logger_name + "' already exists");}
}

初始化logger对象

registry通过initialize_logger接口,为其提供默认初始化方式,无需为每个属性再单独设置。使用initialize_logger初始化的logger对象,会自动添加进全局注册表;如果是手动初始化的logger对象,则需要手动添加进全局注册表。

// 初始化一个logger对象new_logger
SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger)
{std::lock_guard<std::mutex> lock(logger_map_mutex_);new_logger->set_formatter(formatter_->clone());if (err_handler_) // 注意:err_handler_受互斥锁logger_map_mutex_保护{new_logger->set_error_handler(err_handler_);}// 判断用户是否有事先指定new_logger的log level, 如果有就按事先指定的设置; 如果没有, 就用默认的auto it = log_levels_.find(new_logger->name());auto new_level = it != log_levels_.end() ? it->second : global_log_level_;new_logger->set_level(new_level);// 设置flush levelnew_logger->flush_on(flush_level_);// 设置环形缓冲区, 用于回溯最近的log消息if (backtrace_n_messages_ > 0){new_logger->enable_backtrace(backtrace_n_messages_);}// 自动注册进全局注册表if (automatic_registration_){register_logger_(std::move(new_logger));}
}

全局格式器

每个logger对象都拥有自己的格式器(formatter)成员,也就是说,能指定各自的pattern,从而决定输出的log消息格式。但如果想通过一个接口,为所有logger设置格式器,该怎么办?

此时,可以用到registry的全局格式器,接口set_formatter能为所有已注册的logger对象更新格式器。

// 为所有已注册的logger对象设置格式器
SPDLOG_INLINE void registry::set_formatter(std::unique_ptr<formatter> formatter)
{std::lock_guard<std::mutex> lock(logger_map_mutex_);formatter_ = std::move(formatter);// 为所有注册表loggers_中的logger对象更新formatterfor (auto &l : loggers_){l.second->set_formatter(formatter_->clone()); // 注意这类调用了formatter::clone深度克隆对象}
}

预置日志等级

spdlog支持事先为logger对象预置日志等级,待到新建的logger对象用initialize_logger()初始化时,为其指定log level。也就是说,此时尚未创建logger对象。

预置的日志等级log_levels_是log_levels类型,本质上一个unordered_map,key是logger name, value是log level枚举值。也就是说,spdlog只支持内置的日志等级。

using log_levels = std::unordered_map<std::string, level::level_enum>;

预置日志等级工作原理:通过环境变量或main启动参数,为logger对象指定log level,通过registry::set_level将其存放进map log_levels_中。当调用initialize_logger对logger对象进行初始化时,会从log_levels_中查找是否已经预置log level,如果已经预置,就直接用预置的日志等级初始化当前新建的logger对象;如果没有预置,就用缺省的global_log_level_来设置。

SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger)
{...// 根据预置的log level或缺省的level, 来设置新logger对象的levelauto it = log_levels_.find(new_logger->name());auto new_level = it != log_levels_.end() ? it->second : global_log_level_;new_logger->set_level(new_level);...
}

在更新日志时,registry::set_levels为所有已注册logger对象更新预置日志等级。initialize_logger中设置预置日志等级,只会更新单个logger对象的日志等级,而set_levels中设置,则会更新所有已注册logger对象的日志等级。

// 更新日志等级
// 当想为已注册logger对象更新时,指定levels即可;当想更新全局日志等级时,需要指定global_level
SPDLOG_INLINE void registry::set_levels(log_levels levels, level::level_enum  *global_level)
{std::lock_guard<std::mutex> lock(logger_map_mutex_);log_levels_ = std::move(levels);auto global_level_requested = global_level != nullptr;global_log_level_ = global_level_requested ? *global_level :  global_log_level_;// 为所有已注册logger对象更新日志等级, 优先用预置的, 没有找到预置时, 再用全局的for (auto &logger : loggers_){auto logger_entry = log_levels_.find(logger.first);if (logger_entry != log_levels_.end()){logger.second->set_level(logger_entry->second);}else if (global_level_requested){logger.second->set_level(*global_level);}}
}

## 定时工作类periodic_worker

在下文中的flush操作中,创建了一个定时工作类periodic_worker对象。该类构造时,会启动一个线程,在其中利用锁+条件变量定时执行用户回调,从而实现用户自定义任务的定时执行。

// 定时工作者类periodic_worker, 为用户自定义任务fun提供定时回调功能
class SPDLOG_API periodic_worker
{
public:// 构造函数template<typename Rep, typename Period>periodic_worker(const std::function<void()> &callback_fun,  std::chrono::duration<Rep, Period> interval);// 禁用copy操作periodic_worker(const periodic_worker &) = delete;periodic_worker &operator=(const periodic_worker &) = delete;~periodic_worker();private:bool active_;std::thread worker_thread_;std::mutex mutex_;std::condition_variable cv_;
};

用户传入的时间间隔参数(interval)的类型并不确定,可能是秒(chrono::seconds),也可能是毫秒(chrono::milliseconds),或者分钟(chrono::minutes)等等,为了兼容这些类型,因而使用模板来支持。

template<typename Rep, typename Period>
periodic_worker(const std::function<void()> &callback_fun,  std::chrono::duration<Rep, Period> interval) 
{active_ = (interval > std::chrono::duration<Rep, Period>::zero());if (!active_){return;}// for循环+互斥锁+条件变量 实现子线程主循环, 以定时执行callback_funworker_thread_ = std::thread([this, callback_fun, interval]() {for (;;){std::unique_lock<std::mutex> lock(this->mutex_);// wait_for等到超时, 返回cv_status::timeout(true)// 如果wait_for第三个参数如果返回true, 则不再等待; 如果返回false, 则继续等待// active_ 控制线程(循环)是否退出if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })){return;}callback_fun();}});
}

析构函数负责停止子线程的循环,并连接子线程以回收资源。

SPDLOG_INLINE periodic_worker::~periodic_worker()
{if (worker_thread_.joinable()) // 只有线程可连接情况下,连接线程才有意义{{std::lock_guard<std::mutex> lock(mutex_);active_ = false;}cv_.notify_one();worker_thread_.join();}
}

flush日志等级

spdlog支持单独flush(冲刷)指定level的日志消息(与记录日志的level分开)到目标文件,registry提供flush_on接口,设置(注册的)logger对象全局的flush_level_。

// 当各个logger对象调用自身flush函数时, 会根据该flush等级与log消息的等级判断是否应该将文件立即冲刷到目标文件
SPDLOG_INLINE void registry::flush_on(level::level_enum log_level)
{std::lock_guard<std::mutex> lock(logger_map_mutex_);for (auto &l : loggers_){l.second->flush_on(log_level);}flush_level_ = log_level;
}

搭配定时器periodic_flusher_,定时调用flush_every,设置一个周期性工作的线程,每隔指定的时间间隔 interval 就调用一次 flush_all 方法,刷新所有注册的 logger 对象。

// 指定一个周期性工作的线程, 定时flush所有loggertemplate<typename Rep, typename Period>void flush_every(std::chrono::duration<Rep, Period> interval){std::lock_guard<std::mutex> lock(flusher_mutex_);auto clbk = [this]() { this->flush_all(); };periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval);}// flush所有已注册logger对象的log消息到目标文件
// 要求logger对象必须是线程安全的
SPDLOG_INLINE void registry::flush_all()
{std::lock_guard<std::mutex> lock(logger_map_mutex_);for (auto &l : loggers_){l.second->flush();}
}

默认logger

为了方便用户spdlog,registry提供了默认logger对象default_logger_,无需用户再手动创建、设置。default_logger_的默认目标(sink)是控制台(stdout),可以很方便用户调试。

获取默认的logger对象,有两种方式:

  • default_logger()获取共享指针管理的logger对象,线程安全;
  • 通过shared_ptr<>::get()获取raw pointer,速度default_logger()比更快,但无法与set_default_logger()并发调用。
// 获取shared_ptr管理的默认logger, 线程安全
SPDLOG_INLINE std::shared_ptr<logger> registry::default_logger()
{std::lock_guard<std::mutex> lock(logger_map_mutex_);return default_logger_;
}
// 获取指向默认logger的raw pointer, 非线程安全, 但速度更快
SPDLOG_INLINE logger *registry::get_default_raw()
{return default_logger_.get();
}

set_default_logger 方法用于设置和更新默认的 logger 对象。用户可以通过这个方法将默认的 logger 替换成自定义的 logger 对象。

// set default logger.
SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger)
{std::lock_guard<std::mutex> lock(logger_map_mutex_);// 先从注册表删除// remove previous default logger from the mapif (default_logger_ != nullptr){loggers_.erase(default_logger_->name());}// 再将新的new_default_logger加入注册表if (new_default_logger != nullptr){loggers_[new_default_logger->name()] = new_default_logger;}// 更新默认的loggerdefault_logger_ = std::move(new_default_logger);
}

在已注册logger上应用函数

将自定义函数作为函数参数,传入apply_all,对每个已注册的logger对象都调用fun()。

// 将已注册logger作为参数, 应用到函数fun
SPDLOG_INLINE void registry::apply_all(const std::function<void(const  std::shared_ptr<logger>)> &fun)
{std::lock_guard<std::mutex> lock(logger_map_mutex_);for (auto &l : loggers_){fun(l.second);}
}

回溯最近的log消息

logger对象包含backtracer(环形队列)成员用于回溯最近的log消息,registry提供enable_backtrace接口,方便用户设置、开启该功能。

// 开启回溯功能
// 指定logger对象的环形缓冲区大小,设置了大小才能回溯最近的log消息
SPDLOG_INLINE void registry::enable_backtrace(size_t n_messages)
{std::lock_guard<std::mutex> lock(logger_map_mutex_); // 加锁确保线程安全backtrace_n_messages_ = n_messages;for (auto &l : loggers_){l.second->enable_backtrace(n_messages);}
}

线程安全

registry用了2把互斥锁(std::mutex) + 1把递归锁(std::recursive_mutex)。总原则: logger_map_mutex_保护几乎所有数据成员,flusher_mutex_和tp_mutex_分别只保护periodic_flusher_、tp_。

为什么 tp_mutex_ 使用递归锁?

递归锁允许同一线程多次获取锁,而不会引发死锁。如果使用普通的 std::mutex,在同一线程内第二次尝试获取锁时会导致死锁。因此使用 std::recursive_mutex 允许同一线程多次获取锁,从而避免这种问题。

线程池的并发访问:由于线程池对象 tp_ 在创建后通常不会被修改(除了在释放时),并且并发访问通常是安全的,因此使用递归锁不会引发性能问题或数据一致性问题。

错误处理

logger对象本身包含错误回调err_handler_,registry的err_handler_存在意义就是为logger的错误回调提供缓存及用户接口。可通过registry的set_error_handler接口,为每个logger对象更新错误回调。

SPDLOG_INLINE void registry::set_error_handler(err_handler handler)
{std::lock_guard<std::mutex> lock(logger_map_mutex_);for (auto &l : loggers_){l.second->set_error_handler(handler);}err_handler_ = std::move(handler);
}

错误回调主要在宏 SPDLOG_LOGGER_CATCH 中被调用。当捕获到异常时,会根据是否提供了自定义错误处理函数来决定调用哪一个回调。

#ifndef SPDLOG_NO_EXCEPTIONS
#    define SPDLOG_LOGGER_CATCH(location)                                                                                        \catch (const std::exception &ex)                                                                                         \{                                                                                                                        \if (location.filename)                                                                                               \{                                                                                                                    \err_handler_(fmt_lib::format(SPDLOG_FMT_STRING("{} [{}({})]"),  ex.what(), location.filename, location.line));   \}                                                                                                                    \else                                                                                                                 \{                                                                                                                    \err_handler_(ex.what());                                                                                         \}                                                                                                                    \}                                                                                                                        \catch (...)                                                                                                              \{                                                                                                                        \err_handler_("Rethrowing unknown exception in logger");                                                              \throw;                                                                                                               \}
#else
#    define SPDLOG_LOGGER_CATCH(location)
#endif

在 logger 将日志消息写入到 sink 时,可能会发生写入失败的错误,此时会抛出异常。示例代码:

SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name)
{if (loggers_.find(logger_name) != loggers_.end()){ // 如果 logger 已经存在,则抛出异常throw_spdlog_ex("logger with name '" + logger_name + "' already exists");}
}

common 模块提供了方便的异常抛出接口,有两个重载版本,分别针对系统调用错误和非系统调用错误。

[[noreturn]] SPDLOG_API void throw_spdlog_ex(const std::string &msg, int  last_errno);
[[noreturn]] SPDLOG_API void throw_spdlog_ex(std::string msg);// 系统调用错误抛出异常
SPDLOG_INLINE void throw_spdlog_ex(const std::string &msg, int last_errno)
{SPDLOG_THROW(spdlog_ex(msg, last_errno));
}// 非系统调用错误抛出异常
SPDLOG_INLINE void throw_spdlog_ex(std::string msg)
{SPDLOG_THROW(spdlog_ex(std::move(msg)));
}

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

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

相关文章

Forth Python语言:深度解析其四维、五维、六维与七维之奥秘

Forth Python语言&#xff1a;深度解析其四维、五维、六维与七维之奥秘 在编程语言的浩瀚星空中&#xff0c;Forth Python以其独特的魅力与深邃的内涵&#xff0c;吸引着众多探索者的目光。然而&#xff0c;这门语言究竟有何独到之处&#xff1f;本文将从四维、五维、六维和七…

大模型高级 RAG 检索策略:自动合并检索

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学. 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总合集&…

Flutter 中的 CupertinoSliverNavigationBar 小部件:全面指南

Flutter 中的 CupertinoSliverNavigationBar 小部件&#xff1a;全面指南 Flutter 是一个由 Google 开发的跨平台 UI 框架&#xff0c;它允许开发者使用 Dart 语言来构建高性能、美观的移动、Web 和桌面应用。在 Flutter 的丰富组件库中&#xff0c;CupertinoSliverNavigation…

比较3维空间中4个点的不同结构

在4*4*4的3维空间中&#xff0c;取4个点共有635376种可能&#xff0c;有209个结构&#xff0c;继续按旋转对称分类则只有55个不同的结构。如其中的4t12 4个点在同一个平面&#xff0c;有1个点与其中的3个点不在同一行也不在同一列&#xff0c;这样的位置不止一个 这两个结构都是…

Apache Calcite - 自定义数据源适配之访问内存列表

前言 上一篇文章中学习了Calcite基本概念&#xff0c;其中框架的核心能力是通过统一的Sql访问不同来源的数据。这篇文章中将通过一个简单的例子学习如何实现改功能。 最终通过sql来访问Java List中的数据。 准备工作 maven依赖 <dependency><groupId>org.apache…

ubuntu系统下安装mysql的步骤详解

一、下载安装包 下载地址&#xff1a; https://dev.mysql.com/downloads/repo/apt 跳转到这个页面&#xff1a; 直接点击Download。 直接点击最下面的开始下载安装包即可。 二、将安装包下载到ubuntu系统中 先将用户切换成root用户&#xff0c;把下载好的安装包复制到桌面上&…

域名更换服务器的原因

在互联网的运营过程中&#xff0c;域名更换服务器是一个常见的操作&#xff0c;可能是由于业务扩展、性能需求、成本考虑或服务质量等多种因素。然而&#xff0c;这个过程如果处理不当&#xff0c;可能会导致网站访问中断、搜索引擎排名下降或用户体验受损。本文将探讨在域名更…

系统架构设计师【第11章】: 未来信息综合技术 (核心总结)

文章目录 11.1 信息物理系统技术概述11.1.1 信息物理系统的概念11.1.2 CPS的实现11.1.3 信息物理系统的建设和应用 11.2 人工智能技术概述11.2.1 人工智能的概念11.2.2 人工智能的发展历程11.2.3 人工智能关键技术 11.3 机器人技术概述11.3.1 机器人的概念11.3.2 机…

丛林生存法则其实就两个字:输出

不管你是在上班&#xff0c;还是在灵活就业&#xff0c;现在的大环境下&#xff0c;你要想活下来&#xff0c;生存下去&#xff0c;一定要记住这两个字&#xff1a;输出。如果你能记住更多的字&#xff0c;那便是持续高水平的输出。 你如果是大厂程序员&#xff0c;你肯定发现…

Linux DHCP server 配置

参考&#xff1a;linux dhcp配置多vlan ip_linux 接口vlan-CSDN博客 配置静态IP地址&#xff1a; 给固定的MAC地址分配指定的IP地址&#xff0c;固定的IP地址不必包含在指定的IP池中&#xff0c;如果包含在IP地址池中&#xff0c;固定的IP地址会从IP地址池中移除 配置方法&…

清洁力强的洗地机前十名排行榜:2024十大洗地机热销款式好用不踩雷

如今&#xff0c;洗地机行业竞争激烈&#xff0c;各品牌紧紧抓住用户对智能化和深度清洁的需求&#xff0c;深入研究创新。经过几轮行业内部的激烈竞争后&#xff0c;许多厂商在宣传中各说各的&#xff0c;对洗地机的重要参数描述不一&#xff0c;给消费者的选择带来了不少困惑…

【CVPR_2024】:逐元素乘积为什么会产生如此令人满意的结果?

写在前面&#xff1a;本博客仅作记录学习之用&#xff0c;部分图片来自网络&#xff0c;如需引用请注明出处&#xff0c;同时如有侵犯您的权益&#xff0c;请联系删除&#xff01; 文章目录 前言论文重写星形运算一层网络推广多层网络特殊情况 W 1 W_1 W1​和/或 W 2 W_2 W2​…

JDK版本特性(JDK8\11\17\21版本)

JDK版本特性 Oracle官网https://www.oracle.com/java/technologies/java-se-support-roadmap.html Oracle官网中JDK版本的说明&#xff0c;Java SE 8、11、17和21是LTS版本。也就是长期支持版本。 我们针对这几个版本了解学习下对应版本的新特性。 JDK8版本 正式发布于2014…

Facebook的创新实验室:人工智能与新技术探索

Facebook作为全球领先的社交媒体平台之一&#xff0c;一直在不断探索和应用最新的技术来改善用户体验、推动创新和拓展业务边界。其创新实验室更是探索人工智能&#xff08;AI&#xff09;和新技术的前沿&#xff0c;为未来的社交媒体发展开辟了新的可能性。本文将深入探讨Face…

C语言| 求1到100之间所有能被3整除的数之和

思路分析&#xff1a; 有两种解题方法&#xff1a; 第一种 直接在100个数字里面&#xff0c;找能被3整除的数字。 for循环里面&#xff0c;判断条件是i%3 0。 第二种 设置变量i 3&#xff0c;for循环&#xff0c;每次i 加3&#xff0c;并相加求和&#xff0c;直到遍历完…

【题解 | 分享】2023年十四届蓝桥杯国赛(Java B组)

互质 答案&#xff1a;640720414 参考&#xff1a; public class Main {static int mod 1000000007;public static void main(String[] args) {long sum power(2023, 2023);long p1 ((sum % mod) * power( 7, mod - 2)) % mod;long p2 ((sum % mod) * power( 17, mod -…

Java数据结构与算法(最长回文子串中心扩散法)

前言 回文子串是练习数据结构和算法比较好的使用场景&#xff0c;可以同时练习到双指针、动态规划等一些列算法。 实现原理 中心扩散算法实现。这里定义最长回文子串长度的大小为maxLen&#xff0c;起点位置为0. 奇数个数为中心点和偶数个数为中心点分别计算回文长度大小。…

【ZYNQ】SCU 与 GIC

在多 CPU 架构中&#xff0c;处理器之间可以对共享数据进行操作。Snoop control uint (SCU) 模块用于确保每个处理器都在最新的数据拷贝上运行&#xff0c;从而保持缓存一致性。通用中断控制器 Generic interrupt controller (GIC) 使用优先级的思想&#xff0c;管理 CPU 中断信…

Spring系统学习 - Spring入门

什么是Spring&#xff1f; Spring翻译过来就是春天的意思&#xff0c;字面意思&#xff0c;冠以Spring的意思就是想表示使用这个框架&#xff0c;代表程序员的春天来了&#xff0c;实际上就是让开发更加简单方便&#xff0c;实际上Spring确实做到了。 官网地址&#xff1a;ht…

springboot 的yaml配置文件加密

springboot 的yaml配置文件加密 一、采用yaml 插件加密添加依赖创建启动类配置加密密钥加密需要加密的内容用过测试类编写加密的YAML配置解密配置可选&#xff1a;自定义配置扩展&#xff1a;修改ENC() 一、采用yaml 插件加密 使用Jasypt对Spring Boot的YAML配置文件进行加密是…