最近在研究一个开源项目源码的时候,发现该项目在操作配置文件时并不是直接使用QT提供的配置文件操作类QSettings,而是在QSettings的基础上又进行了一层封装,故此对QSetting源码做一番探究,虽然离完全搞清楚还差的太远,但终究值得记录一下。
QSettings实现原理初探
总体实现
我看的是Qt5.12.2的源码,具体源文件路径为Qt5.12.2/5.12.2/Src/qtbase/src/corelib/io,包含以下几个文件:
qsettings.cpp
qsettings.h
qsettings_mac.cpp
qsettings_p.h
qsettings_win.cpp
qsettings_winrt.cpp
我们主要关注文件qsettings_p.h中的类QConfFileSettingsPrivate
QSettings类创建QSettingsPrivate类并保存在指针d_ptr:
QSettings::QSettings(const QString &organization, const QString &application)
: d_ptr(QSettingsPrivate::create(globalDefaultFormat, QSettings::UserScope, organization, application))
{
d_ptr->q_ptr = this;
}
QSettings::QSettings(Scope scope, const QString &organization, const QString &application)
: d_ptr(QSettingsPrivate::create(globalDefaultFormat, scope, organization, application))
{
d_ptr->q_ptr = this;
}
QSettings::QSettings(Format format, Scope scope, const QString &organization,
const QString &application)
: d_ptr(QSettingsPrivate::create(format, scope, organization, application))
{
d_ptr->q_ptr = this;
}
QSettings::QSettings(const QString &fileName, Format format)
: d_ptr(QSettingsPrivate::create(fileName, format))
{
d_ptr->q_ptr = this;
}
QSettingsPrivate的create方法创建QConfFileSettingsPrivate类并保存在q_ptr:
#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope,
const QString &organization, const QString &application)
{
return new QConfFileSettingsPrivate(format, scope, organization, application);
}
#endif
#if !defined(Q_OS_WIN)
QSettingsPrivate *QSettingsPrivate::create(const QString &fileName, QSettings::Format format)
{
return new QConfFileSettingsPrivate(fileName, format);
}
#endif
QConfFileSettingsPrivate类有个叫QVector<QConfFile *> confFiles;的成员变量,这个是最终对配置文件进行操作的地方。
在这个类里面有四个比较重要的成员变量:
UnparsedSettingsMap unparsedIniSections; //保存从配置文件读取的最原始的数据
ParsedSettingsMap originalKeys; //保存从unparsedIniSections中解析出来的
ParsedSettingsMap addedKeys; //保存新增的配置项
ParsedSettingsMap removedKeys; //保存待删除的配置项
这四个类的数据结构定义如下:
typedef QMap<QSettingsKey, QByteArray> UnparsedSettingsMap;
typedef QMap<QSettingsKey, QVariant> ParsedSettingsMap;
未经解析的键值用Qytearray保存原始数据,经过解析的键值使用QVariant保存。
setValue实现
设置键值时setValue方法调用QSettingsPrivate的set方法:
d->set(k, value);
set方法先移除对应值,再插入新的值:
confFile->removedKeys.remove(theKey);
confFile->addedKeys.insert(theKey, value);
然后调用QSettingsPrivate的requestUpdate方法请求更新:
d->requestUpdate();
requestUpdate方法向QSettings类发送一个UpdateRequest事件:
QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest));
QSettings在事件处理函数event()中调用QSettingsPrivate的update方法进行数据更新:
d->update();
QSettingsPrivate的update方法最终由QConfFileSettingsPrivate::syncConfFile来实现。
syncConfFile方法首先对addedKeys,removedKeys进行了处理,然后读取了新的unparsedIniSections,接着对addedKeys和removedKeys运算得到了增删后的mergedKeys,将增删后的mergedKeys写入真实的配置文件。
value实现
获取键值时,value方法调用QSettingsPrivate的get方法:
d->get(k, &result);
get方法首先搜索新增的配置项是否包含此键值,再搜索原始键值表originalKeys是否含有此配置项。
remove实现
remove首先查询了新增的键值列表addedKeys,接着查询了原始键值列表originalKeys。
contain实现
contain以空指针作为保存键值的内存调用了QSettingsPrivate的get方法:
return d->get(k, 0);
get方法原型为:
get(const QString &key, QVariant *value) const
get方法首先查询了新增的键值列表addedKeys,接着查询了原始键值列表originalKeys。
QSettings的效率优化
QSettings存在的问题
由上可知,QSettings主要通过管理QMap来管理配置文件中的键值对,每次增删键值对后都先暂存在QMap,然后再进行同步操作讲暂存的内容刷新入实际配置文件,也就是说每次键值有修改都要执行以下步骤:
打开文件->读取全部内容->解析全部内容->运算得新的键值对列表->写入文件并修改时间戳。(详见void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile))
频繁的文件读写操作无疑会增加系统负担,这不是我们想看到的。
编程过程中发现的QSettings的不足
1.在大量的配置项中,每一个配置项访问的频率是不均等的,有一些配置项我们需要经常性的读写,那么使用原有的value方法将会先后查找多个QMap,在所有配置项中得到我们想要的值。但是,我希望它可以在高频使用的配置项中优先查找,查找失败再到全部配置项中查找。为此我们经常在类中创建多个成员变量暂存这些高频使用的配置项,我手好累。。。
2.不是每一次我们修改配置文件都希望把数值写入真实的配置文件,有时只是拿来当标志位用一下。但QSettings的setValue方法却结结实实的写入了实际的配置文件中,浪费啊。
解决方法
在内存中建立本地缓存,保存高频使用的配置项;使用unordered_map加快查询速度。具体实现:
Settings.h
class Settings
{
public:
static QVariant getValue(const std::string& group, const std::string& name);
static void setValue(const std::string& group, const std::string& name, const QVariant& value, bool dont_save_to_disk = false);
static void restoreDefaults();
private:
Settings() = delete; // class is fully static
// This works similar to getValue but returns the default value instead of the value set by the user
static QVariant getDefaultValue(const std::string& group, const std::string& name);
// Cache for storing the settings to avoid repeatedly reading the settings file all the time
static std::unordered_map<std::string, QVariant> m_hCache;
};
Settings.cpp
QVariant Settings::getValue(const std::string& group, const std::string& name)
{
// Have a look in the cache first
auto cacheIndex = m_hCache.find(group + name);
if(cacheIndex != m_hCache.end())
{
return cacheIndex->second;
} else {
// Nothing found in the cache, so get the value from the settings file or get the default value if there is no value set yet
QSettings settings(QApplication::organizationName(), QApplication::organizationName());
QVariant value = settings.value(QString::fromStdString(group + "/" + name), getDefaultValue(group, name));
// Store this value in the cache for further usage and return it afterwards
m_hCache.insert({group + name, value});
return value;
}
}
void Settings::setValue(const std::string& group, const std::string& name, const QVariant& value, bool dont_save_to_disk)
{
// Sometime the value has to be saved for the current session only but get discarded when the application exits.
// In order to achieve this this flag can be set which disables the save to disk mechanism and only leaves the save to cache part active.
if(dont_save_to_disk == false)
{
// Set the group and save the given value
QSettings settings(QApplication::organizationName(), QApplication::organizationName());
settings.beginGroup(QString::fromStdString(group));
settings.setValue(QString::fromStdString(name), value);
settings.endGroup();
}
// Also change it in the cache
m_hCache[group + name] = value;
}
QVariant Settings::getDefaultValue(const std::string& group, const std::string& name)
{
// db/defaultencoding?
if(group == "db" && name == "defaultencoding")
return "UTF-8";
return QVariant();
}
void Settings::restoreDefaults ()
{
QSettings settings(QApplication::organizationName(), QApplication::organizationName());
settings.clear();
m_hCache.clear();
}
以上。