// 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; }