纯C/C++封装的INI文件读写类

// CIniHelper.h Copyallright By DJH5520 2017-10
#ifndef _CINIHELPER_H_ #define _CINIHELPER_H_ #include <unordered_map> // 查找速度O(1) #include <vector> #include <string> #include <fstream> #include <cassert> #include <exception> #include <iterator> #include <algorithm> #include <functional> #include <cstdio> // 键值对结构 struct SKeyValue { std::string m_strKey; std::string m_strValue; }; // Section结构 struct SSection { std::string m_strSectionName; // Section名称 std::vector<SKeyValue> m_kvPairs;// 当前Section下所有键值对,按顺序存储 }; class CIniHelper { private: // 禁止外部构造 CIniHelper() = default; // 禁止拷贝 CIniHelper(const CIniHelper& other) = delete; CIniHelper& operator=(const CIniHelper& other) = delete; ~CIniHelper() { DumpToFile(); } public: /*! * 功能:获取单例对象指针(注:直接采用饿汉模式以保证线程安全) * 参数: 无 * 返回值: 内部创建的单例对象指针 */ static CIniHelper* GetInstance(); /*! * 功能:释放单例对象指针(在任何地方均不需要使用本类时,调用该函数delete创建的单例对象,否则只在程序结束时释放) * 参数: 无 * 返回值: void */ static void FreeInstance(); /*! * 功能:加载ini文件,使用前必须先加载ini * 参数: strPath:ini文件的路径 * 返回值: void */ void LoadIniFile(const std::string& strPath); /*! * 功能:将当前ini内容写入文件 * 参数: void * 返回值: 成功返回0, 失败返回-1 */ int DumpToFile() { return WriteFile(m_strFilePath); } //==================================读写方法,模拟Windows API函数格式=================================// /*! * 功能:按int类型获取指定Section下指定Key的Value值 * 参数: strSecName:指定查找的节点名称 strKeyName:指定查找的Key nDefault:当未找到时,返回的默认值 * 返回值: 返回找到的value值 */ int GetPrivateProfileInt(const std::string& strSecName, const std::string& strKeyName, int nDefault); /*! * 功能:按double类型获取指定Section下指定Key的Value值 * 参数: strSecName:指定查找的节点名称 strKeyName:指定查找的Key dDefault:当未找到时,返回的默认值 * 返回值: 返回找到的value值 */ double GetPrivateProfileDouble(const std::string& strSecName, const std::string& strKeyName, double dDefault); /*! * 功能:按string类型获取指定Section下指定Key的Value值 * 参数: strSecName:指定查找的节点名称 strKeyName:指定查找的Key strDefault:当未找到时,返回的默认值 * 返回值: 返回找到的value值 */ std::string GetPrivateProfileString(const std::string& strSecName, const std::string& strKeyName, const std::string& strDefault); /*! * 功能:获取指定Section下所有的键值对 * 参数: strSecName:指定查找的节点名称 kvPairs:输出参数,用于保存获取到的键值对 * 返回值: 获取成功返回0,失败返回-1 */ int GetPrivateProfileSection(const std::string& strSecName, std::vector<SKeyValue>& kvPairs); /*! * 功能:获取ini文件中所有Section的名称 * 参数: vecSecNames:输出参数,用于保存获取到的名称 * 返回值: 无 */ void GetPrivateProfileSectionNames(std::vector<std::string>& vecSecNames); /*! * 功能:写入或修改指定Section下指定Key的Value值(不存在则写入,已存在则修改value值) * 参数: strSecName:写入或修改的节点名称 strKeyName:写入或修改的键名 strValue:写入或修改的键的值 * 返回值: 成功返回0,失败返回-1 */ int WritePrivateProfileString(const std::string& strSecName, const std::string& strKeyName, const std::string& strValue); private: /// 按行读取文件内容 void ReadFile(const std::string& strPath); /// 按行写入文件 int WriteFile(const std::string& strPath); /// 去除字符串首尾空白 void trim(std::string& str); /// 去除字符串左边空白 void TrimLeft(std::string& str); /// 去除字符串右边空白 void TrimRight(std::string& str); /// 忽略大小写比较字符串 int CompareNocase(const std::string& str1, const std::string& str2); /// 判断是否是Section inline bool IsSection(const std::string& str); /// 判断一行字符串是否是键值对 bool IsKeyValuePairs(const std::string& str); /// 获取所有的Section void GetAllSections(); /// 从键值对行提取键值对,使用前提:调用之前已经判断该行就是键值对 bool GetKeyValuePair(const std::string& strLine, std::string& strKey, std::string& strValue); #if 0 bool GetKeyValue(const std::string& strLine, SKeyValue& kvPair); #endif /// 根据Section和Key查找Value,未找到则返回空串 std::string GetValueString(const std::string& strSecName, const std::string& strKeyName); /// 获取最第一个注释符号的位置(支持注释符号:"//" "#" ";") size_t GetCommentPos(const std::string& strLine); private: /// ini文件路径 std::string m_strFilePath; /// ini文件内容 std::vector<std::string> m_fileContent; #if 0 /// 所有Section,选用vector容器以保留原文顺序,追求效率可以使用map/hash这种容器 std::vector<SSection> m_sections; #endif /// 单例对象指针 static CIniHelper* m_instance; /// 核心成员:<Section, <Key, Value>> unordered_map缺点是无法保持原文顺序,只是查询效率高,需要保持顺序建议用顺序性容器 std::unordered_map<std::string, std::unordered_map<std::string, std::string>> m_allSection; }; #endif
#include "CIniHelper.h"


// 单例对象初始化
CIniHelper* CIniHelper::m_instance = new CIniHelper;

CIniHelper* CIniHelper::GetInstance()
{
    return m_instance;
}

void CIniHelper::FreeInstance()
{
    if (m_instance)
    {
        delete m_instance;
        m_instance = nullptr;
    }
}

void CIniHelper::LoadIniFile(const std::string& strPath)
{
    // 清空所有容器
    // 关于vector容器:
    // clear方法并不会真正释放已经分配的内存,查看其源码可知它只是调用内部元素的析构函数,已分配的内存并不会释放,可以调用capacity查看其容量并未改变
    // 这是vector的内存优化策略,当再次使用该容器时,它只需要调用构造函数对已有的内存初始化即可,vector真正释放内存是在其自身析构时
    // 使用swap将其与一个空的临时vector对象交换,可以将旧的内存转移到临时vector,当临时对象析构时旧的内存就释放了
    //m_fileContent.clear();
    m_fileContent.swap(std::vector<std::string>());

    // map容器的erase和clear时会释放内存的(根据STL源码剖析)
    m_allSection.clear();

    // 读取ini文件内容到容器
    assert(strPath != "");
    m_strFilePath = strPath;
    ReadFile(m_strFilePath);

    // 解析ini中所有Section
    GetAllSections();
}

int CIniHelper::GetPrivateProfileInt(const std::string& strSecName, const std::string& strKeyName, int nDefault)
{
    std::string strValue = GetValueString(strSecName, strKeyName);

    if (strValue.empty())
    {
        return nDefault;
    }

    return std::stoi(strValue);
}


double CIniHelper::GetPrivateProfileDouble(const std::string& strSecName, const std::string& strKeyName, double dDefault)
{
    std::string strValue = GetValueString(strSecName, strKeyName);

    if (strValue.empty())
    {
        return dDefault;
    }

    return std::stod(strValue);
}


std::string CIniHelper::GetPrivateProfileString(const std::string& strSecName,
    const std::string& strKeyName,
    const std::string& strDefault)
{
    std::string strValue = GetValueString(strSecName, strKeyName);

    if (strValue.empty())
    {
        return strDefault;
    }
    else
    {
        return strValue;
    }
}


int CIniHelper::GetPrivateProfileSection(const std::string& strSecName, std::vector<SKeyValue>& kvPairs)
{
    try
    {
        kvPairs.clear();

        // 查找Section下所有的键值对一般需要保持顺序,unordered_map没法用,只能用顺序容器或去原文查找
        std::string strInnerSec = "[" + strSecName + "]";
        auto it = m_fileContent.cbegin();
        for (; it != m_fileContent.cend(); ++it)
        {
            if (IsSection(*it) && (*it) == strInnerSec)
                break; // 找到了
        }

        if (it != m_fileContent.cend())
        {
            // 继续提取该Section下面,直至下一个Section之间的KeyValue
            ++it;
            for (; it != m_fileContent.cend(); ++it)
            {
                if (IsSection(*it))
                {
                    break; // 已到达下一个Section
                }
                else if (IsKeyValuePairs(*it))
                {
                    SKeyValue kv;
                    if (GetKeyValuePair(*it, kv.m_strKey, kv.m_strValue))
                    {
                        kvPairs.emplace_back(kv);
                    }
                }
            }
        }
    }
    catch (const std::exception&)
    {
        return -1;
    }


#if 0
    std::string strInnerSec = "[" + strSecName + "]";
    for (auto iter = m_sections.cbegin(); iter != m_sections.cend(); ++iter)
    {
        auto& sec = *iter;
        if (sec.m_strSectionName == strInnerSec)
        {
            kvPairs = sec.m_kvPairs;
            return 0;
        }
    }
#endif

    return 0;
}


void CIniHelper::GetPrivateProfileSectionNames(std::vector<std::string>& vecSecNames)
{
    vecSecNames.clear();

    // 获取Section一般需要保持顺序,unordered_map没法用,只能用顺序容器原文查找
    for (auto& strLine : m_fileContent)
    {
        if (IsSection(strLine))
        {
            std::string strSec = strLine.substr(1, strLine.length() - 2); // 去除"[" "]"即第一个和最后一个
            trim(strSec); // 去除空白
            vecSecNames.emplace_back(strSec);
        }
    }


#if 0
    for (auto it = m_sections.cbegin(); it != m_sections.cend(); ++it)
    {
        vecSecNames.push_back(it->m_strSectionName);
    }
#endif
}

int CIniHelper::WritePrivateProfileString(const std::string& strSecName, const std::string& strKeyName, const std::string& strValue)
{
    try
    {
        // 写入Section的键值对一般需要保持顺序,unordered_map没法用,只能用顺序容器原文查找
        std::string strInnerSec = "[" + strSecName + "]";

        // 查找Section
        auto it = m_fileContent.begin();
        for (; it != m_fileContent.end(); ++it)
        {
            if (IsSection(*it) && (*it) == strInnerSec)
                break;// 找到了指定的Section
        }

        bool bFindKey = false;
        if (it != m_fileContent.end())
        {// 找到了指定的Section,继续查找Section下面是否存在指定的Key
            ++it; // 跳到下一行
            for (; it != m_fileContent.end(); ++it)
            {
                // 是否是键值对
                if (IsKeyValuePairs(*it))
                {
                    if ((*it).find(strKeyName) != std::string::npos)
                    {// 找到了Key
                        bFindKey = true;

                        // 获取该Key原来的值
                        std::string strOldValue = GetPrivateProfileString(strSecName, strKeyName, "");

                        // 如果一致则不用修改
                        if (strOldValue == strValue) return 0;

                        // 替换旧的value
                        if (strOldValue == "")
                        {// 原来的Value为空
                            (*it) = strKeyName + "=" + strValue;
                        }
                        else
                        {
                            size_t oldValuePos = (*it).find(strOldValue);
                            if (oldValuePos != std::string::npos)
                            {
                                (*it).replace(oldValuePos, strOldValue.length(), strValue); // 替换
                            }
                            else
                            {// 没找到旧的值,直接修改为新的值
                                (*it) = strKeyName + "=" + strValue;
                            }
                        }

                        // 同步更新
                        m_allSection[strInnerSec][strKeyName] = strValue;
                        break; 
                    }
                    continue;
                }
                else if ((bFindKey == false) && ((it + 1) == m_fileContent.end()) && IsSection(*(it + 1)))
                {// 提前检查下一行是不是新的Section,或者已经是最后一行, 如果是 且未找到Key,则证明这是一个新的Key,则将其插入当前Section的最后

                    // 插入这个新的key, insert函数是在指定位置前一个位置插入,所以要加+才表示在当前位置后面插入
                    std::string strPair = strKeyName + "=" + strValue;
                    m_fileContent.insert(it + 1, strPair);
                    // 同步更新
                    m_allSection[strInnerSec][strKeyName] = strValue;
                    break;
                }
            }
        }
        else
        {// 未找到指定Section,则这是一个新的Section,将其添加到文件最后
            m_fileContent.push_back(strInnerSec);
            m_fileContent.push_back(strKeyName + "=" + strValue);

            // 同步更新
            std::unordered_map<std::string, std::string> kv;
            kv.emplace(strKeyName, strValue);
            m_allSection.emplace(strInnerSec, kv);
        }

        // 每次修改都保存文件效率不高,可以考虑使用DumpToFile接口,让用户不再修改时再真正保存到文件,或者析构时写入
        // return WriteFile(m_strFilePath); 
        return 0;
    }
    catch (const std::exception&)
    {
        return -1;
    }
}

//===============================================================================================//
void CIniHelper::ReadFile(const std::string& strPath)
{
    try
    {
        std::ifstream fs;
        fs.open(strPath); 
        std::string strLine;
        // 按行读取文本文件
        while (fs)
        {
            std::getline(fs, strLine);
            m_fileContent.emplace_back(strLine); // 效率比push_back高
        }
        fs.close();
    }
    catch (const std::exception&)
    {
        std::abort();
    }
}


int CIniHelper::WriteFile(const std::string& strPath)
{
    std::string strTempFilePath =  strPath + ".tmp";

    try
    {
        std::ofstream fs;
        // 先将内容写入临时文件,写入成功后替换原文件,防止写入失败时破坏原文件
        fs.open(strTempFilePath);
        auto it = m_fileContent.cbegin();
        for (; it != (m_fileContent.cend() - 1); ++it)
        {
            fs << *it << "\n";
        }
        // 最后一行不加换行符
        fs << *it;

        fs.close();
    }
    catch (const std::exception&)
    {
        return -1;
    }

    std::remove(strPath.c_str()); // 删除原文件
    std::rename(strTempFilePath.c_str(), strPath.c_str()); // 将临时文件重命名

    return 0;
}


void CIniHelper::trim(std::string& str)
{
    if (!str.empty())
    {
        const char strSpace[] = " \t\n\r\f\v"; // 空白字符集
        str.erase(0, str.find_first_not_of(strSpace));
        str.erase(str.find_last_not_of(strSpace) + 1);
    }
}

void CIniHelper::TrimLeft(std::string& str)
{
    if (!str.empty())
    {
        const char strSpace[] = " \t\n\r\f\v"; // 空白字符
        str.erase(0, str.find_first_not_of(strSpace));
    }
}

void CIniHelper::TrimRight(std::string& str)
{
    if (!str.empty())
    {
        const char strSpace[] = " \t\n\r\f\v"; // 空白字符
        str.erase(str.find_last_not_of(strSpace) + 1);
    }
}

int CIniHelper::CompareNocase(const std::string& str1, const std::string& str2)
{
    std::string s1, s2;

    // 均转换成小写后比较
    std::transform(str1.cbegin(), str1.cend(), back_inserter(s1), ::tolower);
    std::transform(str2.cbegin(), str2.cend(), back_inserter(s2), ::tolower);

    return s1.compare(s2);
}

bool CIniHelper::IsSection(const std::string& str)
{
    size_t len = str.length();
    // 一个合法的Section长度最少为3,且第一个和最后一个字符分别是'['和']', 如:[Config]
    if (len >= 3 && str.at(0) == '[' && str.at(len - 1) == ']')
    {
        return true;
    }
    else
        return false;
}

bool CIniHelper::IsKeyValuePairs(const std::string& str)
{
    if (str.length() >= 3 && str.find('=') != std::string::npos)
    {
        return true;
    }
    return false;
}

void CIniHelper::GetAllSections()
{
    // 从容器中获取文件中的每一行文本,并解析
    for (auto& strLine : m_fileContent)
    {
        static std::string SectionName = ""; // 静态变量,用于记住键值对上面的Section名

        // 去除首位空白字符
        trim(strLine);

        // 判断文本内容是否是Section还是Section下的键值对
        if (IsSection(strLine))
        {// Section    
            SectionName = strLine;// 记住Section名,如果该Section存在键值对,则下一行文本的内容就是其键值对
            m_allSection.emplace(SectionName, std::unordered_map<std::string, std::string>()); // Section对应的键值对容器暂时为空
            continue;
        }
        else if (IsKeyValuePairs(strLine))
        {// 键值对
            // 提取Key和Value
            std::string strKey, strValue;
            if (GetKeyValuePair(strLine, strKey, strValue))
            {
                // 将其插入对应的Section下面
                if (!SectionName.empty())
                {
                    auto& kvMap = m_allSection.at(SectionName);
                    kvMap.emplace(strKey, strValue);
                }
            }
        }
    }

#if 0
    // 解析文件每一行,获取键值对
    for (auto iter = m_fileContent.cbegin(); iter != m_fileContent.cend(); ++iter)
    {
        std::string strLine = *iter;

        // 删除一行的前后空白
        trim(strLine);

        // 判断类型
        if (IsSection(strLine))
        {
            // 先尾部插入一个Section
            SSection tmp;
            tmp.m_strSectionName = strLine;
            m_sections.push_back(tmp);
        }
        else if (IsKeyValuePairs(strLine))
        {
            // 如果是键值对,则需要将其放到它对应的Section下面(最后一个插入的那个Section就是,它上面的Section)
            SSection& sec = m_sections.back();
            // 提取Key和Value
            SKeyValue tmp;
            if (GetKeyValue(strLine, tmp))
            {
                sec.m_kvPairs.push_back(tmp);
            }
        }
    }
#endif
}

bool CIniHelper::GetKeyValuePair(const std::string& strLine, std::string& strKey, std::string& strValue)
{
    strKey = "", strValue = "";
    // 提取Key(不能为空)
    size_t posEqual = strLine.find('='); // 找到第一个'='位置
    if (posEqual == 0) return false; // 如果第1个字符就是'='则不是键值对
    strKey = strLine.substr(0, posEqual); // 截取"="前面的字符串作为Key
    // 去除空白
    trim(strKey);
    if (strKey.empty()) return false;

    // 提取value(可以为空)
    // 获取第一个注释符号的位置,目前支持的注释符有 "//" '#' ';'
    size_t posComment = GetCommentPos(strLine);
    if ((posComment > posEqual + 1) && (posComment != std::string::npos)) // 检查注释符位置是否合法
    {
        // 有注释符号,截取"="后面到注释符号之间的内容作为Value
        strValue = strLine.substr(posEqual + 1, posComment - posEqual - 1);
    }
    else
    {
        // 没有注释则直接截取=后面的内容
        strValue = strLine.substr(posEqual + 1);
    }
    trim(strValue);

    return true;
}

#if 0
bool CIniHelper::GetKeyValue(const std::string& strLine, SKeyValue& kvPair)
{
    // 提取Key
    size_t posEqual = strLine.find('='); // 找到第一个'='位置
    if (posEqual == 0) return false; // 如果第一个字符就是'='则非法
    std::string strKey = strLine.substr(0, posEqual);
    // 去除左右空白
    trim(strKey);
    if (strKey.empty()) return false;

    // 提取value
    std::string strValue = "";
    // 获取第一个注释符号的位置
    size_t posComment = GetCommentPos(strLine);
    if ((posComment > posEqual + 1) && (posComment != std::string::npos)) // 检查位置是否合法
    {
        // 有注释符号,截取=后面到注释符号之间的内容
        strValue = strLine.substr(posEqual + 1, posComment - posEqual - 1);
    }
    else
    {
        // 没有注释 截取=后面的内容
        strValue = strLine.substr(posEqual + 1);
    }
    // 去除空白
    trim(strValue);

    kvPair.m_strKey = strKey;
    kvPair.m_strValue = strValue;

    return true;
}
#endif

std::string CIniHelper::GetValueString(const std::string& strSecName, const std::string& strKeyName)
{
    std::string strResult = "";
    std::string strInterSec = "[" + strSecName + "]"; // eg: [Config]

    // 查找对应Section
    auto sec = m_allSection.find(strInterSec);
    if (sec != m_allSection.end())
    {
        // 存在则继续查找Key
        auto& kvPair = sec->second;

        auto it = kvPair.find(strKeyName);
        if (it != kvPair.end())
        {
            // 找到了
            strResult = it->second;
        }
    }

#if 0
    std::string strInterSec = "[" + strSecName + "]";

    // 查找Section
    for (auto iter = m_sections.cbegin(); iter != m_sections.cend(); ++iter)
    {
        auto& sec = *iter;

        // 找到了Section
        if (sec.m_strSectionName == strInterSec)
        {
            // 继续查找Key
            const std::vector<SKeyValue>& kvs = sec.m_kvPairs;
            for (auto it = kvs.cbegin(); it != kvs.cend(); ++it)
            {
                // 找到了Key
                if (strKeyName == (*it).m_strKey)
                {
                    strResult = (*it).m_strValue;
                    break;
                }
            }

            break;
        }
    }
#endif

    return strResult;
}

size_t CIniHelper::GetCommentPos(const std::string& strLine)
{
    size_t spos[3] = { 0 }; // pos[0]表示"//"第一次出现的位置,pos[1]表示"#",pos[2]表示";"第一次出现的位置
    spos[0] = strLine.find("//");
    spos[1] = strLine.find("#");
    spos[2] = strLine.find(";");

    // 取最小值,即最先出现的注释符位置
    size_t* pos = std::min_element(std::begin(spos), std::end(spos));
    return *pos;
}

//=======================================================================================//
#include "CIniHelper.h"
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <ctime>

clock_t g_start = 0, g_end = 0;

int main(int argc, char* argv[]) 
{
#if 1
    g_start = std::clock();
    CIniHelper* pini = CIniHelper::GetInstance();
    g_end = std::clock();
    std::cout << ">>>>>>>>>>>>>>GetInstance() Use Time:" << g_end - g_start << "ms\n"<< std::endl;


    // 首次使用时,必须加载ini文件,最耗时的函数
    g_start = std::clock();
    pini->LoadIniFile("./Config.ini");
    g_end = std::clock();
    std::cout << ">>>>>>>>>>>>>>LoadIniFile() Use Time:" << g_end - g_start << "ms\n" << std::endl;


    // 获取int值
    g_start = std::clock();
    std::cout << "[PN9Test]DummyFrame=" << pini->GetPrivateProfileInt("PN9Test", "DummyFrame", 0) << std::endl;
    g_end = std::clock();
    std::cout << ">>>>>>>>>>>>>>GetPrivateProfileInt() Use Time:" << g_end - g_start << "ms\n" << std::endl;


    // 获取double类型
    g_start = std::clock();
    std::cout << "[Capture_D50]DFOV=" << pini->GetPrivateProfileDouble("Capture_D50", "DFOV", 0) << std::endl;
    g_end = std::clock();
    std::cout << ">>>>>>>>>>>>>>GetPrivateProfileDouble() Use Time:" << g_end - g_start << "ms\n" << std::endl;

    // 获取string
    g_start = std::clock();
    std::cout << "[TestItems]LightDefectPixel=" << pini->GetPrivateProfileString("TestItems", "LightDefectPixel", "") << std::endl;
    g_end = std::clock();
    std::cout << ">>>>>>>>>>>>>>GetPrivateProfileString() Use Time:" << g_end - g_start << "ms\n" << std::endl;

    // 获取整个Section下所有键值对
    std::vector<SKeyValue> kvPairs;
    g_start = std::clock();
    pini->GetPrivateProfileSection("TestItems", kvPairs);
    g_end = std::clock();
    std::cout << "[Section TestItems]" << std::endl;
    for (auto it = kvPairs.begin(); it != kvPairs.end(); ++it)
    {
        std::cout << it->m_strKey << "=" << it->m_strValue << std::endl;
    }
    std::cout << ">>>>>>>>>>>>>>GetPrivateProfileSection() Use Time:" << g_end - g_start << "ms\n" << std::endl;

    // 回写文件
    g_start = std::clock();
    pini->WritePrivateProfileString("Capture_SFR145", "Test_Ver", "10.2.8");
    g_end = std::clock();
    std::cout << ">>>>>>>>>>>>>>WritePrivateProfileString() Use Time:" << g_end - g_start << "ms\n" << std::endl;

    CIniHelper::FreeInstance();
#endif
    system("pause");
    return 0;
}

 

上一篇:可拖动的播放条


下一篇:JavaScript的“继承”