QTextStream行为搜索不符合预期的字符串

时间:2013-04-06 11:08:25

标签: c++ qt qtextstream

我有几行代码:

QFile file("h:/test.txt");
file.open(QFile::ReadOnly | QFile::Text);
QTextStream in(&file);

bool found = false;
uint pos = 0;

do {
    QString temp = in.readLine();
    int p = temp.indexOf("something");
    if (p < 0) {
        pos += temp.length() + 1;
    } else {
        pos += p;
        found = true;
    }
} while (!found && !in.atEnd());

in.seek(0);
QString text = in.read(pos);
cout << text.toStdString() << endl;

我们的想法是在文本文件中搜索特定的char序列,找到后,将文件从开头加载到搜索文本的出现位置。我用于测试的输入是:

this is line one, the first line
this is line two, it is second
this is the third line
and this is line 4
line 5 goes here
and finally, there is line number 6

这里有一个奇怪的部分 - 如果搜索到的字符串在任何行上保存为最后一个,我得到预期的行为。它完美无缺。

但是如果我搜索最后一行6的字符串,结果总是短5个字符。如果它是第7行,结果将是6个字符短,依此类推,当搜索的字符串位于最后行时,结果总是lineNumber - 1个字符更短。

那么,这是一个错误还是我遗漏了一些明显的东西?

编辑:只是为了澄清,我不是要求其他方法来做到这一点,我问为什么我会这样做。

5 个答案:

答案 0 :(得分:4)

显然你会遇到这种行为,因为readLine()按行大小跳过行分隔符字符(LF CRLF或CR取决于文件)。 您从此方法获得的缓冲区不会包含这些符号,因此您不会在位置计算中使用这些字符。

解决方案是不是通过行读取而是通过缓冲读取。这是您的代码,已修改:

QFile file("h:/test.txt");
file.open(QFile::ReadOnly | QFile::Text);
QTextStream in(&file);

bool found = false;
uint pos = 0;
qint64 buffSize = 64; // adjust to your needs

do {
    QString temp = in.read(buffSize);
    int p = temp.indexOf("something");
    if (p < 0) {
        uint posAdj = buffSize;
        if (temp.length() < buffSize)
            posAdj = temp.length();
        pos += posAdj;
    } else {
        pos += p;
        found = true;
    }
} while (!found && !in.atEnd());

in.seek(0);
QString text = in.read(pos);
cout << text.toStdString() << endl;

修改

上面的代码包含错误,因为word可能被缓冲区分割。这是一个打破东西的示例输入(假设我们为keks搜索):

test test test test test test
test test test test test test  keks
test test test test test test
test test test test test test
test test test test test test
test test test test test test

解决方案

以下是完整代码,对于我尝试过的所有输入,效果很好:

#include <QFile>
#include <QTextStream>
#include <iostream>


int findPos(const QString& expr, QTextStream& stream) {
    if (expr.isEmpty())
        return -1;

    // buffer size of same length as searched expr should be OK to go
    qint64 buffSize = quint64(expr.length());

    stream.seek(0);
    QString startBuffer = stream.read(buffSize);
    int pos = 0;

    while(!stream.atEnd()) {
        QString cycleBuffer = stream.read(buffSize);
        QString searchBuffer = startBuffer + cycleBuffer;
        int bufferPos = searchBuffer.indexOf(expr);
        if (bufferPos >= 0)
            return pos + bufferPos + expr.length();
        pos += cycleBuffer.length();
        startBuffer = cycleBuffer;
    }

    return pos;
}

int main(int argc, char *argv[])
{
    Q_UNUSED(argc);
    Q_UNUSED(argv);

    QFile file("test.txt");
    file.open(QFile::ReadOnly | QFile::Text);
    QTextStream in(&file);

    int pos = findPos("keks", in);

    in.seek(0);
    QString text = in.read(pos);
    std::cout << text.toUtf8().data() << std::endl;
}

答案 1 :(得分:4)

当您在最后一行搜索时,您会读取所有输入流 - in.atEnd()返回true。看起来它会以某种方式破坏文件或文本流,或者将它们设置为不同步,因此搜索不再有效。

如果您更换

in.seek(0);
QString text = in.read(pos);
cout << text.toStdString() << endl;

通过

QString text;
if(in.atEnd())
{
    file.close();
    file.open(QFile::ReadOnly | QFile::Text);
    QTextStream in1(&file);
    text = in1.read(pos);
}

else
{
    in.seek(0);
    text = in.read(pos);
}
cout << text.toStdString().c_str() << endl;

它将按预期工作。 附:可能有一些更清洁的解决方案,然后重新打开文件,但问题肯定来自到达流和文件的末尾,并尝试在...之后对它们进行操作。

答案 2 :(得分:3)

你知道windows和* nix行结尾之间的区别(\ r \ n vs \ n)。在文本模式下打开文件时,您应该知道\ r \ n的所有序列都被转换为\ n。

您尝试计算跳过行的偏移量的原始代码中的错误,但您不知道文本文件中行的确切长度。

length = number_of_chars + number_of_eol_chars
where number_of_chars == QString::length()
and number_of_eol_chars == (1 if \n) or (2 if \r\n)

如果没有对文件的原始访问权限,您无法检测到 number_of_eol_chars 。并且您不在代码中使用它,因为您将文件作为文本打开,而不是作为二进制文件。因此,您的代码中出现错误,您已将 number_of_eol_chars 硬编码为1,而不是检测到它。对于Windows文本文件中的每一行(使用\ r \ n eol),每个跳过的行都会在pos中出错。

固定代码:

#include <QFile>
#include <QTextStream>

#include <iostream>
#include <string>


int main(int argc, char *argv[])
{
    QFile f("test.txt");
    const bool isOpened = f.open( QFile::ReadOnly | QFile::Text );
    if ( !isOpened )
        return 1;
    QTextStream in( &f );

    const QString searchFor = "finally";

    bool found = false;
    qint64 pos = 0;

    do 
    {
        const qint64 lineStartPos = in.pos();
        const QString temp = in.readLine();
        const int ofs = temp.indexOf( searchFor );
        if ( ofs < 0 )
        {
            // Here you skip line and increment pos on exact length of line
            // You shoud not hardcode "1", because it may be "2" (\n or \r\n)
            const qint64 length = in.pos() - lineStartPos;
            pos += length;
        }
        else
        {
            pos += ofs;
            found = true;
        }

    } while ( !found && !in.atEnd() );

    in.seek( 0 );
    const QString text = in.read( pos );

    std::cout << text.toStdString() << std::endl;

    return 0;
}

答案 3 :(得分:2)

我不完全确定你为什么会看到这种行为,但我怀疑它与行结尾有关。我尝试了你的代码,当文件有CRLF行结尾 AND 时,我只看到最后一行行为。文件末尾没有新行(CRLF)。所以是的,很奇怪。如果文件有LF行结尾,那么它总是按预期工作。

话虽如此,通过在每行末尾添加+ 1来跟踪位置可能不是一个好主意,因为您不知道您的源文件是CRLF还是LF,QTextStream将始终剥去线的结尾。这是一个应该更好的功能。它逐行构建输出字符串,我没有看到任何奇怪的行为:

void searchStream( QString fileName, QString searchStr )
{
    QFile file( fileName );
    if ( file.open(QFile::ReadOnly | QFile::Text) == false )
        return;

    QString text;
    QTextStream in(&file);
    QTextStream out(&text);

    bool found = false;

    do {
        QString temp = in.readLine();
        int p = temp.indexOf( searchStr );
        if (p < 0) {
            out << temp << endl;
        } else {
            found = true;
            out << temp.left(p);
        }
    } while (!found && !in.atEnd());

    std::cout << text.toStdString() << std::endl;
}

它不会跟踪原始流中的位置,所以如果你真的想要一个位置,那么我建议使用QTextStream :: pos(),因为无论文件是CRLF还是LF,它都是准确的。 / p>

答案 4 :(得分:2)

QTextStream.read()方法将要读取的最大字符数作为参数,而不是文件位置。在许多环境中,位置不是简单的字符数:VMS和Windows都是作为例外而出现的。 VMS强制使用记录结构,该结构在文件中使用许多隐藏的元数据位,文件位置为“魔术cookie”

获取正确值的唯一独立于文件系统的方法是在文件已经定位到正确位置时使用QTextStream::pos(),然后继续读取,直到文件位置返回到相同位置。

(已编辑,因为最初未指定的要求禁止多个分配来缓冲文本。)
但是,考虑到程序的要求,重新读取文件的第一部分是没有意义的。开始在开头保存文本,并在找到字符串时停止:

QString out;
do {
    QString temp = in.readLine();
    int p = temp.indexOf("something");
    if (p < 0) {
        out += temp;
    } else {
        out += temp.substr(pos);  //not sure of the proper function/parameters here
        break;
    }
} while (!in.atEnd());

cout << out.toStdString() << endl;

由于您使用的是Windows,因此文本文件处理会将'\ r \ n'转换为'\ n',这会导致文件定位与字符计数不匹配。有几种方法可以解决这个问题,但也许最简单的方法就是将文件作为二进制文件处理(即,通过删除text mode而不是“text”)以防止翻译:

file.open(QFile::ReadOnly);

然后代码应该按预期工作。它对Windows中的输出\ r \ n没有任何损害,但有时在使用Windows的文本实用程序时会导致令人讨厌的显示。如果这很重要,请在文本在内存中时使用\ n搜索并替换\ r \ n。