QSettings类优化与实现原理探索(主要看效率问题)

最近在研究一个开源项目源码的时候,发现该项目在操作配置文件时并不是直接使用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();
}

以上。

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值