C++ 日志系统实战第五步:日志器的设计
全是通俗易懂的讲解,如果你本节之前的知识都掌握清楚,那就速速来看我的项目笔记吧~
本文项目代码编写收尾!
日志器类 (Logger) 设计(建造者模式)
日志器主要用于和前端交互。当我们需要使用日志系统打印 log 时,只需创建 Logger 对象,调用该对象的 debug、info、warn、error、fatal 等方法,即可输出想打印的日志。它支持解析可变参数列表和输出格式,能做到像使用 printf 函数一样打印日志。
当前日志系统支持同步日志与异步日志两种模式。两种日志器的差异仅在于日志的落地方式:
- 同步日志器:直接输出日志消息。
- 异步日志器:将日志消息放入缓冲区,由异步线程进行输出。
因此,设计日志器类时,先设计一个 Logger 基类,在此基础上,继承出 SyncLogger 同步日志器和 AsyncLogger 异步日志器。
由于日志器模块整合了多个模块,创建一个日志器时,需要设置日志器名称、日志输出等级、日志器类型、日志输出格式以及落地方向(落地方向可能有多个) ,整个日志器的创建过程较为复杂。为保持良好的代码风格,编写出优雅的代码,日志器的创建采用建造者模式。
logger.hpp
#pragma once
/*
日志器实现
1.抽象基类
2.派生出日志器具体实现类
*/
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "message.hpp"
#include "looper.hpp"
#include <atomic>
#include <mutex>
#include <cstdarg>namespace mylog
{class Logger{public:Logger(std::string logger_name, Level limit_level, FormatBuilder::ptr format_builder, std::vector<mylog::LogSink::ptr> sinks) : logger_name_(logger_name),limit_level_(limit_level),sinks_(sinks.begin(), sinks.end()),format_builder_(format_builder){}using ptr = std::shared_ptr<Logger>;// 完成构造日志消息构造过程并格式化,然后调用log函数输出日志void debug(std::string file_, size_t line_, const std::string &fat, ...){// 1.判断是否达到日志等级if (limit_level_ > Level::Debug){return;}// 2.对fmt字符串和不定参数进行字符串组织,得到最终的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 获取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Debug, file_, line_, res); // 序列化日志消息free(res); // 释放res}void info(std::string file_, size_t line_, const std::string &fat, ...){// 1.判断是否达到日志等级if (limit_level_ > Level::Info){return;}// 2.对fmt字符串和不定参数进行字符串组织,得到最终的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 获取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Info, file_, line_, res); // 序列化日志消息free(res); // 释放res}void warning(std::string file_, size_t line_, const std::string &fat, ...){// 1.判断是否达到日志等级if (limit_level_ > Level::Warning){return;}// 2.对fmt字符串和不定参数进行字符串组织,得到最终的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 获取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Warning, file_, line_, res); // 序列化日志消息free(res); // 释放res}void error(std::string file_, size_t line_, const std::string &fat, ...){// 1.判断是否达到日志等级if (limit_level_ > Level::Error){return;}// 2.对fmt字符串和不定参数进行字符串组织,得到最终的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 获取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Error, file_, line_, res); // 序列化日志消息free(res); // 释放res}void fatal(std::string file_, size_t line_, const std::string &fat, ...){// 1.判断是否达到日志等级if (limit_level_ > Level::Fatal){return;}// 2.对fmt字符串和不定参数进行字符串组织,得到最终的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 获取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Fatal, file_, line_, res); // 序列化日志消息free(res); // 释放res}protected:void serialize(Level level, std::string file, size_t line, const char *str) // 序列化日志消息{// 3.构造日志消息LogMessage message(level, file.c_str(), line, str, logger_name_.c_str());// 4.格式化日志消息std::stringstream ss;format_builder_->format(ss, message);// 5.输出日志消息log(ss.str().c_str(), ss.str().size());}// 抽象接口完成实际的落地输出——不同日志器实现类完成具体的落地输出virtual void log(const char *msg, size_t line) = 0;protected:std::mutex mutex_; // 日志器互斥锁std::string logger_name_; // 日志器名称std::atomic<Level> limit_level_; // 日志等级限制std::vector<mylog::LogSink::ptr> sinks_; // 日志输出器FormatBuilder::ptr format_builder_; // 日志格式构造器};// 日志器具体实现类——同步日志器class sync_logger : public Logger{// 同步日志器,将日志直接通过落地模块句柄进行日志落地public:sync_logger(std::string logger_name, Level limit_level, FormatBuilder::ptr format_builder, std::vector<mylog::LogSink::ptr> sinks) : Logger(logger_name, limit_level, format_builder, sinks){}void log(const char *data, size_t len) override{// 加锁std::unique_lock<std::mutex> lock(mutex_);if (sinks_.empty()){return;}for (auto &sink : sinks_){sink->log(data, len); // 调用sink的log函数输出日志}}};class Async_logger : public Logger{public:Async_logger(std::string logger_name, Level limit_level, FormatBuilder::ptr format_builder, std::vector<mylog::LogSink::ptr> sinks, AsyncType async_type) : Logger(logger_name, limit_level, format_builder, sinks){looper_ = std::make_shared<Async_looper>(std::bind(&Async_logger::reallog, this, std::placeholders::_1));}void log(const char *data, size_t len) override // 将数据写入缓冲区{looper_->push(data, len);}void reallog(Buffer &buf){if (sinks_.empty()){return;}for (auto &sink : sinks_){sink->log(buf.begin(), buf.readable_size()); // 调用sink的log函数输出日志}}private:Async_looper::ptr looper_;};/*使用建造者模式来建造日志器,而不是让用户直接构造日志器,这样可以避免用户构造错误的日志器,简化用户的使用复杂度*/enum class LoggerType{Loggertype_Sync, // 同步日志器Loggertype_Async, // 异步日志器};// 1.抽象一个建筑者类// 1.设置日志器类型// 2.将不同类型日志器的创建放到同一个日志器建造者中完成class Logger_builder{public:Logger_builder() : type_(LoggerType::Loggertype_Sync), limit_level_(Level::Debug),async_type_(AsyncType::ASYNC_SAFE){}void buildLoggerType(LoggerType type){type_ = type;}void buildLoggerName(std::string logger_name){logger_name_ = logger_name;}void buildLimitLevel(Level limit_level){limit_level_ = limit_level;}void buildFormatBuilder(const std::string &pattern){format_builder_ = std::make_shared<FormatBuilder>(pattern);}void buildAsyncType(AsyncType async_type){async_type_ = async_type;}template <typename LogSinkType, typename... Args>void buildSinks(Args &&...args){LogSink::ptr psink = std::make_shared<LogSinkType>(std::forward<Args>(args)...);sinks_.push_back(psink);}virtual Logger::ptr build() = 0;protected:AsyncType async_type_;LoggerType type_;std::string logger_name_;Level limit_level_;FormatBuilder::ptr format_builder_;std::vector<mylog::LogSink::ptr> sinks_;};// 2.派生出具体建筑者类——局部日志器的建造者 & 全局的日志器建造者class Local_logger_builder : public Logger_builder{public:Logger::ptr build() override{assert(logger_name_.empty() == false);if (format_builder_.get() == nullptr){format_builder_ = std::make_shared<FormatBuilder>();}if (sinks_.empty()){sinks_.push_back(std::make_shared<StdoutSink>());}if (type_ == LoggerType::Loggertype_Async){return std::make_shared<Async_logger>(logger_name_,limit_level_, format_builder_, sinks_, async_type_);}return std::make_shared<sync_logger>(logger_name_, limit_level_, format_builder_, sinks_);}};
}
只有在交换的时候,生产者和消费者才会产生一次锁冲突
buffer.hpp
#pragma once/*实现异步日志缓冲区*/
#include "util.hpp"
#include <vector>
#include <cassert>namespace mylog
{
#define BUFFER_SIZE (1 * 1024 * 1024)
#define THRESHOLD_BUFFER_SIZE (8 * 1024 * 1024) // 内存阈值,没有超过阈值,内存翻倍扩容,达到阈值后,内存线性增长
#define INCREASE_BUFFER_SIZE (1 * 1024 * 1024) // 增加缓冲区大小class Buffer{private:std::vector<char> buffer_; // 缓冲区size_t reader_index; // 当前可读的位置size_t writer_index; // 当前可写的位置private:void move_writer(size_t len) // 移动写指针{assert(len + writer_index <= writeable_size());writer_index += len;}void ensure_Enough_capacity(size_t len) // 确保缓冲区有足够的空间容纳len字节{if (len <= writeable_size()) // 如果写入数据超过可写空间,不需要扩容{return;}else // 如果写入数据超过可写空间,需要扩容{size_t new_size = 0;while (new_size < len){if (buffer_.size() < THRESHOLD_BUFFER_SIZE){new_size = buffer_.size() * 2; // 小于阈值,翻倍增长}else{new_size = buffer_.size() + INCREASE_BUFFER_SIZE; // 大于阈值,线性增长}buffer_.resize(new_size); // 扩容}}}public:Buffer() : buffer_(BUFFER_SIZE), reader_index(0), writer_index(0) {}void push(const char *data, size_t len) // 写入数据{// 当前可写空间不足// if(len>writeable_size()) //如果写入数据超过可写空间// {// return;//1.直接返回// }// 2.动态空间,用于极限性能测试——扩容ensure_Enough_capacity(len);// 写入数据std::copy(data, data + len, &buffer_[writer_index]);// 将写入位置后移len个字节move_writer(len);}const char *begin() // 返回缓冲区起始位置{return &buffer_[reader_index];}size_t readable_size() // 返回可读字节数{return writer_index - reader_index;}size_t writeable_size() // 返回可写字节数{// 对于扩容思路来说,不存在可写空间大小,业务总是可写return buffer_.size() - writer_index;}void move_reader(size_t len) // 移动读指针{assert(len <= readable_size());reader_index += len;}void reset() // 重置缓冲区{reader_index = 0;writer_index = 0;}void swap(Buffer &other) // 交换缓冲区{buffer_.swap(other.buffer_);std::swap(reader_index, other.reader_index);std::swap(writer_index, other.writer_index);}bool empty() // 判断缓冲区是否为空{return reader_index == writer_index;}};
}
双缓冲区异步任务处理器(AsyncLooper)设计
- 设计思想:异步处理线程 + 数据池
- 使用者将需要完成的任务添加到任务池中,由异步线程来完成任务的实际执行操作。
- 任务池的设计思想:双缓冲区阻塞数据池
- 优势:
- 避免了空间的频繁申请释放。
- 尽可能的减少了生产者与消费者之间锁冲突的概率,提高了任务处理效率。
在任务池的设计中,有很多备选方案,比如循环队列等等。但是不管是哪一种都会涉及到锁冲突的情况,因为在生产者与消费者模型中,任何两个角色之间都具有互斥关系,因此每一次的任务添加与取出都有可能涉及锁的冲突。
而双缓冲区不同,双缓冲区是处理器将一个缓冲区中的任务全部处理完毕后,然后交换两个缓冲区,重新对新的缓冲区中的任务进行处理。虽然同时多线程写入也会冲突,但是冲突并不会像每次只处理一条的时候频繁(减少了生产者与消费者之间的锁冲突),且不涉及到空间的频繁申请释放所带来的消耗。
0
looper.hpp
/*异步器*/
#pragma once#include "buffer.hpp"
#include <condition_variable>
#include <mutex>
#include <thread>
#include <functional>
#include <memory>
#include <atomic>namespace mylog
{using Functor = std::function<void(Buffer &)>;enum class AsyncType{ASYNC_SAFE, // 安全状态,表示缓冲区满了则阻塞,防止内存资源耗尽ASYNC_UNSAFE // 非安全状态};class Async_looper{public:using ptr = std::shared_ptr<Async_looper>;Async_looper(const Functor &callback, AsyncType type = AsyncType::ASYNC_SAFE) : stop_(false), thread_(std::thread(&Async_looper::threadEntry, this)), callback_(callback), type_(type) {}void stop(){stop_ = true;con_cv_.notify_all(); // 通知消费者线程退出thread_.join();}~Async_looper(){stop();}void push(const char *data, size_t len){// 1.无线扩容——非安全; 2.固定大小——生产缓冲区满后,生产者线程阻塞;std::unique_lock<std::mutex> lock(mutex_);// 条件变量控制,若缓冲区剩余大小大于数据长度,可以添加数据,否则等待if (type_ == AsyncType::ASYNC_SAFE)pro_cv_.wait(lock, [&](){ return pro_buffer_.writeable_size() >= len; });// 添加数据到缓冲区pro_buffer_.push(data, len);// 通知消费者线程有数据可消费if (type_ == AsyncType::ASYNC_SAFE)con_cv_.notify_all();}private:// 线程入口函数——对消费缓冲区的数据进行处理,处理完毕后,初始化缓冲区void threadEntry(){while (true){// 1.判断生产缓冲区有没有数据,有就交换; .无数据,等待;{std::unique_lock<std::mutex> lock(mutex_);con_cv_.wait(lock, [&](){ return stop_ || !pro_buffer_.empty(); });// 如果停止且生产者缓冲区为空则退出if (stop_ && pro_buffer_.empty())break;// 1.交换生产者缓冲区和消费者缓冲区con_buffer_.swap(pro_buffer_);// 2.通知生产者线程可以继续添加数据if (type_ == AsyncType::ASYNC_SAFE)pro_cv_.notify_all();}// 3.被唤醒后,对消费缓冲区进行数据处理callback_(con_buffer_);// 4.初始化消费者缓冲区con_buffer_.reset();}}private:Functor callback_; // 具体对缓冲区数据进行操作的回调函数private:AsyncType type_;std::atomic<bool> stop_;Buffer pro_buffer_; // 生产者缓冲区Buffer con_buffer_; // 消费者缓冲区std::mutex mutex_; // 互斥锁std::condition_variable pro_cv_; // 生产者条件变量std::condition_variable con_cv_; // 消费者条件变量std::thread thread_; // 异步线程};
}
class Async_logger : public Logger{public:Async_logger(std::string logger_name, Level limit_level, FormatBuilder::ptr format_builder, std::vector<mylog::LogSink::ptr> sinks,AsyncType async_type) : Logger(logger_name, limit_level, format_builder, sinks){looper_ = std::make_shared<Async_looper>(std::bind(&Async_logger::reallog, this, std::placeholders::_1));}void log(const char *data, size_t len) override//将数据写入缓冲区{looper_->push(data, len);}void reallog(Buffer &buf){if(sinks_.empty()){return;}for(auto &sink : sinks_){sink->log(buf.begin(), buf.writeable_size()); // 调用sink的log函数输出日志}}private:Async_looper::ptr looper_;};
单例日志器管理类设计(单例模式)
日志的输出,我们希望能够在任意位置都可以进行,但是当我们创建了一个日志器之后,就会受到日志器所在作用域的访问属性限制。
因此,为了突破访问区域的限制,我们创建一个日志器管理类,且这个类是一个单例类,这样的话,我们就可以在任意位置来通过管理器单例获取到指定的日志器来进行日志输出了。
基于单例日志器管理器的设计思想,我们对于日志器建造者类进行继承,继承出一个全局日志器建造者类,实现一个日志器在创建完毕后,直接将其添加到单例的日志器管理器中,以便于能够在任何位置通过日志器名称能够获取到指定的日志器进行日志输出。
在logger.hpp里面
class LoggerManager{private:std::mutex mutex_;Logger::ptr root_logger_; // 默认日志器std::unordered_map<std::string, Logger::ptr> loggers_; // 日志器名称-日志器映射表private:LoggerManager(){std::unique_ptr<mylog::Local_logger_builder> builder(new mylog::Local_logger_builder());builder->buildLoggerName("root");root_logger_ = builder->build();addLogger(root_logger_);}public:static LoggerManager &getInstance(){// 在C++11之后,针对静态变量,编译器在编译的层面实现了线程安全// 当静态局部变量在没有构造完成之前,其他线程进入就会阻塞static LoggerManager eton;return eton;}void addLogger(Logger::ptr logger){if (hasLogger(logger->get_logger_name()))return;std::unique_lock<std::mutex> lock(mutex_);loggers_.insert(std::make_pair(logger->get_logger_name(), logger));}bool hasLogger(const std::string &name){std::unique_lock<std::mutex> lock(mutex_);auto it = loggers_.find(name);if (it != loggers_.end()){return true;}return false;}Logger::ptr getLogger(const std::string &name){std::unique_lock<std::mutex> lock(mutex_);auto it = loggers_.find(name);if (it != loggers_.end()){return it->second;}return nullptr;}Logger::ptr rootLogger(){return root_logger_;}};
日志宏 & 全局接口设计(代理模式)
- 提供全局的日志器获取接口。
- 使用代理模式,通过全局函数或宏函数来代理 Logger 类的 log、debug、info、warn、error、fatal 等接口,以便于控制源码文件名称和行号的输出控制,简化用户操作。
- 当仅需标准输出日志的时候,可以通过主日志器来打印日志。且操作时只需要通过宏函数直接进行输出即可。
mylog.h
#pragma once#include"logger.hpp"namespace mylog
{Logger::ptr get_logger(const std::string &name){return mylog::LoggerManager::getInstance().getLogger(name);}Logger::ptr rootLogger(){return mylog::LoggerManager::getInstance().rootLogger();}#define debug(fmt, ...) debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define info(fmt, ...) info(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define warn(fmt, ...) warn(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define error(fmt, ...) error(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define fatal(fmt, ...) fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define DEBUG(fmt, ...) mylog::rootLogger()->debug(fmt, ##__VA_ARGS__)#define INFO(fmt, ...) mylog::rootLogger()->info(fmt, ##__VA_ARGS__)#define WARN(fmt, ...) mylog::rootLogger()->warn(fmt, ##__VA_ARGS__)#define ERROR(fmt, ...) mylog::rootLogger()->error(fmt, ##__VA_ARGS__)#define FATAL(fmt, ...) mylog::rootLogger()->fatal(fmt, ##__VA_ARGS__)
}
后续还有测试代码,期待共同实现~
如果你对日志系统感到兴趣,欢迎关注我👉【A charmer】
相关文章:

C++ 日志系统实战第五步:日志器的设计
全是通俗易懂的讲解,如果你本节之前的知识都掌握清楚,那就速速来看我的项目笔记吧~ 本文项目代码编写收尾! 日志器类 (Logger) 设计(建造者模式) 日志器主要用于和前端交互。当我们需要使用日志系统打印 log 时&…...
@Docker Compose部署Alertmanager
文章目录 Docker Compose部署Alertmanager1. 准备工作1.1 系统要求1.2 目录结构准备 2. 配置文件准备2.1 创建docker-compose.yml文件2.2 创建Alertmanager配置文件 3. 部署Alertmanager3.1 启动服务3.2 验证服务状态3.3 检查日志 4. 服务验证4.1 访问Web UI 4.2 API健康检查5.…...
前端面试准备-3
1.let、const、var的区别 ①:let和const为块级作用域,var为全局作用域 ②:let和var可以重新赋值定义,而const不可以 ③:var会提升到作用域顶部,但不会初始化;let和const也会提升到作用不顶部…...

性能测试-jmeter实战1
课程:B站大学 记录软件测试-性能测试学习历程、掌握前端性能测试、后端性能测试、服务端性能测试的你才是一个专业的软件测试工程师 性能测试-jmeter实战1 为什么需要性能测试呢?性能测试的作用?性能测试体系性能测试基础性能测试工具性能监控…...
汽车高速通信的EMC挑战
随着“软件定义汽车”的理念全面渗透,中国汽车行业正加速向集中式电子电气架构(E/E架构)转型。SOA(面向服务的架构)理念推动下,整车开始围绕中央计算平台(OIB)与分布式域控制器(VIU)构建,硬件平台具备前所未有的数据处理能力,能掌控整车控制与实时感知决策。 一、…...
[SC]SystemC在CPU/GPU验证中的应用(五)
SystemC在CPU/GPU验证中的应用(五) 摘要:下面分享50个逐步升级SystemC编程能力的示例及建议的学习路线图。您可以一次一批地完成它们——从前五个基础的例子开始,然后转向channels, TLM, bus models, simple CPU/GPU kernels等等。在每个阶段掌握之后,再进行下一组…...
[蓝桥杯C++ 2024 国 B ] 立定跳远(二分)
题目描述 在运动会上,小明从数轴的原点开始向正方向立定跳远。项目设置了 n n n 个检查点 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots , a_n a1,a2,⋯,an 且 a i ≥ a i − 1 > 0 a_i \ge a_{i−1} > 0 ai≥ai−1>0。小明必须先后跳跃到每个检查…...
现代网络安全攻防技术与发展现状
1. 引言 随着数字化转型进程的加速,全球信息化程度不断深入,网络安全问题日益凸显。根据最新的统计数据,2022年全球范围内的网络攻击事件较前一年增长了约41%,造成的经济损失高达超过6万亿美元。在这个背景下,了解现代…...

杏仁海棠花饼的学习日记第十四天CSS
一,前言 第二天,今天看CSS。 二,CSS简介及导入方式 CSS简介 CSS(层叠样式表,Cascading Style Sheets)是一种用于描述 HTML 或 XML(包括 SVG、XHTML 等)文档呈现效果的样式语言。…...

ESP8266远程控制:实现网络通信与设备控制
概述: 最近一直在弄esp8266的网络通信,但是一直都还没搞懂到底esp8266可不可以通过连接一个网络过后,在很远的地方使用网络将其关掉 在网上找了两个教程都有程序,都跑通了 第一个 第二个找不到了,但是程序有 CSDN上放文…...
RabbitMQ监控:关键技术、技巧与最佳实践
RabbitMQ作为企业级消息中间件的核心组件,其稳定性和性能直接影响分布式系统的可靠性。有效的监控不仅能帮助快速定位问题,还能优化系统资源分配,预防潜在故障。本文基于RabbitMQ官方文档,深入探讨其监控的技术方案、实践技巧及最…...

【机器学习基础】机器学习入门核心算法:隐马尔可夫模型 (HMM)
机器学习入门核心算法:隐马尔可夫模型 (HMM) 一、算法逻辑与核心思想二、算法原理与数学推导核心问题与算法推导 三、模型评估四、应用案例1. 语音识别 (Speech Recognition)2. 自然语言处理 (Natural Language Processing - NLP)3. 手写体识…...
zookeeper 操作总结
zookeeper 中的节点类型 节点类型命令选项说明持久节点无选项(默认)永久存在,除非手动删除。临时节点-e与客户端会话绑定,会话结束自动删除(不能有子节点)。顺序节点-s节点名自动追加递增…...
golang 实现基于redis的并行流量控制(计数锁)
在业务开发中,有时需要对某个操作在整个集群中限制并发度,例如限制大模型对话的并行数。基于redis zset实现计数锁,做个笔记。 关键词:并行流量控制、计数锁 package redisutilimport ("context""fmt""…...

Leetcode 2819. 购买巧克力后的最小相对损失
1.题目基本信息 1.1.题目描述 现给定一个整数数组 prices,表示巧克力的价格;以及一个二维整数数组 queries,其中 queries[i] [ki, mi]。 Alice 和 Bob 去买巧克力,Alice 提出了一种付款方式,而 Bob 同意了。 对于…...

AI炼丹日志-25 - OpenAI 开源的编码助手 Codex 上手指南
点一下关注吧!!!非常感谢!!持续更新!!! Java篇: MyBatis 更新完毕目前开始更新 Spring,一起深入浅出! 大数据篇 300: Hadoop&…...
AnyConv OGG 转换器:轻松转换音频格式
在数字音频世界中,不同的文件格式适用于不同的场景和设备。OGG 是一种开放、免费的音频格式,具有高压缩率和良好的音质。然而,有时我们需要将 OGG 文件转换为其他格式,或者将其他格式转换为 OGG。这就是 AnyConv OGG 转换器发挥作用的地方。 什么是 AnyConv OGG 转换器? …...

C# 类和继承(使用基类的引用)
使用基类的引用 派生类的实例由基类的实例和派生类新增的成员组成。派生类的引用指向整个类对象,包括 基类部分。 如果有一个派生类对象的引用,就可以获取该对象基类部分的引用(使用类型转换运算符把 该引用转换为基类类型)。类…...

进程间通信(消息队列)
目录 一 原理 二 API 1. ftok 2. msgget 3. msgctl 4. msgsnd 5. msgrcv 三 demo代码 四 基于责任链模式和消息队列对数据处理 1. 什么是责任链模式 2. 下面基于责任链模式来对消息队列获取的消息进行处理 前置 其实system v 版本的进程间通信,设计的接…...
Linux gron 命令使用详解
简介 gron 是一个独特的命令行工具,用于将 JSON 数据转换为离散的、易于 grep 处理的赋值语句格式。它的名字来源于 “grepable on” 或 “grepable JSON”,主要解决在命令行中处理复杂 JSON 数据的难题。 核心价值 gron 的核心是将 JSON 数据展平为类…...

Nginx--手写脚本压缩和切分日志(也适用于docker)
原文网址:Nginx--手写脚本压缩和切分日志(也适用于docker)_IT利刃出鞘的博客-CSDN博客 简介 本文介绍nginx如何手写脚本压缩和切分日志。 1.创建切分日志的脚本 创建脚本文件:/work/tmp/nginx-log_sh(后边要用run-…...

OpenCv高阶(十八)——dlib人脸检测与识别
文章目录 一、dlib库是什么?二、opencv库与dlib库的优缺点对比1、opencv优缺点2、dlib库优缺点 三、dlib库的安装1、在线安装2、本地安装 四、dlib库的人脸检测器1. 基于 HOG 的检测器2. 基于 CNN 的检测器 五、dlib人脸检测的简单使用1、导入必要库2、初始化人脸检…...

中山大学无人机具身导航新突破!FlightGPT:迈向通用性和可解释性的无人机视觉语言导航
作者:Hengxing Cai 1 , 2 ^{1,2} 1,2, Jinhan Dong 2 , 3 ^{2,3} 2,3, Jingjun Tan 1 ^{1} 1, Jingcheng Deng 4 ^{4} 4, Sihang Li 2 ^{2} 2, Zhifeng Gao 2 ^{2} 2, Haidong Wang 1 ^{1} 1, Zicheng Su 5 ^{5} 5, Agachai Sumalee 6 ^{6} 6, Renxin Zhong 1 ^{1} …...

WIN11+CUDA11.8+VS2019配置BundleFusion
参考: BundleFusion:VS2019 2017 ,CUDA11.5,win11,Realsense D435i离线数据包跑通,环境搭建 - 知乎 Win10VS2017CUDA10.1环境下配置BundleFusion - 知乎 BundleFusionWIN11VS2019 CUDA11.7环境配置-CSDN博客 我的环境:Win 11…...

WPF prism
Prism Prism.Dryloc 包 安装 Nuget 包 - Prism.DryIoc 1. 修改 App.xaml 修改 App.xaml 文件,添加 prism 命名空间, 继承由 Application → PrismApplication,删除默认启动 url, StartupUri“MainWindow.xaml” <dryioc:PrismApplicationx:Class…...
实时同步缓存,与阶段性同步缓存——补充理解《补充》
根据 Redis 缓存的数据与 DBMS 中数据的同步性划分,缓存一般可划分为两类:实时同步缓存,与阶段性同步缓存。 实时同步缓存是指,DBMS 中数据更新后,Redis 缓存中的存放的相关数据会被立即清 除,以促使再有对…...

[Redis] Redis:高性能内存数据库与分布式架构设计
标题:[Redis] 浅谈分布式系统 水墨不写bug 文章目录 一、什么是Redis?一、核心定位二、核心优势三、典型应用场景四、Redis vs 传统数据库 二、架构选择与设计1、单机架构(应用程序 数据库服务器)2、应用程序和数据库服务器分离3…...
Mobaxterm解锁Docker
Mobaxterm是一款功能强大的终端模拟器和SSH客户端,它支持Windows、Linux和Mac操作系统,对于使用Docker的开发者和运维人员来说,Mobaxterm是一个非常有用的工具。本文将深入解析Mobaxterm,并分享一些使用Docker时的高效技巧。 Mob…...

React 第四十九节 Router中useNavigation的具体使用详解及注意事项
前言 useNavigation 是 React Router 中一个强大的钩子,用于获取当前页面导航的状态信息。 它可以帮助开发者根据导航状态优化用户体验,如显示加载指示器、防止重复提交等。 一、useNavigation核心用途 检测导航状态:判断当前是否正在进行…...

【JavaEE】Spring事务
目录 一、事务简介二、Spring事务的实现2.1 事务的操作2.2 分类2.2.1 Spring编程式事务2.2.2 Spring 声明式事务 Transactional2.2.2.1 Transactional 详解2.2.2.1.1 rollbackFor2.2.2.1.2 Isolation2.2.2.1.3 propagation 一、事务简介 事务:事务是⼀组操作的集合…...