7

次の数行のコードがあります。

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文字分短くなります。

それで、これはバグですか、それとも明らかな何かが欠けていますか?

編集: 明確にするために、これを行うための代替方法を求めているのではなく、なぜこの動作が発生するのかを尋ねています。

4

5 に答える 5

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;

編集

上記のコードには、単語がバッファによって分割されている可能性があるため、エラーが含まれています。これは、何かを壊すサンプル入力です(kekを検索すると仮定します):

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

You know the difference between windows and *nix line endings (\r\n vs \n). When you open file in text mode you should know that all sequence of \r\n are transtaled to \n.

Your mistake in original code that you are trying to calculate offset of skipped line, but you don't know it exact length of line in text file.

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)

You could not detect number_of_eol_chars without raw access to file. And you don't use it in your code, because you open file as text, but not as binary. So error in your code, that you had hardcoded number_of_eol_chars with 1, instead of detecting it. For each line in windows text files (with \r\n eol) you will get mistake in pos for each skipped line.

Fixed code:

#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 は、ファイル内のメタデータの多くの隠しビットを使用するレコード構造を課し、ファイルの位置は「マジック クッキー」です。

正しい値を取得するファイルシステムに依存しない唯一の方法は、ファイルが既に正しい場所に配置されている場合に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 のテキスト ユーティリティを使用すると、表示が煩わしい場合があります。それが重要な場合は、テキストがメモリに格納されたら、\r\n を検索して \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 に答える