// 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 << "
";
}
// 最后一行不加换行符
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[] = "
fv"; // 空白字符集
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[] = "
fv"; // 空白字符
str.erase(0, str.find_first_not_of(strSpace));
}
}
void CIniHelper::TrimRight(std::string& str)
{
if (!str.empty())
{
const char strSpace[] = "
fv"; // 空白字符
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
"<< 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
" << 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
" << 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
" << 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
" << 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
" << 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
" << std::endl;
CIniHelper::FreeInstance();
#endif
system("pause");
return 0;
}