使用任意分隔符从 FileStream 读取

Reading from FileStream with arbitrary delimiter

我在使用 C++ 从文件中读取消息时遇到了问题。通常人们所做的是创建一个文件流,然后使用 getline() 函数来获取消息。 getline() 函数可以接受一个额外的参数作为分隔符,这样它 return 每个 "line" 由新的分隔符而不是默认的 '\n' 分隔。但是,此分隔符必须是字符。在我的用例中,消息中的分隔符可能是其他类似“|--|”的东西,所以我尝试获得一个解决方案,使其接受字符串作为分隔符而不是字符。

我稍微搜索了一下 Whosebug,发现了一些有趣的帖子。 Parse (split) a string in C++ using string delimiter (standard C++) 这个给出了一个使用 string::find()string::substr() 来解析任意定界符的解决方案。然而,那里的所有解决方案都假设输入是一个字符串而不是一个流,在我的例子中,文件流数据太 big/waste 不能一次放入内存所以它应该逐个读入 msg (或大量的消息一次)。

实际上,通读了 std::getline() 函数的 gcc 实现,似乎更容易处理大小写分隔符是单个字符的情况。因为每次加载一大块字符时,您总是可以搜索分隔符并将它们分开。如果您的定界符超过一个字符,情况会有所不同,但定界符本身可能会跨越两个不同的块,并导致许多其他极端情况。

不确定以前是否有人遇到过这种要求以及你们如何优雅地处理它。好像有一个像istream& getNext (istream&& is, string& str, string delim)这样的标准函数就好了?这对我来说似乎是一个通用的用例。为什么这个不在标准库中,这样人们就不用再单独实现自己的版本了?

非常感谢

如果您可以逐字节读取,您可以构建一个状态转换table有限状态机的实现来识别您的停止条件

std::string delimeter="someString";
//initialize table with a row per target string character, a column per possible char and all zeros
std::vector<vector<int> > table(delimeter.size(),std::vector<int>(256,0));
int endState=delimeter.size();
//set the entry for the state looking for the next letter and finding that character to the next state
for(unsigned int i=0;i<delimeter.size();i++){
    table[i][(int)delimeter[i]]=i+1;
}

现在你可以像这样使用它了

int currentState=0;
int read=0;
bool done=false;
while(!done&&(read=<istream>.read())>=0){
    if(read>=256){
        currentState=0;
    }else{
        currentState=table[currentState][read];
    }
    if(currentState==endState){
        done=true;
    }
    //do your streamy stuff
}

当然这仅在定界符为扩展 ASCII 时才有效,但对于像您的示例这样的内容它会很好地工作。

STL 根本不支持您的要求。您将必须编写自己的函数(或找到第 3 方函数)来满足您的需要。

例如,您可以使用 std::getline() 读取分隔符的第一个字符,然后使用 std::istream::get() 读取后续字符并将它们与分隔符的其余部分进行比较。例如:

std::istream& my_getline(std::istream &input, std::string &str, const std::string &delim)
{
    if (delim.empty())
        throw std::invalid_argument("delim cannot be empty!"); 

    if (delim.size() == 1)
        return std::getline(input, str, delim[0]);

    str.clear();

    std::string temp;
    char ch;
    bool found = false;

    do
    {
        if (!std::getline(input, temp, delim[0]))
            break;

        str += temp;

        found = true;

        for (int i = 1; i < delim.size(); ++i)
        {
            if (!input.get(ch))
            {
                if (input.eof())
                    input.clear(std::ios_base::eofbit);

                str.append(delim.c_str(), i);
                return input;
            }

            if (delim[i] != ch)
            {
                str.append(delim.c_str(), i);
                str += ch;
                found = false;
                break;
            }
        }
    }
    while (!found);

    return input;
}

看起来,创建类似getline() 的内容最简单:读取到分隔符的last 字符。然后检查字符串对于分隔符是否足够长,如果是,是否以分隔符结尾。如果不是,请继续阅读:

std::string getline(std::istream& in, std::string& value, std::string const& separator) {
    std::istreambuf_iterator<char> it(in), end;
    if (separator.empty()) { // empty separator -> return the entire stream
        return std::string(it, end);
    }
    std::string rc;
    char        last(separator.back());
    for (; it != end; ++it) {
        rc.push_back(*it);
        if (rc.back() == last
            && separator.size() <= rc.size()
            && rc.substr(rc.size() - separator.size()) == separator) {
            return rc.resize(rc.size() - separator.size());
        }
    }
    return rc; // no separator was found
}