如何加快大字符串的解析速度?

How can I speed up parsing of large strings?

本文关键字:速度 何加快 字符串      更新时间:2023-10-16

所以我做了一个可以读取各种配置文件的程序。其中一些配置文件可以很小,有些可以半大(最大的配置文件是 3,844 KB(。

读入文件存储在一个字符串中(在下面的程序中称为 sample(。

然后,我让程序根据各种格式规则从字符串中提取信息。这很好用,唯一的问题是在读取较大的文件时它非常慢......

我想知道我是否可以做些什么来加快解析速度,或者是否有一个现有的库可以满足我的需要(提取字符串直到分隔符并在同一级别的 2 个分隔符之间提取字符串字符串(。任何帮助都会很棒。

这是我的代码和它应该如何工作的示例......

#include "stdafx.h"
#include <string>
#include <vector>
std::string ExtractStringUntilDelimiter(
std::string& original_string,
const std::string& delimiter,
const int delimiters_to_skip = 1)
{
std::string needle = "";
if (original_string.find(delimiter) != std::string::npos)
{
int total_found = 0;
auto occurance_index = static_cast<size_t>(-1);
while (total_found != delimiters_to_skip)
{
occurance_index = original_string.find(delimiter);
if (occurance_index != std::string::npos)
{
needle = original_string.substr(0, occurance_index);
total_found++;
}
else
{
break;
}
}
// Remove the found string from the original string...
original_string.erase(0, occurance_index + 1);
}
else
{
needle = original_string;
original_string.clear();
}
if (!needle.empty() && needle[0] == '"')
{
needle = needle.substr(1);
}
if (!needle.empty() && needle[needle.length() - 1] == '"')
{
needle.pop_back();
}
return needle;
}
void ExtractInitialDelimiter(
std::string& original_string,
const char delimiter)
{
// Remove extra new line characters
while (!original_string.empty() && original_string[0] == delimiter)
{
original_string.erase(0, 1);
}
}
void ExtractInitialAndFinalDelimiters(
std::string& original_string,
const char delimiter)
{
ExtractInitialDelimiter(original_string, delimiter);
while (!original_string.empty() && original_string[original_string.size() - 1] == delimiter)
{
original_string.erase(original_string.size() - 1, 1);
}
}
std::string ExtractStringBetweenDelimiters(
std::string& original_string,
const std::string& opening_delimiter,
const std::string& closing_delimiter)
{
const size_t first_delimiter = original_string.find(opening_delimiter);
if (first_delimiter != std::string::npos)
{
int total_open = 1;
const size_t opening_index = first_delimiter + opening_delimiter.size();
for (size_t i = opening_index; i < original_string.size(); i++)
{
// Check if we have room for opening_delimiter...
if (i + opening_delimiter.size() <= original_string.size())
{
for (size_t j = 0; j < opening_delimiter.size(); j++)
{
if (original_string[i + j] != opening_delimiter[j])
{
break;
}
else if (j == opening_delimiter.size() - 1)
{
total_open++;
}
}
}

// Check if we have room for closing_delimiter...
if (i + closing_delimiter.size() <= original_string.size())
{
for (size_t j = 0; j < closing_delimiter.size(); j++)
{
if (original_string[i + j] != closing_delimiter[j])
{
break;
}
else if (j == closing_delimiter.size() - 1)
{
total_open--;
}
}
}

if (total_open == 0)
{
// Extract result, and return it...
std::string needle = original_string.substr(opening_index, i - opening_index);
original_string.erase(first_delimiter, i + closing_delimiter.size());
// Remove new line symbols
ExtractInitialAndFinalDelimiters(needle, 'n');
ExtractInitialAndFinalDelimiters(original_string, 'n');
return needle;
}
}
}
return "";
}
int main()
{
std::string sample = "{n"
"Line1n"
"Line2n"
"{n"
"SubLine1n"
"SubLine2n"
"}n"
"}";
std::string result = ExtractStringBetweenDelimiters(sample, "{", "}");
std::string LineOne = ExtractStringUntilDelimiter(result, "n");
std::string LineTwo = ExtractStringUntilDelimiter(result, "n");
std::string SerializedVector = ExtractStringBetweenDelimiters(result, "{", "}");
std::string SubLineOne = ExtractStringUntilDelimiter(SerializedVector, "n");
std::string SubLineTwo = ExtractStringUntilDelimiter(SerializedVector, "n");
// Just for testing...
printf("LineOne: %sn", LineOne.c_str());
printf("LineTwo: %sn", LineTwo.c_str());
printf("tSubLineOne: %sn", SubLineOne.c_str());
printf("tSubLineTwo: %sn", SubLineTwo.c_str());
system("pause");
}

使用string_view或手掷的。

不要修改加载的字符串。

original_string.erase(0, occurance_index + 1);

是代码气味,并且使用大的原始字符串会很昂贵。

如果您要修改某些内容,请一次性完成。 不要从前面重复删除 - 即 O(n^2(。 相反,沿着它前进并将"完成"的东西推入输出累加器中。

这将涉及更改代码的工作方式。

  1. 您正在将数据读入字符串。 "字符串长度"应该不是问题。目前为止,一切都好。。。

  2. 您使用的是"string.find((."。 这未必是一个糟糕的选择。

  3. 您正在使用"string.erase(("。 这可能是您问题的主要来源。

建议:

  • 将原始字符串视为"只读"。 不要调用 erase((,不要修改它。

  • 就个人而言,我会考虑将您的文本读取到 C 字符串(文本缓冲区(中,然后使用 strstr(( 解析文本缓冲区。

这是ExtractStringBetweenDelimiters的更有效版本。请注意,此版本不会改变原始缓冲区。您将对返回的字符串执行后续查询。

std::string trim(std::string buffer, char what)
{
auto not_what = [&what](char ch)
{
return ch != what;
};
auto first = std::find_if(buffer.begin(), buffer.end(), not_what);
auto last = std::find_if(buffer.rbegin(), std::make_reverse_iterator(first), not_what).base();
return std::string(first, last);
}
std::string ExtractStringBetweenDelimiters(
std::string const& buffer,
const char opening_delimiter,
const char closing_delimiter)
{
std::string result;
auto first = std::find(buffer.begin(), buffer.end(), opening_delimiter);
if (first != buffer.end())
{
auto last = std::find(buffer.rbegin(), std::make_reverse_iterator(first),
closing_delimiter).base();
if(last > first)
{
result.assign(first + 1, last);
result = trim(std::move(result), 'n');
}
}
return result;
}

如果您可以访问string_view(c++17 for std::string_view 或 boost::string_view(,您可以从这两个函数中返回其中一个以提高效率。

值得一提的是,如果任何序列化字符串包含分隔符(例如"{"(,这种解析结构化文件的方法将会导致问题。

最后,你会想要编写或使用别人的解析器。

boost::spirit库学习起来有点复杂,但为这类事情创建了非常有效的解析器。