处理大文本文件
Working with big text files
我有一个格式如下的文件:
[1]
Parameter1=Value1
.
.
.
End
[2]
.
.
括号中的数字表示实体的id。大约有 4500 个实体。我需要解析所有实体并选择与我的参数和值匹配的实体。文件大小约为 20mb。我的第一种方法是逐行读取文件并将它们存储在结构数组中,例如:
struct Component{
std::string parameter;
std::string value;
};
struct Entity{
std::string id;
std::list<Component> components;
};
std::list<Entity> g_entities;
但是这种方法占用大量内存并且非常慢。我也试过只存储与我的 parameters/values 匹配的那些。但这也真的很慢并且占用了很多内存。理想情况下,我想将所有数据存储在内存中,这样我就不必在每次需要过滤我的文件时都加载文件 parameters/values 如果可能的话,合理的内存使用量。
编辑 1:
我逐行读取文件:
std::ifstream readTemp(filePath);
std::stringstream dataStream;
dataStream << readTemp.rdbuf();
readTemp.close();
while (std::getline(dataStream, line)){
if (line.find('[') != std::string::npos){
// Create Entity
Entity entity;
// Set entity id
entity.id = line.substr(line.find('[') + 1, line.find(']') - 1);
// Read all lines until EnumEnd=0
while (1){
std::getline(dataStream, line);
// Break loop if end of entity
if (line.find("EnumEnd=0") != std::string::npos){
if (CheckMatch(entity))
entities.push_back(entity);
entity.components.clear();
break;
}
Component comp;
int pos_eq = line.find('=');
comp.parameterId = line.substr(0, pos_eq);
comp.value = line.substr(pos_eq + 1);
entity.components.push_back(comp);
}
}
}
您可以通过 interning 您的参数和值使用更少的内存,以免存储它们的多个副本。
您可以将字符串映射到唯一数字 ID,这是您在加载文件时创建的,然后在查询数据结构时只使用这些 ID。以最初可能较慢的解析为代价,之后使用这些结构应该会更快,因为您只会匹配 32 位整数而不是比较字符串。
将每个字符串存储一次的粗略概念证明:
#include <unordered_map>
#include <string>
#include <iostream>
using namespace std;
int string_id(const string& s) {
static unordered_map<string, int> m;
static int id = 0;
auto it = m.find(s);
if (it == m.end()) {
m[s] = ++id;
return id;
} else {
return it->second;
}
}
int main() {
// prints 1 2 2 1
cout << string_id("hello") << " ";
cout << string_id("world") << " ";
cout << string_id("world") << " ";
cout << string_id("hello") << endl;
}
unordered_map
最终会将每个字符串存储一次,因此您已准备好记忆。根据你的匹配函数,你可以定义
struct Component {
int parameter;
int value;
};
然后您的匹配可以是 myComponent.parameter == string_id("some_key")
甚至 myComponent.parameter == some_stored_string_id
。如果你想要你的字符串,你也需要一个反向映射。
PS:编辑后。和关于内存消耗的评论
500MB / 20MB = 25.
如果每行是 25 个字符,内存消耗看起来还可以。
好的,您可以使用查找 table 将参数名称映射到数字。
如果names-set很小,这样最多可以节省2倍的消耗。
您的数据结构可能如下所示:
std::map<int, std::map<int, std::string> > my_ini_file_data;
std::map<std::string, int> param_to_idx;
(前提是部分(您称之为实体)中的参数名称不是唯一的)
放数据是:
std::string param = "Param";
std::string value = "Val";
int entity_id = 0;
if ( param_to_idx.find(param) == param_to_idx.end() )
param_to_idx[param] = param_to_idx.size();
my_ini_file_data[entity_id][ param_to_idx[param] ] = value;
获取数据是:
value = my_ini_file_data[entity_id][ param_to_idx[param] ];
如果值集也远小于条目数,
您甚至可以将值映射到数字:
std::map<int, std::map<int, int> > my_ini_file_data;
std::map<std::string, int> param_to_idx;
std::map<std::string, int> value_to_idx;
std::map<int, std::string> idx_to_value;
放数据是:
std::string param = "Param";
std::string value = "Val";
int entity_id = 0;
if ( param_to_idx.find(param) == param_to_idx.end() )
param_to_idx[param] = param_to_idx.size();
if ( value_to_idx.find(value) == value_to_idx.end() )
{
int idx = value_to_idx.size();
value_to_idx[value] = idx;
idx_to_value[idx] = value;
}
my_ini_file_data[entity_id][ param_to_idx[param] ] = value_to_idx[value];
获取数据是:
value = idx_to_value[my_ini_file_data[entity_id][ param_to_idx[param] ] ];
希望,这对您有所帮助。
初步回答
关于内存,我不会关心,除非你有一种内存非常小的嵌入式系统。
关于速度,我可以给你一些建议:
找出瓶颈是什么。
- 使用std::list!每次向量增长时,使用 std::vector 重新初始化内存。如果由于某种原因你最后需要一个向量,创建一个保留所需条目数的向量,你可以通过调用 list::size()
获得
- 写一个 while 循环,在那里你只调用 getline。如果仅此一项
已经很慢了,一次读取整个块,创建一个 reader-stream
从 char* 块中取出并从流中逐行读取。
如果简单阅读的速度还可以,优化你的解析代码。你
可以通过存储位置来减少查找调用的次数。例如
int pos_begin = line.find('[]');
if (pos_begin != std::string::npos){
int pos_end = line.find(']');
if (pos_end != std::string::npos){
entity.id = line.substr(pos_begin + 1, pos_begin - 1);
// Read all lines until EnumEnd=0
while (1){
std::getline(readTemp, line);
// Break loop if end of entity
if (line.find("EnumEnd=0") != std::string::npos){
if (CheckMatch(entity))
entities.push_back(entity);
break;
}
Component comp;
int pos_eq = line.find('=');
comp.parameter= line.substr(0, pos_eq);
comp.value = line.substr(pos_eq + 1);
entity.components.push_back(comp);
}
}
}
- 根据实体的大小,检查 CheckMatch 是否慢。实体越小,代码越慢 - 在这种情况下。
我有一个格式如下的文件:
[1]
Parameter1=Value1
.
.
.
End
[2]
.
.
括号中的数字表示实体的id。大约有 4500 个实体。我需要解析所有实体并选择与我的参数和值匹配的实体。文件大小约为 20mb。我的第一种方法是逐行读取文件并将它们存储在结构数组中,例如:
struct Component{
std::string parameter;
std::string value;
};
struct Entity{
std::string id;
std::list<Component> components;
};
std::list<Entity> g_entities;
但是这种方法占用大量内存并且非常慢。我也试过只存储与我的 parameters/values 匹配的那些。但这也真的很慢并且占用了很多内存。理想情况下,我想将所有数据存储在内存中,这样我就不必在每次需要过滤我的文件时都加载文件 parameters/values 如果可能的话,合理的内存使用量。
编辑 1: 我逐行读取文件:
std::ifstream readTemp(filePath);
std::stringstream dataStream;
dataStream << readTemp.rdbuf();
readTemp.close();
while (std::getline(dataStream, line)){
if (line.find('[') != std::string::npos){
// Create Entity
Entity entity;
// Set entity id
entity.id = line.substr(line.find('[') + 1, line.find(']') - 1);
// Read all lines until EnumEnd=0
while (1){
std::getline(dataStream, line);
// Break loop if end of entity
if (line.find("EnumEnd=0") != std::string::npos){
if (CheckMatch(entity))
entities.push_back(entity);
entity.components.clear();
break;
}
Component comp;
int pos_eq = line.find('=');
comp.parameterId = line.substr(0, pos_eq);
comp.value = line.substr(pos_eq + 1);
entity.components.push_back(comp);
}
}
}
您可以通过 interning 您的参数和值使用更少的内存,以免存储它们的多个副本。
您可以将字符串映射到唯一数字 ID,这是您在加载文件时创建的,然后在查询数据结构时只使用这些 ID。以最初可能较慢的解析为代价,之后使用这些结构应该会更快,因为您只会匹配 32 位整数而不是比较字符串。
将每个字符串存储一次的粗略概念证明:
#include <unordered_map>
#include <string>
#include <iostream>
using namespace std;
int string_id(const string& s) {
static unordered_map<string, int> m;
static int id = 0;
auto it = m.find(s);
if (it == m.end()) {
m[s] = ++id;
return id;
} else {
return it->second;
}
}
int main() {
// prints 1 2 2 1
cout << string_id("hello") << " ";
cout << string_id("world") << " ";
cout << string_id("world") << " ";
cout << string_id("hello") << endl;
}
unordered_map
最终会将每个字符串存储一次,因此您已准备好记忆。根据你的匹配函数,你可以定义
struct Component {
int parameter;
int value;
};
然后您的匹配可以是 myComponent.parameter == string_id("some_key")
甚至 myComponent.parameter == some_stored_string_id
。如果你想要你的字符串,你也需要一个反向映射。
PS:编辑后。和关于内存消耗的评论
500MB / 20MB = 25.
如果每行是 25 个字符,内存消耗看起来还可以。
好的,您可以使用查找 table 将参数名称映射到数字。 如果names-set很小,这样最多可以节省2倍的消耗。
您的数据结构可能如下所示:
std::map<int, std::map<int, std::string> > my_ini_file_data;
std::map<std::string, int> param_to_idx;
(前提是部分(您称之为实体)中的参数名称不是唯一的)
放数据是:
std::string param = "Param";
std::string value = "Val";
int entity_id = 0;
if ( param_to_idx.find(param) == param_to_idx.end() )
param_to_idx[param] = param_to_idx.size();
my_ini_file_data[entity_id][ param_to_idx[param] ] = value;
获取数据是:
value = my_ini_file_data[entity_id][ param_to_idx[param] ];
如果值集也远小于条目数, 您甚至可以将值映射到数字:
std::map<int, std::map<int, int> > my_ini_file_data;
std::map<std::string, int> param_to_idx;
std::map<std::string, int> value_to_idx;
std::map<int, std::string> idx_to_value;
放数据是:
std::string param = "Param";
std::string value = "Val";
int entity_id = 0;
if ( param_to_idx.find(param) == param_to_idx.end() )
param_to_idx[param] = param_to_idx.size();
if ( value_to_idx.find(value) == value_to_idx.end() )
{
int idx = value_to_idx.size();
value_to_idx[value] = idx;
idx_to_value[idx] = value;
}
my_ini_file_data[entity_id][ param_to_idx[param] ] = value_to_idx[value];
获取数据是:
value = idx_to_value[my_ini_file_data[entity_id][ param_to_idx[param] ] ];
希望,这对您有所帮助。
初步回答
关于内存,我不会关心,除非你有一种内存非常小的嵌入式系统。
关于速度,我可以给你一些建议:
找出瓶颈是什么。
- 使用std::list!每次向量增长时,使用 std::vector 重新初始化内存。如果由于某种原因你最后需要一个向量,创建一个保留所需条目数的向量,你可以通过调用 list::size() 获得
- 写一个 while 循环,在那里你只调用 getline。如果仅此一项 已经很慢了,一次读取整个块,创建一个 reader-stream 从 char* 块中取出并从流中逐行读取。
如果简单阅读的速度还可以,优化你的解析代码。你 可以通过存储位置来减少查找调用的次数。例如
int pos_begin = line.find('[]'); if (pos_begin != std::string::npos){ int pos_end = line.find(']'); if (pos_end != std::string::npos){ entity.id = line.substr(pos_begin + 1, pos_begin - 1); // Read all lines until EnumEnd=0 while (1){ std::getline(readTemp, line); // Break loop if end of entity if (line.find("EnumEnd=0") != std::string::npos){ if (CheckMatch(entity)) entities.push_back(entity); break; } Component comp; int pos_eq = line.find('='); comp.parameter= line.substr(0, pos_eq); comp.value = line.substr(pos_eq + 1); entity.components.push_back(comp); } } }
- 根据实体的大小,检查 CheckMatch 是否慢。实体越小,代码越慢 - 在这种情况下。