news 2026/4/22 6:41:48

【Qt 开发笔记】能扛住断电、多线程的通用配置类(移植直接用)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Qt 开发笔记】能扛住断电、多线程的通用配置类(移植直接用)

做上位机和工控软件久了会发现,配置文件看着简单,坑却特别多。
程序写一半突然断电、多线程同时读写、异常退出,都能把配置文件搞坏,轻则参数丢失,重则软件直接起不来。

为了以后新项目移植不用重复造轮子,我把自己一直在用的配置管理类整理了一下,自带线程安全、原子写入、自动备份,INI/JSON/XML 都能用,基本能应对大部分现场稳定运行的需求。


一、为什么要自己写一个配置类?

Qt 自带的 QSettings 其实能用,但有几个硬伤很容易在现场出问题:

  1. 直接写原文件,写一半断电直接损坏,没有备份
  2. 多线程读写不加锁很容易乱
  3. 文件损坏了不会自动恢复,软件直接卡死
  4. 切换格式要改一堆代码,不方便移植

所以我自己封装了一层,保证:

  • 怎么读写都不会把配置写崩
  • 坏了能自动从备份恢复
  • 多线程同时操作也安全
  • 换 INI、JSON、XML 几乎不用改业务代码

二、完整代码(可直接复制进项目)

#include<QObject>#include<QSettings>#include<QFile>#include<QDir>#include<QReadWriteLock>#include<QDebug>#include<QJsonDocument>#include<QJsonObject>#include<QJsonValue>#include<QXmlStreamWriter>#include<QXmlStreamReader>#include<QVector>#include<QPair>// 配置文件格式,三种常用的都支持enumclassConfigFormat{INI,JSON,XML};classConfigManager:publicQObject{Q_OBJECTpublic:// 单例,全局只用一个实例,避免多处打开文件staticConfigManager*instance(){staticConfigManager*inst=nullptr;if(!inst){inst=newConfigManager();}returninst;}// 初始化:指定文件路径 + 格式// 程序启动时调用一次就行voidinit(constQString&filename,ConfigFormat format){m_filename=filename;m_format=format;checkBackup();// 一启动先检查配置坏没坏}// 读配置,带默认值,没有 key 也不会崩QVariantgetValue(constQString&key,constQVariant&defaultValue=QVariant()){QReadLockerlocker(&m_lock);// 读加锁,允许多线程同时读if(m_format==ConfigFormat::INI){QSettingsset(m_filename,QSettings::IniFormat);returnset.value(key,defaultValue);}elseif(m_format==ConfigFormat::JSON){returngetJsonValue(key,defaultValue);}elseif(m_format==ConfigFormat::XML){returngetXmlValue(key,defaultValue);}returndefaultValue;}// 写配置,自动加锁、原子写入、备份voidsetValue(constQString&key,constQVariant&value){QWriteLockerlocker(&m_lock);// 写加锁,同一时间只能一个线程写if(m_format==ConfigFormat::INI){QSettingsset(m_filename,QSettings::IniFormat);set.setValue(key,value);set.sync();backup();// 写完顺手备个份}elseif(m_format==ConfigFormat::JSON){setJsonValue(key,value);}elseif(m_format==ConfigFormat::XML){setXmlValue(key,value);}}// 检查配置文件是否损坏,坏了自动从 bak 恢复voidcheckBackup(){QFilef(m_filename);// 文件不存在或者解析失败,都视为损坏if(!f.exists()||isFileCorrupted()){QFilefbak(m_filename+".bak");if(fbak.exists()){fbak.copy(m_filename);qInfo()<<"配置文件损坏,已自动从备份恢复:"<<m_filename;}else{qWarning()<<"配置文件不存在,创建一个新的空文件:"<<m_filename;f.open(QIODevice::WriteOnly);f.close();}}}// 手动备份,一般不用自己调,setValue 里会自动调用voidbackup(){QFile::remove(m_filename+".bak");QFile::copy(m_filename,m_filename+".bak");}private:ConfigManager()=default;// 判断文件是不是坏了boolisFileCorrupted(){QFilef(m_filename);if(!f.open(QIODevice::ReadOnly)){returntrue;}boolcorrupted=false;if(m_format==ConfigFormat::JSON){QJsonParseError e;QJsonDocument::fromJson(f.readAll(),&e);corrupted=(e.error!=QJsonParseError::NoError);}elseif(m_format==ConfigFormat::XML){QXmlStreamReaderr(&f);while(!r.atEnd()){r.readNext();}corrupted=r.hasError();}f.close();returncorrupted;}// ==================== JSON 读写 ====================QVariantgetJsonValue(constQString&key,constQVariant&def){QFilef(m_filename);if(!f.open(QIODevice::ReadOnly)){returndef;}QJsonDocument doc=QJsonDocument::fromJson(f.readAll());f.close();returndoc.object().value(key).toVariant(def);}voidsetJsonValue(constQString&key,constQVariant&val){QJsonObject obj;if(QFile(m_filename).exists()){QFilef(m_filename);if(f.open(QIODevice::ReadOnly)){obj=QJsonDocument::fromJson(f.readAll()).object();f.close();}}obj.insert(key,QJsonValue::fromVariant(val));// 关键点:先写临时文件,再替换,防止写一半断电损坏QString tmpName=m_filename+".tmp";QFileftmp(tmpName);if(ftmp.open(QIODevice::WriteOnly)){ftmp.write(QJsonDocument(obj).toJson());ftmp.close();QFile::remove(m_filename);QFile::rename(tmpName,m_filename);}backup();}// ==================== XML 读写 ====================QVariantgetXmlValue(constQString&key,constQVariant&def){QFilef(m_filename);if(!f.open(QIODevice::ReadOnly)){returndef;}QXmlStreamReaderr(&f);QString value;while(!r.atEnd()){if(r.readNext()==QXmlStreamReader::StartElement&&r.name()==key){value=r.readElementText();break;}}f.close();returnvalue.isEmpty()?def:value;}voidsetXmlValue(constQString&key,constQVariant&val){QVector<QPair<QString,QString>>nodes;if(QFile(m_filename).exists()){QFilef(m_filename);if(f.open(QIODevice::ReadOnly)){QXmlStreamReaderr(&f);while(!r.atEnd()){if(r.readNext()==QXmlStreamReader::StartElement&&!r.name().isEmpty()){nodes.append({r.name().toString(),r.readElementText()});}}f.close();}}// 存在就更新,不存在就追加boolfound=false;for(auto&node:nodes){if(node.first==key){node.second=val.toString();found=true;break;}}if(!found){nodes.append({key,val.toString()});}// 同样用临时文件保证安全写入QString tmpName=m_filename+".tmp";QFileftmp(tmpName);if(ftmp.open(QIODevice::WriteOnly)){QXmlStreamWriterw(&ftmp);w.setAutoFormatting(true);w.writeStartDocument();w.writeStartElement("config");for(auto&node:nodes){w.writeTextElement(node.first,node.second);}w.writeEndElement();w.writeEndDocument();ftmp.close();QFile::remove(m_filename);QFile::rename(tmpName,m_filename);}backup();}private:QString m_filename;ConfigFormat m_format;QReadWriteLock m_lock;// 读写锁,多线程安全核心};

三、实际使用示例(非常简单)

1. 程序启动时初始化

main函数里 early init 一下就行:

// INI 格式ConfigManager::instance()->init("config.ini",ConfigFormat::INI);// JSON 格式// ConfigManager::instance()->init("config.json", ConfigFormat::JSON);

2. 读参数

// 读串口配置,没有就用默认值,不会崩溃QString port=ConfigManager::instance()->getValue("Serial/Port","COM1").toString();intbaud=ConfigManager::instance()->getValue("Serial/Baud",115200).toInt();

3. 写参数

ConfigManager::instance()->setValue("Serial/Port","COM10");ConfigManager::instance()->setValue("Serial/Baud",9600);

内部会自动:加锁 → 安全写入 → 备份,不用你管。


四、几个关键设计思路(方便你以后自己改)

1. 读写锁 QReadWriteLock

  • 读操作可以并发,效率高
  • 写操作独占,不会出现一边读一边写乱掉
    上位机多线程、串口线程、UI线程同时操作配置也不会崩。

2. 原子写入(先写 tmp 再替换)

这是防断电、防异常退出最关键的一步。
不直接覆盖原文件,而是:

  1. 写到.tmp
  2. 写完再替换原文件
    哪怕中途断电、死机,最多坏临时文件,原来的配置完好无损。

3. 自动备份 + 自动恢复

每次写入都会生成.bak
启动时自动检查:

  • 格式错误
  • 文件为空
  • 文件不存在
    都会自动从备份恢复,现场不会因为配置丢了就打不开软件。

4. 统一接口,方便移植

不管用 INI、JSON 还是 XML,业务代码都不用改,只换一个枚举就行,老项目升级、新项目重构都很方便。


五、适合哪些项目?

我自己主要用在:

  • 医疗设备上位机
  • 工业串口 / 网口控制软件
  • 需要长期挂机运行的客户端
  • 多线程读写配置比较频繁的程序

只要你怕配置文件损坏、怕现场出问题,这个类基本都能扛住。


六、小结

这个 ConfigManager 是我从多个实际项目里抽出来的通用组件,没有花哨结构,就是稳定、抗造、好移植。
以后开新项目直接拖进去,初始化一行,读写两行,不用再纠结配置损坏、线程安全这些破事,可以把精力专心写业务逻辑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/11 18:55:11

设计剧本杀门店剧本版权,按月摊销简易账务实操方案。

【Python 实战】剧本杀门店剧本版权按月摊销账务系统标签&#xff1a;Python / 智能会计 / 剧本杀行业 / 无形资产摊销 / 实战项目前言&#xff1a;为什么我要写这个&#xff1f;在给一家剧本杀连锁店做财务咨询时&#xff0c;我发现一个非常典型的问题&#xff1a;❌ 剧本买来…

作者头像 李华
网站建设 2026/4/22 6:41:06

GetQzonehistory终极指南:永久备份QQ空间说说的完整解决方案

GetQzonehistory终极指南&#xff1a;永久备份QQ空间说说的完整解决方案 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 在数字时代&#xff0c;我们的青春记忆大多存储在社交平台中&am…

作者头像 李华
网站建设 2026/4/22 6:41:48

PHP exec()函数埋的坑:深入理解命令注入漏洞的原理与防御

PHP命令注入漏洞深度解析&#xff1a;从CTF到真实世界的安全防御 在2020年的ACTF新生赛中&#xff0c;一道名为"Exec"的题目让众多参赛者首次直面Web安全中最危险的漏洞类型之一——命令注入。这道看似简单的PING功能测试题&#xff0c;背后隐藏着PHP开发中常见的安全…

作者头像 李华
网站建设 2026/4/11 18:51:11

【12.MyBatis源码剖析与架构实战】10.2 ⼆级缓存存取流程剖析-案例

MyBatis 的缓存机制是其性能优化的关键模块之一,通过减少对数据库的直接访问来大幅提升查询效率。在深入二级缓存之前,理解整个缓存体系的基础(一级缓存)以及支撑它的核心源码至关重要。 🔍 缓存体系概览 MyBatis 提供两级缓存,均基于 Cache 接口实现。 一级缓存 (Loc…

作者头像 李华