前言
这篇博文给大家分享如何读取key-value
形式的文件内容,并使用map存储。
需求引入
项目中经常用到key-value形式的配置文件,以前用java直接使用properties文件很方便,但在C/C++中,还是需要读取文件获取内容,就去尝试了一下具体如何实现。这里的配置文件内容如下:
#comment row
key1=name #注释
password= 12345
key2 =
=
ip = 8.8.8.8
port = 9999
time = 10#0
key5
n 10
b = 120
文件中加入了注释(#)、空行、无效数据
等内容,以此来模拟非法数据。当然,你也可以使用:
替代=
,作为key-value的分隔符。
处理思路
首先定义了一个读取配置的类ReadConfig
,其中定义了一个readConfig()方法,在该方法中调用handleLine()方法处理每行数据,处理过的每行都是一对key-value,再调用trim()处理key和value前后的空格,trim()方法中调用了定义的判断空格isSpace()方法,最后依次存入map,具体流程如下图:
源码结构
包括类定义readConfig.h
,类实现readConfig.cpp
,主程序testRead.cpp
。
源码
readConfig.h
/*
* readConfig.h
*
* Created on: 2019年11月30日
* Author: xb
*/
#ifndef READCONFIG_H_
#define READCONFIG_H_
#include <map>
#include <string>
#include <iostream>
#define COMMENT '#' //注释符号:#
using std::string;
using std::map;
class ReadConfig {
public:
ReadConfig();
virtual ~ReadConfig();
/**
* 判断空格
*
* @param c 字符
*
* @return true:是
* false:不是
*/
bool isSpace(char c);
/**
* 读取配置文件内容,存入map
*
* @param filename 读取的文件名,可以使用绝对路径或相对路径
* @param m 容器map,存放读取的文件内容
*
* @return true:读取成功
* false:读取失败
*
*/
bool readConfig(const string &filename, map<string, string> &m);
/**
* 去除字符串前后全部空格
*
* @param str 字符串
*
*/
void trim(string &str);
/**
* 处理一行数据,格式为 key = value
* 有#号的行,如果#在行首,忽略该行;如果#不在行首,则读取#号之前的字符串;
* 按'='分割处理完'#'的字符串,切分key和value
*
* @param line 存放按行读取的内容
* @param key 处理过的'='左边的值
* @param value 处理过的'='右边的值
*
* @return true:按行处理成功
* false:按行处理失败
*/
bool handleLine(const string &line, string &key, string &value);
void printMap(const map<string, string> &m);
};
#endif /* READCONFIG_H_ */
readConfig.cpp
/*
* readConfig.cpp
*
* @brief 读取的文件格式如下:
* 1.#注释行...忽略
* 2.key=value √
* 3.key = value √
* 4.key ×
* 5.空行 ×
* 6.key = ×
*
*
* Created on: 2019年11月30日
* Author: xb
*/
#include "../base/readConfig.h"
#include <string.h>
#include <fstream>
#include <errno.h>
using namespace std;
ReadConfig::ReadConfig() {
//cout << "enter default constructor..." << endl;
}
ReadConfig::~ReadConfig() {
//cout << "bye,program is exiting..." << endl;
}
bool ReadConfig::isSpace(char c) {
if (' ' == c || '\t' == c) {
return true;
}
return false;
}
void ReadConfig::trim(string &str) {
if (str.empty()) {
return;
}
unsigned int i, begin, end; //begin:开始位置下标,end结束位置下标
for (i = 0; i < str.size(); i++) {
if (!isSpace(str[i])) {
break;
}
}
if (i == str.size() - 1) {
str = "";
return;
}
begin = i; //确定开始位置
for (i = str.size() - 1; i >= 0; i--) {
if (!isSpace(str[i])) {
break;
}
}
end = i; //确定结束位置
str = str.substr(begin, (end - begin) + 1); //截取字符串,从begin位置开始,截取[(end - begin) + 1]位
}
bool ReadConfig::handleLine(const string &line, string &key, string &value) {
if (line.empty()) {
return false;
}
//begin:字符串起始位置下标;end:该行字符串结束位置下标;position:查找'#'、'='出现位置的下标
int begin = 0, end = line.size() - 1, position;
//如果在行line中找到了#(该行有注释)
if ((position = line.find(COMMENT)) != -1) {
if (0 == position) { //如果在行首找到注释
return false;
}
//注释不在行首
end = position - 1;
}
string new_line = line.substr(begin, end - begin + 1); //删除注释
if ((position = new_line.find('=')) == -1) {
return false; //没有找到'='
}
// key = value,分别截取'='两边的字符串
key = new_line.substr(0, position); //截取'='左边的为key
value = new_line.substr(position + 1, (end + 1) - (position + 1)); //截取'='右边的为value
trim(key);
if (key.empty()) {
return false;
}
trim(value);
if (value.empty()) {
return false;
}
return true;
}
bool ReadConfig::readConfig(const string &filename, map<string, string> &m) {
ifstream infile(filename);
if (!infile) {
printf("open file error:%s(errno:%d)\n",strerror(errno),errno);
return false;
}
string line, key, value;
while (getline(infile, line)) {
if (handleLine(line, key, value)) {
m[key] = value;
}
}
infile.close();
return true;
}
void ReadConfig::printMap(const map<string, string> &m) {
map<string, string>::const_iterator it = m.begin();
for (; it != m.end();it++) {
cout << it->first << ":" << it->second << endl;
}
}
testRead.cpp
/*
* testRead.cpp
*
* Created on: 2019年11月30日
* Author: xb
*/
#include "../base/readConfig.h"
int main() {
using std::cout;
using std::endl;
ReadConfig read;
map<string, string> m;
string filename = "main.cfg";//这里使用了相对路径
cout << "read config from " << filename ;
if (read.readConfig(filename, m)) {
cout << ",get " << m.size() << " rows valid data total:" << endl;
read.printMap(m);
} else {
printf("read config error!\n");
return -1;
}
return 0;
}