7

I have these few lines of code:

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;

The idea is to search a text file for a specific char sequence, and when found, load the file from the beginning to the occurrence of the searched text. The input I used for testing was:

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

And here comes the strange part - if the searched string is on any of lines save for the last, I get the expected behavior. It works perfectly fine.

BUT if I search for a string that is on the last line 6, the result is always 5 characters short. If it was the 7th line, the result would be 6 characters short and so on, when the searched string is on the last line, the result is always lineNumber - 1 characters shorter.

So, is this a bug or I am missing something obvious?

EDIT: Just to clarify, I am not asking for alternative ways to do this, I am asking why do I get this behavior.

4

5 回答 5

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;

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

于 2013-04-16T21:32:08.643 回答
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;
}
于 2013-04-15T21:52:36.157 回答
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;
}
于 2013-04-19T09:02:25.663 回答
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而不是“文本” )以防止翻译:

file.open(QFile::ReadOnly);

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

于 2013-04-15T00:21:26.253 回答
2

我不完全确定您为什么会看到这种行为,但我怀疑它与行尾有关。我试过你的代码,当文件有 CRLF 行结尾并且文件末尾没有新行(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 都是准确的。

于 2013-04-06T13:24:12.783 回答