处理大文本文件

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] ] ];

希望,这对您有所帮助。

初步回答

关于内存,我不会关心,除非你有一种内存非常小的嵌入式系统。

关于速度,我可以给你一些建议:

找出瓶颈是什么。

  1. 使用std::list!每次向量增长时,使用 std::vector 重新初始化内存。如果由于某种原因你最后需要一个向量,创建一个保留所需条目数的向量,你可以通过调用 list::size()
  2. 获得
  3. 写一个 while 循环,在那里你只调用 getline。如果仅此一项 已经很慢了,一次读取整个块,创建一个 reader-stream 从 char* 块中取出并从流中逐行读取。
  4. 如果简单阅读的速度还可以,优化你的解析代码。你 可以通过存储位置来减少查找调用的次数。例如

    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);
            }
        }
    }
    
    1. 根据实体的大小,检查 CheckMatch 是否慢。实体越小,代码越慢 - 在这种情况下。