C++基于多设计模式下的同步异步日志系统day3
C++基于多设计模式下的同步&异步日志系统day3
📟作者主页:慢热的陕西人
🌴专栏链接:C++基于多设计模式下的同步&异步日志系统
📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言
主要内容日志项目的输出格式化类的设计和日志落地(LogSink)类设计(简单工厂模式)
文章目录
- C++基于多设计模式下的同步&异步日志系统day3
- 1.代码设计
- 1.1日志输出格式化类设计
- 1.2日志落地(LogSink)类设计(简单工厂模式)
1.代码设计
1.1日志输出格式化类设计
⽇志格式化(Formatter)类主要负责格式化⽇志消息。其主要包含以下内容
-
pattern成员:保存⽇志输出的格式字符串。
◦ %d⽇期
◦ %T缩进
◦ %t线程id
◦ %p⽇志级别
◦ %c⽇志器名称
◦ %f⽂件名
◦ %l⾏号
◦ %m⽇志消息
◦ %n换⾏ -
std::vectorFormatItem::ptritems成员:⽤于按序保存格式化字符串对应的⼦格式化对象
FormatItem
类主要负责⽇志消息⼦项的获取及格式化。其包含以下⼦类
MsgFormatItem:
表⽰要从LogMsg
中取出有效⽇志数据LevelFormatItem:
表⽰要从LogMsg
中取出⽇志等级NameFormatItem:
表⽰要从LogMsg
中取出⽇志器名称ThreadFormatItem:
表⽰要从LogMsg
中取出线程IDTimeFormatItem:
表⽰要从LogMsg
中取出时间戳并按照指定格式进⾏格式化CFileFormatItem:
表⽰要从LogMsg
中取出源码所在⽂件名CLineFormatItem:
表⽰要从LogMsg
中取出源码所在⾏号TabFormatItem:
表⽰⼀个制表符缩进NLineFormatItem:
表⽰⼀个换⾏OtherFormatItem:
表⽰⾮格式化的原始字符串
⽰例:“[%d{%H:%M:%S}]%m%n”
pattern = "[%d{%H:%M:%S}] %m%n"
items = {
{OtherFormatItem(), "["},
{TimeFormatItem(), "%H:%M:%S"},
{OtherFormatItem(), "]"},
{MsgFormatItem (), ""},
{NLineFormatItem (), ""}
}
LogMsg msg = {
size_t _line = 22;
size_t _ctime = 12345678;
std::thread::id _tid = 0x12345678;
std::string _name = "logger";
std::string _file = "main.cpp";
std::string _payload = "创建套接字失败";
LogLevel::value _level = ERROR;
};
格式化的过程其实就是按次序从Msg中取出需要的数据进⾏字符串的连接的过程。
最终组织出来的格式化消息:“[22:32:54]创建套接字失败\n”
代码实现:
#ifndef __M_FMT_H__
#define __M_FMT_H__#include"message.hpp"
#include<time.h>
#include<vector>
#include<cassert>
#include<sstream>namespace xupt
{//抽象格式化子类基类class FormatItem{public:using ptr = std::shared_ptr<FormatItem>;virtual void format(std::ostream& out, const LogMsg &msg) = 0; };//派生格式化子类项---消息,等级,时间,文件名,行号,线程ID,日志器名,制表符,换行,其他class MsgFormatItem : public FormatItem{public:virtual void format(std:: ostream& out, const LogMsg &msg){out << msg._payload;}};class LevelFormatItem : public FormatItem{public:virtual void format(std:: ostream& out, const LogMsg &msg){out << LogLevel::toString(msg._level);}};class TimeFormatItem : public FormatItem{public:TimeFormatItem(const std::string &fmt = "%H:%M:%S"):_time_fmt(fmt){}virtual void format(std:: ostream& out, const LogMsg &msg){struct tm t;localtime_r(&msg._ctime, &t);char tmp[128];strftime(tmp, 127, _time_fmt.c_str(), &t);out << tmp;}private:std::string _time_fmt;}; class FileFormatItem : public FormatItem{public:virtual void format(std:: ostream& out, const LogMsg &msg){out << msg._file;}}; class LineFormatItem : public FormatItem{public:virtual void format(std:: ostream& out, const LogMsg &msg){out << msg._line;}}; class ThreadFormatItem : public FormatItem{public:virtual void format(std:: ostream& out, const LogMsg &msg){out << msg._tid;}}; class LoggerFormatItem : public FormatItem{public:virtual void format(std:: ostream& out, const LogMsg &msg){out << msg._logger;}}; class TabFormatItem : public FormatItem{public:virtual void format(std:: ostream& out, const LogMsg &msg){out << "\t";}}; class NewLineFormatItem : public FormatItem{public:virtual void format(std:: ostream& out, const LogMsg &msg){out << "\n";}}; class OtherFormatItem : public FormatItem{public:OtherFormatItem(const std::string str):_str(str){}virtual void format(std:: ostream& out, const LogMsg &msg){out << _str;}private:std::string _str;}; class Formatter{public:Formatter(const std::string & pattern = "[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n"):_pattern(pattern){assert(parsePattern()); //如果解析失败直接跳断言}//对msg进行格式化void format(std::ostream& out ,const LogMsg & Msg){for(auto& item : _items){item->format(out, Msg);}}std::string format(const LogMsg & Msg){std::stringstream ss;format(ss, Msg);return ss.str();}private://对格式化规则字符串进行解析bool parsePattern(){//1.对格式化规则字符串进行解析//abcd[%d{%H:%M:%S}][%p]%T%m%nstd::vector<std::pair<std::string, std::string>> fmt_order;size_t pos = 0;std::string key, val; while(pos < _pattern.size()){//1.处理原始字符串,如果不是%,那么就是原始字符if(_pattern[pos] != '%'){val.push_back(_pattern[pos++]);continue;}//%的情况//1.双%的情况if(pos + 1 < _pattern.size() && _pattern[pos + 1] == '%'){val.push_back('%'); pos += 2; continue;}//能走到这里,代表%后是一个格式化字符串,这时候代表原始字符串处理完毕if(val.empty() == false){fmt_order.push_back(std::make_pair("", val));val.clear();}//2.%后是格式化字符的情况pos += 1;//这一步之后,pos指向格式化字符位置if(pos == _pattern.size()){std::cout << "%后没有对应的格式化字符" << std::endl;return false;}key = _pattern[pos];//2.{}的情况pos += 1;if(_pattern[pos] == '{'){pos += 1; //这时候pos指向子规则的起始位置while(pos < _pattern.size() && _pattern[pos] != '}'){val.push_back(_pattern[pos++]);}//走到了末尾跳出了循环,则代表没有遇到},代表格式是错误的if(pos == _pattern.size()) //没有找到}{std::cout << "子规则{}匹配失败" << std::endl;return false;}pos += 1;}fmt_order.push_back(std::make_pair(key, val));key.clear();val.clear();}//2.根据解析得到的数据初始化子项数组成员for(auto &it : fmt_order){_items.push_back(createItem(it.first,it.second));}return true;}//根据不同的格式化字符创建不同的格式化子项对象FormatItem::ptr createItem(const std::string & key, const std::string &val){if(key == "d") return std::make_shared<TimeFormatItem>(val);if(key == "t") return std::make_shared<ThreadFormatItem>();if(key == "c") return std::make_shared<LoggerFormatItem>();if(key == "f") return std::make_shared<FileFormatItem>();if(key == "l") return std::make_shared<LineFormatItem>();if(key == "p") return std::make_shared<LevelFormatItem>();if(key == "T") return std::make_shared<TabFormatItem>();if(key == "m") return std::make_shared<MsgFormatItem>();if(key == "n") return std::make_shared<NewLineFormatItem>();if(key.empty()) return std::make_shared<OtherFormatItem>(val);std::cout << "没有对应的格式化字符串:%" << key << std::endl;abort();return FormatItem::ptr();}private:std::string _pattern; //格式化规则字符串std::vector<FormatItem::ptr> _items; };}#endif
1.2日志落地(LogSink)类设计(简单工厂模式)
⽇志落地类主要负责落地⽇志消息到⽬的地
它主要包括以下内容:
- Formatter⽇志格式化器:主要是负责格式化⽇志消息。
- mutex互斥锁:保证多线程⽇志落地过程中的线程安全,避免出现交叉输出的情况。这个类⽀持可扩展,其成员函数log设置为纯虚函数,当我们需要增加⼀个log输出⽬标,可以增加⼀个类继承⾃该类并重写log⽅法实现具体的落地⽇志逻辑。
目前实现了三个不同方向上的日志落地:
-
标准输出:
StdoutSink
-
固定文件:
FileSink
-
滚动文件:
RollSinkBySize
◦ 滚动⽇志⽂件输出的必要性:
• 由于机器磁盘空间有限,我们不可能⼀直⽆限地向⼀个⽂件中增加数据
• 如果⼀个⽇志⽂件体积太⼤,⼀⽅⾯是不好打开,另⼀⽅⾯是即时打开了由于包含数据巨⼤,也不利于查找我们需要的信息
• 所以实际开发中会对单个⽇志⽂件的⼤⼩也会做⼀些控制,即当⼤⼩超过某个⼤⼩时(如1GB),我们就重新创建⼀个新的⽇志⽂件来滚动写⽇志。对于那些过期的⽇志,⼤部分企业内部都有专⻔的运维⼈员去定时清理过期的⽇志,或者设置系统定时任务,定时清理过期⽇志
◦ ⽇志⽂件的滚动思想:
⽇志⽂件滚动的条件有两个:⽂件⼤⼩和时间。我们可以选择:
- ⽇志⽂件在⼤于1GB的时候会更换新的⽂件
- 每天定点滚动⼀个⽇志⽂件
本项⽬基于⽂件⼤⼩的判断滚动⽣成新的⽂件
/* 日志落地模块的实现1.抽象落地基类2.派生落地子类(根据不同的落地方向)3.使用工厂模式进行使用和生产的分离 */#ifndef __M_SIK_H__ #define __M_SIK_H__#include"util.hpp" #include<memory> #include<fstream> #include<cassert> #include<sstream>namespace xupt {class LogSink{public:using ptr = std::shared_ptr<LogSink>;LogSink() {}virtual ~LogSink() {}virtual void log(const char* data, size_t len) = 0; }; //落地方向:标准输出class StdoutSink : public LogSink{public:virtual void log(const char* data, size_t len){std::cout.write(data, len);}};//落地方向:指定文件class FileSink : public LogSink{public:FileSink(const std::string &pathname):_pathname(pathname){//1.创建日志文件所在的目录util::File::CreateDirectory(util::File::path(_pathname));//2.创建并打开日志文件_ofs.open(_pathname, std::ios::binary | std::ios::app); //以二进制追加的方式写入//断言以下是否打开成功assert(_ofs.is_open());}//写入到文件virtual void log(const char* data, size_t len){_ofs.write(data, len);assert(_ofs.good());}private:std::string _pathname;std::ofstream _ofs;};//落地方向:滚动文件(以大小进行滚动)class RollSinkBySize : public LogSink{public://构造函数,传入文件名,文件大小,并管理文件输出句柄RollSinkBySize(const std::string &basename, size_t max_size):_basename(basename), _max_size(max_size),_cur_size(0),_name_count(0){std::string pathname = createNewFile();//1.创建日志文件所在的目录 util::File::CreateDirectory(util::File::path(pathname));//2.创建并打开日志文件_ofs.open(pathname, std::ios::binary | std::ios::app); //以二进制追加的方式写入//断言以下是否打开成功assert(_ofs.is_open()); }//将日志消息写入到文件,写入前判断文件大小,超过了最大大小,就要切换文件virtual void log(const char* data, size_t len){if(_cur_size >= _max_size){//先关闭之前的文件_ofs.close();//创建新的文件std::string pathname = createNewFile();_ofs.open(pathname,std::ios::binary | std::ios::app);//断言以下是否打开成功assert(_ofs.is_open()); //置零当前文件大小_cur_size = 0;}//写入?没有更改_cur_size_ofs.write(data, len);//断言以下是否成功写入assert(_ofs.good()); _cur_size += len; }private://当文件大小达到限制的时候,用于切换文件使用(创建新的文件名,并不能创建文件) std::string createNewFile(){//获取系统时间time_t t = util::Date::now();struct tm lt;localtime_r(&t, <);//创建一个字符串流std::stringstream filename;filename << _basename;filename << lt.tm_year + 1900;filename << lt.tm_mon + 1;filename << lt.tm_mday;filename << lt.tm_hour;filename << lt.tm_min;filename << lt.tm_sec;filename << "-";filename << _name_count++;filename << ".log";return filename.str();}private:size_t _name_count ; //用于区别文件名的std::string _basename; //存储日志信息的文件, 命名规则我们一般是加一个固定的头,尾部用时间来区分std::ofstream _ofs; //维护一个我们的文件输出句柄size_t _max_size; //单个文件的最大大小size_t _cur_size; //当前文件的大小,算是一个优化,这样我们就不用去频繁查看文件属性,从而降低效率};/*拓展一个以时间作为文件滚动切换类型的日志落地模块 */ class RollSinkByTime : public LogSink{public:enum class TimeGap{GAP_SECOND,GAP_MINUTE,GAP_HOUR,GAP_DAY,};public:RollSinkByTime(const std::string &basename, TimeGap gap_type):_basename(basename){switch(gap_type) {case TimeGap::GAP_SECOND : _gap_size = 1; break;case TimeGap::GAP_MINUTE : _gap_size = 60; break;case TimeGap::GAP_HOUR : _gap_size = 3600; break;case TimeGap::GAP_DAY : _gap_size = 3600 * 24; break;}_cur_gap = _gap_size == 1 ? util::Date::now() : util::Date::now() % _gap_size; //初始化当前的时间片util::File::CreateDirectory(util::File::path(_basename)); //1.创建日志文件所在的目录 std::string filename = createNewFile(); //2.创建文件名,并且打开文件_ofs.open(filename,std::ios::binary | std::ios::app);assert(_ofs.is_open()); }//写入到文件,判断当前的时间段,是否是当前文件的时间段,不是则切换文件virtual void log(const char* data, size_t len){//获取系统时间time_t cur = util::Date::now(); if(cur % _gap_size != _cur_gap){_ofs.close();std::string filename = createNewFile(); _ofs.open(filename,std::ios::binary | std::ios::app);assert(_ofs.is_open()); }//写入文件_ofs.write(data, len);assert(_ofs.good());}private://当时间片达到限制的时候,用于切换文件使用,(创建新的文件名,并不能创建文件) std::string createNewFile(){//获取系统时间time_t t = util::Date::now();struct tm lt;localtime_r(&t, <);//创建一个字符串流std::stringstream filename;filename << _basename;filename << lt.tm_year + 1900;filename << lt.tm_mon + 1;filename << lt.tm_mday;filename << lt.tm_hour;filename << lt.tm_min;filename << lt.tm_sec;filename << "-";filename << ".log";return filename.str();}private:std::string _basename;std::ofstream _ofs;size_t _cur_gap;size_t _gap_size;};class SinkFactory{public:template<typename SinkType, typename ...Args>static LogSink::ptr create(Args && ...args) //右值引用,保持原来类型的属性,用于后来的完美转发{return std::make_shared<SinkType>(std::forward<Args>(args)...);}}; }#endif
到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正
相关文章:

C++基于多设计模式下的同步异步日志系统day3
C基于多设计模式下的同步&异步日志系统day3 📟作者主页:慢热的陕西人 🌴专栏链接:C基于多设计模式下的同步&异步日志系统 📣欢迎各位大佬👍点赞🔥关注🚓收藏,&am…...
Cypher语句查询neo4j数据库教程
文章目录 Cypher介绍执行Cypher语句查询总结 Cypher介绍 NodeMatcher和RelationshipMatcher能够表达的匹配条件相对简单,更加复杂的查询还是需要用Cypher语句来表达。 Py2neo本身支持执行Cypher语句的执行,可以将复杂的查询写成Cypher语句,…...

【ESP32 IDF快速入门】点亮第一个LED灯与流水灯
文章目录 前言一、有哪些工作模式?1.1 GPIO的详细介绍1.2 GPIO的内部框图输入模式输出部分 二、GPIO操作函数2.1 GPIO 汇总2.2 GPIO操作函数gpio_config配置引脚reset 引脚函数设置引脚电平选中对应引脚设置引脚的方向 2.3 点亮第一个灯 三、流水灯总结 前言 ESP32…...

再见,Visual Basic——曾经风靡一时的编程语言
2020年3月,微软团队宣布了对Visual Basic(VB)的“终审判决”:不再进行开发或增加新功能。这意味着曾经风光无限的VB正式退出了历史舞台。 VB是微软推出的首款可视化编程软件,自1991年问世以来,便受到了广大…...

【C++精简版回顾】18.文件操作
1.文件操作头文件 2.操作文件所用到的函数 1.文件io 1.头文件 #include<fstream> 2.打开文件 (1)函数名 文件对象.open (2)函数参数 /* ios::out 可读 ios::in 可…...

【解决方案】ArcGIS Engine二次开发时,运行后出现“正尝试在 OS 加载程序锁内执行托管代码。不要尝试在 DllMain...”
我们在做ArcGIS Engine二次开发时,特别是新手,安装好了开发环境,满怀信心的准备将按照教程搭建好的框架在Visual Studio中进行运行。点击运行后,却出现了“正尝试在 OS 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化…...

新项目,Linux上一键安装MySQL,Redis,Nacos,Minio
大家好,我是 jonssonyan 分享一个我的一个开源项目,这是一个在 Linux 平台上一键安装各种软件的脚本项目,脚本使用 Shell 语言编写,后续还会增加更多软件的一键安装,代码在 GitHub 上全部开源的,开源地址如…...
Rust 从 PyTorch 到 Burn
一、性能轮盘赌 机器码相同,但放置在不同的地址上,性能可能截然不同。 作为软件开发人员,我们经常假设特定代码的性能仅由代码本身和运行它的硬件决定。这种假设让我们在优化代码以获得更好性能时感到有控制力。虽然在大多数情况下这种假设…...
Swin-Transformer网络代码实现
还是参考导师级别博主霹雳吧啦Wz的个人空间-霹雳吧啦Wz个人主页-哔哩哔哩视频 博主写的博客Swin-Transformer网络结构详解_swin transformer-CSDN博客 视频理论讲解12.1 Swin-Transformer网络结构详解_哔哩哔哩_bilibili pytorch实现12.2 使用Pytorch搭建Swin-Transformer网…...

Java ZooKeeper-RocketMQ 面试题
Java ZooKeeper-RocketMQ 面试题 前言1、谈谈你对ZooKeeper的理解 ?2、Zookeeper的工作原理(Zab协议)3、谈谈你对分布式锁的理解,以及分布式锁的实现?4、 zookeeper 是如何保证事务的顺序一致性的?5、 zook…...
css制作瀑布流布局
CSS制作瀑布流布局的步骤如下: HTML结构:使用无序列表ul和列表项li来创建网格布局。 <ul class"grid"><li><img src"image1.jpg"></li><li><img src"image2.jpg"></li><li…...
Redis 的哨兵模式配置
1.配置 vim sentinel.conf# mymaster 给主机起的名字 # 192.168.205.128 主机的ip地址 # 6379 端口号 # 2 当几个哨兵发现主观宕机,则判定为客观宕机。 原则上是大于一半。比如三个哨兵,则设置为 2 sentinel monitor mymaster 192.168.205.128 63…...
基于单片机的继电器参数测试系统设计
摘要:由于原有测试系统在参数设置上过于单一,在对继电器测试过程中需要多次进行器件连接,导致对其测试准确度下降,会造成继电器的二次损伤,基于单片机研究继电器参数测试系统的设计方法。在硬件设计上,构建二级模式集散控制框架,利用单片机进行数据采集和处理,满足实时…...
unity 数学 空间四个点是否在同一个平面
问题:已知三维空间中四点A、B、C、D,如何知道四个点是否在同一个平面呢 首先我们知道三点确定一个平面,所以可以由上面四个点其中任意三点组成一个平面p(A,B,C),另外一个点和三个任意点的形成线࿰…...

数据卷dockerfile
目录 一、数据卷 1. 简介 2. 数据卷和数据卷容器 1. 数据卷: 2. 数据卷容器: 二、自定义镜像 1. 作用 2. 自定义centos 3. 自定义tomcat8 一、数据卷 1. 简介 数据卷是一个可供一个或多个容器使用的特殊目录,它将主机操作系统目录直…...

AOP的介绍与使用
文章目录 AOP的概念AOP术语AOP的实现AspectJSpring AOP Spring AOP原理JDK动态代理CGLib动态代理 SpringAOP代码编写规则自定义切面自定义切点自定义通知在通知中获取当前请求代码实例 一些选择题 AOP的概念 • Aspect Oriented Programing,即面向方面(…...

金融行业专题|期货超融合架构转型与场景探索合集(2023版)
更新内容: 更新 SmartX 超融合在期货行业的覆盖范围、部署规模与应用场景。新增 CTP 主席系统实践与评测、容器云资源池等场景实践。更多超融合金融核心生产业务场景实践,欢迎下载阅读电子书《SmartX 金融核心生产业务场景探索文章合集》。 面对不断变…...

08 yum和git
什么是软件包 安装软件,一个通常的办法就是下载程序的源代码进行编译。这种太麻烦,于是一些人把常用软件编译好,做成软件包放在服务器上,通过包管理器可以很方便的得到这个软件包安装,就好比手机上的应用商店 yum&am…...
JMeter元件和采样器一览
Apache JMeter是一个强大的开源负载测试工具,用于性能和功能测试。JMeter提供了丰富的元件和采样器,使得它能够模拟复杂的测试场景和高并发的用户请求。以下是JMeter中常用的一些元件和采样器的介绍和讲解: 测试计划元件 测试计划࿰…...
BF算法的优化之SPFA算法
介绍 全称Shortest Path Faster Algorithm. 优化思想: 1.由int path[maxn]定义的记录最短距离的容器,只有在path[i]value<path[j]时才会更新,它们两者的值相等时path的值仍保持不变。由此优化容器,选择用一个队列来替path数…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...