Qt QTextStream::pos()徹底解説:ファイル位置取得の基本と注意点

2025-05-27

QTextStream::pos() とは

QTextStream::pos() メソッドは、QTextStream オブジェクトが現在読み書きしているデバイス(ファイルやQByteArrayなど)上の現在位置をバイト単位で返します

戻り値の型は qint64 であり、エラーが発生した場合は -1 を返します。

QTextStream のバッファリングと pos() の注意点

QTextStream はテキストの読み書きを効率化するために内部的にバッファを使用します。このバッファリングの仕組みにより、QTextStream::pos() の挙動にはいくつか注意が必要です。

  1. デバイスの物理的な位置との同期: QTextStream が内部バッファを持っているため、QTextStream::pos() が返す位置は、基となる QIODevice (例えば QFile) の物理的な位置と常に完全に一致するとは限りません。特に、QTextStream を介して読み書きしている間に、対応する QIODevice を直接操作(例: QFile::read()QFile::seek() を呼び出す)すると、QTextStream の内部的な位置とデバイスの位置が同期を失う可能性があります。

  2. エンコーディングの影響: QTextStream はテキストを扱うため、文字エンコーディング(UTF-8, Shift-JISなど)を考慮してデータを処理します。pos() が返すのは「バイト単位」の位置ですが、例えばUTF-8では1文字が1バイトとは限らず、複数バイトになることがあります。そのため、読み書きした文字数と pos() が返すバイト数が直接一致しないことがあります。QTextStream はエンコーディング変換を考慮してデバイス上の対応する位置を返そうとしますが、場合によっては複雑になる可能性があります。

  3. パフォーマンス: Qtのドキュメントや関連する議論では、QTextStream::pos() の呼び出しはコストがかかる(遅い)場合があると指摘されています。これは、QTextStream がバッファされたデータを考慮して、デバイス上の正確な位置を再構築する必要があるためです。特に、ループ内で頻繁に pos() を呼び出すとパフォーマンスに影響を与える可能性があります。

どのような時に使うか

QTextStream::pos() は、主に以下のような場合に役立ちます。

  • 特定の場所へのシーク(seek() との併用): pos() で取得した位置を保存し、後で QTextStream::seek() を使ってその位置に戻って処理を再開したい場合。大きなファイルを部分的に処理する際などに利用できます。
  • 現在の読み書き位置の確認: テキスト処理中に、現在の読み書きがどこまで進んだかを確認したい場合。
#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QDebug>

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

    QFile file("mytextfile.txt");
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream out(&file);
        out << "Hello, Qt!\n";
        qDebug() << "書き込み後の位置 (バイト):" << out.pos(); // 例: 12 (Hello, Qt!\n のバイト数)
        out << "これは2行目です。\n";
        qDebug() << "さらに書き込み後の位置 (バイト):" << out.pos(); // 例: 36
        file.close();
    }

    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        qDebug() << "ファイルオープン直後の位置 (バイト):" << in.pos(); // 0
        QString line1 = in.readLine();
        qDebug() << "1行目読み込み後の位置 (バイト):" << in.pos(); // Hello, Qt!\n のバイト数
        QString line2 = in.readLine();
        qDebug() << "2行目読み込み後の位置 (バイト):" << in.pos(); // ファイルの最後まで読んだ位置
        file.close();
    }

    return a.exec();
}


QTextStream::pos() に関連する一般的なエラーと問題

    • 問題: QTextStream を介してファイルを操作しているにもかかわらず、その下の QFile オブジェクトを直接 QFile::read()QFile::seek() などで操作すると、QTextStream の内部バッファと QFile の物理的な位置が同期を失い、QTextStream::pos() が正しくない値を返すことがあります。
    • 理由: QTextStream は読み書きを効率化するためにデータを内部バッファに保持します。QFile を直接操作すると、QTextStream がその変更を認識しないため、内部状態と実際のデバイスの状態がずれてしまいます。
  1. エンコーディングによる位置のずれ

    • 問題: QTextStream::pos() は「バイト単位」の位置を返しますが、テキストファイルのエンコーディング(例: UTF-8, Shift-JIS)によっては、1文字が複数バイトで表現されることがあります。そのため、QString::length() で得られる文字数と pos() が示すバイト数が一致せず、位置計算がずれることがあります。特に、改行コード(Windowsの CRLF と Unix/Linuxの LF)の違いも影響します。
    • 理由: QTextStream はテキストデータを扱うため、エンコーディング変換を行います。pos() は変換後のバイト位置を返しますが、コード内で文字数を基に位置を推測すると、エンコーディングの複雑さによりずれが生じます。
  2. パフォーマンスの低下

    • 問題: Qtのドキュメントにも記載があるように、QTextStream::pos() は内部バッファの整合性を保つために、基となるデバイスに対してシーク操作を行う必要がある場合があります。そのため、特に大規模なファイルやループ内で頻繁に pos() を呼び出すと、処理速度が著しく低下することがあります。
    • 理由: QTextStream は読み書きの効率を上げるためにバッファリングを行いますが、pos() はバッファされたデータとデバイス上の物理的な位置を同期させるためのコストを伴います。
  3. QTextStream::seek() との併用時の問題

    • 問題: QTextStream::pos() で取得した位置を QTextStream::seek() で再度設定しようとした際に、期待通りにシークできない、あるいはエラー (-1) が返されることがあります。特に、ファイルの終端に達した後や、ファイルの途中でエンコーディングの問題が発生している場合に起こりやすいです。
    • 理由: 上記の「物理ファイル位置との不一致」や「エンコーディングの影響」が複合的に絡み、seek() が正しく動作しない場合があります。また、ストリームが既に終端に達している場合、それ以上のシークは通常無効です。
  1. QFileQTextStream の操作を分離する

    • 解決策: QTextStream を使用してファイルを読み書きする際は、その QTextStream オブジェクトのみを介して操作し、基となる QFile オブジェクトを直接操作しないようにしてください。もし QFile の低レベルな操作が必要な場合は、QTextStream を使用する前に完了させるか、または QTextStream の使用を避けて QFile のみを使用することを検討してください。
    • :
      QFile file("data.txt");
      file.open(QIODevice::ReadOnly | QIODevice::Text);
      QTextStream in(&file);
      
      // BAD: QTextStreamとQFileを混在させる
      // in.readLine();
      // file.seek(100); // これを行うとinの内部状態が狂う可能性がある
      
      // GOOD: QTextStreamのみを使う
      QString line = in.readLine();
      qint64 currentPos = in.pos(); // QTextStreamの内部位置を取得
      in.seek(currentPos); // QTextStreamのseekを使う
      
  2. エンコーディングと改行コードを意識する

    • 解決策: QTextStreamsetCodec() メソッドを使用して、明示的にエンコーディングを設定することをお勧めします。これにより、予期せぬエンコーディングの自動検出によるずれを防げます。特に、Windows と Unix/Linux 間でファイルをやり取りする場合、改行コードの違いに注意が必要です。
    • :
      QTextStream in(&file);
      in.setCodec("UTF-8"); // 明示的にUTF-8を指定
      // または、システムのロケールに合わせる
      // in.setCodec(QTextCodec::codecForLocale());
      
    • 位置計算が文字数ベースで必要な場合は、QTextStream::readAll() などで一度に全て読み込み、QString 上で処理を行う方が安全な場合があります。ただし、これはファイルサイズが大きい場合にはメモリ消費の問題を引き起こす可能性があります。
  3. pos() の呼び出し頻度を減らす

    • 解決策: パフォーマンスが問題になる場合は、QTextStream::pos() の呼び出し回数を最小限に抑えることを検討してください。例えば、数行ごとに一度だけ位置を記録する、または処理の区切りとなる特定のマークを見つけた時のみ位置を記録するなど、戦略的に呼び出すようにします。
    • 代替手段: ファイルの進捗を示すために QFile::pos()QFile::size() を組み合わせることも可能ですが、これはバイナリモードでファイルを開き、文字エンコーディングを考慮しない場合に限られます。テキストデータの場合は、QTextStream のバッファリングとエンコーディング変換があるため、正確な進捗を示すのが難しい場合があります。
  4. QTextStream::seek() の利用方法を再確認する

    • 解決策: QTextStream::seek()false を返す場合は、シークが失敗していることを意味します。
      • ストリームが atEnd() に達している場合、seek() が機能しないことがあります。この場合は、一度ファイルを閉じ、再度開いてから seek() を試す必要があるかもしれません。
      • シークする位置がファイルの範囲外でないか確認してください。
      • QTextStream はバッファリングしているため、seek() を実行する前に flush() を呼び出すことで、バッファされた書き込みデータをデバイスに確実に書き出し、位置の整合性を高めることができる場合があります(読み取りストリームの場合は不要)。
  5. QTextStream の代わりに QFile を直接使用する(最後の手段)

    • 解決策: QTextStream のテキスト処理機能(エンコーディング変換、行単位の読み込みなど)が必須ではなく、厳密なバイト位置制御が必要な場合は、QFile をバイナリモードで開き、QFile::read()QFile::seek() を直接使用することも検討できます。ただし、この場合、文字エンコーディングの処理は開発者が手動で行う必要があります。
    • :
      QFile file("data.txt");
      if (file.open(QIODevice::ReadOnly)) { // バイナリモード
          qint64 currentBytePos = file.pos();
          QByteArray data = file.read(1024); // 1KB読み込む
          // data を手動でデコードする
      }
      


例1: ファイルの書き込みと読み込み中の位置確認

この例では、ファイルにテキストを書き込み、その後読み込む際に QTextStream::pos() を使って現在のストリーム位置を追跡します。

#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QDebug>

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

    // ファイル名
    QString fileName = "example.txt";

    // --- ファイルへの書き込み ---
    QFile writeFile(fileName);
    // QIODevice::WriteOnly: 書き込み専用で開く
    // QIODevice::Text: テキストモードで開く(改行コードの自動変換など)
    if (writeFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream outStream(&writeFile);
        outStream.setCodec("UTF-8"); // 明示的にUTF-8エンコーディングを設定

        qDebug() << "書き込み開始時の位置:" << outStream.pos(); // 通常は0

        outStream << "こんにちは、Qt!\n"; // 1行目
        // "こんにちは、Qt!\n" はUTF-8で複数バイトになる。
        // 例えば "こんにちは" が15バイト + "、"が3バイト + "Qt!"が3バイト + "\n"が1バイト = 22バイト
        // (環境や文字によってバイト数は変わる可能性があります)
        qDebug() << "1行目書き込み後の位置:" << outStream.pos();

        outStream << "これは二行目のテストです。\r\n"; // 2行目 (CRLF)
        qDebug() << "2行目書き込み後の位置:" << outStream.pos();

        outStream << "最後の行です。\n"; // 3行目
        qDebug() << "最終行書き込み後の位置:" << outStream.pos();

        writeFile.close();
        qDebug() << "ファイル書き込み完了。";
    } else {
        qCritical() << "ファイルを書き込み用に開けませんでした:" << writeFile.errorString();
        return 1;
    }

    // --- ファイルからの読み込み ---
    QFile readFile(fileName);
    if (readFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream inStream(&readFile);
        inStream.setCodec("UTF-8"); // 書き込み時と同じエンコーディングを設定

        qDebug() << "\n読み込み開始時の位置:" << inStream.pos(); // 通常は0

        QString line;
        while (!inStream.atEnd()) {
            qint64 currentPosBeforeRead = inStream.pos();
            line = inStream.readLine(); // 1行読み込む
            qDebug() << "行データ: '" << line << "'";
            qDebug() << "読み込み前位置:" << currentPosBeforeRead << ", 読み込み後位置:" << inStream.pos();
        }
        readFile.close();
        qDebug() << "ファイル読み込み完了。";
    } else {
        qCritical() << "ファイルを読み込み用に開けませんでした:" << readFile.errorString();
        return 1;
    }

    // ファイルのクリーンアップ(オプション)
    // QFile::remove(fileName);

    return a.exec();
}

解説

  • 読み込み時も同様に、readLine() を呼び出す前後の pos() の値を見ることで、その行が何バイト分だったか(改行コードを含む)を推測できます。ただし、QString::length()pos() の差は必ずしも一致しない(改行コードのバイト数が含まれる、エンコーディングによって文字あたりのバイト数が異なるなど)ことに注意してください。
  • outStream.pos() は、QTextStream が現在指している位置をバイト単位で返します。書き込み操作後、その位置は書き込まれたデータのバイト数分だけ進みます。

例2: 特定の位置へのシークとデータの再読み込み

この例では、ファイル内の特定のキーワードの位置を記録し、後でその位置にシークして再度データを読み込みます。

#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QDebug>

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

    QString fileName = "seek_example.txt";
    QString searchKeyword = "ターゲット";
    qint64 keywordPos = -1; // ターゲットキーワードの位置を保存

    // --- ファイルの準備(書き込み) ---
    QFile writeFile(fileName);
    if (writeFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream outStream(&writeFile);
        outStream.setCodec("UTF-8");

        outStream << "最初のデータ行。\n";
        outStream << "ここに" << searchKeyword << "があります。\n"; // ターゲットキーワード
        outStream << "最後のデータ行。\n";
        writeFile.close();
        qDebug() << "ファイル書き込み完了。";
    } else {
        qCritical() << "ファイルを書き込み用に開けませんでした:" << writeFile.errorString();
        return 1;
    }

    // --- ファイルからキーワードを探し、位置を記録 ---
    QFile readFile(fileName);
    if (readFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream inStream(&readFile);
        inStream.setCodec("UTF-8");

        QString line;
        while (!inStream.atEnd()) {
            qint64 currentLineStartPos = inStream.pos(); // 行を読み込む前の位置
            line = inStream.readLine();

            if (line.contains(searchKeyword)) {
                // キーワードが見つかった場合
                // QTextStream::pos() は行末(改行文字の後)を指すため、
                // 行頭からのオフセットを計算する必要がある。
                // readLine()は改行コードを含まないため、
                // readLine()後のpos()からreadLine()前のpos()を引くと、
                // その行のバイトサイズ(改行コード含む)がわかる。
                // ここでは、行の開始位置 + 行頭からのキーワードのバイトオフセット を計算する。
                // ただし、日本語を含む場合、QString::indexOfの結果とバイト位置が一致しないため、
                // より厳密な計算が必要になる。簡単な例として、ここでは行頭からの文字オフセットを使用する。
                // しかし、これはマルチバイト文字を考慮すると不正確になる。
                // より正確には、その行のデータをQByteArrayに変換してバイト位置を特定すべき。
                // ここでは簡略化のために、行頭からの文字位置を直接計算する。
                // (注意: これはマルチバイト文字の場合、バイト位置としては正確ではない可能性があります)
                int charIndex = line.indexOf(searchKeyword);
                if (charIndex != -1) {
                    // キーワードの文字位置に基づいて、その前の部分の文字列を一時的に作成し、
                    // そのUTF-8バイト長を計算する
                    QString prefix = line.left(charIndex);
                    keywordPos = currentLineStartPos + prefix.toUtf8().size();
                    qDebug() << "'" << searchKeyword << "' が見つかりました。位置:" << keywordPos;
                    break; // 見つかったらループを抜ける
                }
            }
        }
        readFile.close();
    } else {
        qCritical() << "ファイルを読み込み用に開けませんでした:" << readFile.errorString();
        return 1;
    }

    // --- 記録した位置にシークして再読み込み ---
    if (keywordPos != -1) {
        QFile readFileAgain(fileName);
        if (readFileAgain.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream inStreamAgain(&readFileAgain);
            inStreamAgain.setCodec("UTF-8");

            qDebug() << "\n記録された位置へのシークを試みます:" << keywordPos;
            if (inStreamAgain.seek(keywordPos)) {
                qDebug() << "シーク成功。現在の位置:" << inStreamAgain.pos();
                QString remainingData = inStreamAgain.readAll(); // 残りのデータを全て読み込む
                qDebug() << "シーク位置からのデータ:\n" << remainingData;
            } else {
                qCritical() << "シークに失敗しました。";
            }
            readFileAgain.close();
        } else {
            qCritical() << "ファイルを読み込み用に開けませんでした:" << readFileAgain.errorString();
            return 1;
        }
    } else {
        qDebug() << "キーワード '" << searchKeyword << "' はファイル内で見つかりませんでした。";
    }

    // ファイルのクリーンアップ(オプション)
    // QFile::remove(fileName);

    return a.exec();
}
  • 重要な注意点
    テキストモード (QIODevice::Text) でファイルを扱う場合、readLine() が改行コードを自動的に処理するため、pos() で取得される位置は、実際に読み込まれた QStringlength() とは異なるバイト数分進みます。
    • qint64 currentLineStartPos = inStream.pos(); で行を読み込む前のバイト位置を取得します。
    • line = inStream.readLine(); で行を読み込みます。この line には改行コードは含まれません。
    • inStream.pos() は、readLine() が読み込んだデータのバイト数(改行コード含む)だけ進んだ位置を示します。
    • キーワードの位置を正確に特定するには、行の先頭からのバイトオフセットを計算する必要があります。QString::toUtf8().size() を使うことで、QString の特定の文字数部分がUTF-8で何バイトになるかを計算しています。これにより、QTextStream::pos() が返すバイト位置との整合性を保とうとしています。
    • ただし、QString::indexOf は文字位置を返すため、マルチバイト文字を含むテキストでバイト位置を計算する際には注意が必要です。より厳密なアプリケーションでは、QByteArray を使ってバイト単位で処理するか、特定のエンコーディングを考慮したユーティリティ関数を使用する方が安全です。
  • この例では、QTextStream::seek(qint64 pos) を使って、以前 pos() で取得した位置に戻っています。


QTextStream::pos() の代替方法

QTextStream::pos() が抱える主な問題点は以下の通りです。

  1. パフォーマンス: QTextStream の内部バッファの整合性を保つため、pos() の呼び出しが遅くなることがある。
  2. 正確性の問題: テキストエンコーディング(マルチバイト文字など)や改行コードの自動変換により、pos() が返すバイト位置が、QString の文字数から想像される位置と一致しない場合がある。また、基となる QIODevice を直接操作すると、QTextStream の内部状態とずれる。

これらの問題を回避するための代替方法や、異なるアプローチを以下に示します。

QFile::pos() と QFile::read() / QFile::write() を直接使用する

最も直接的な代替手段は、QTextStream を介さずに、その基となる QFile オブジェクトの低レベルなI/Oメソッドを使用することです。

  • QFile::write(const QByteArray &data): QByteArray のデータをファイルに書き込みます。
  • QFile::read(qint64 maxSize): 指定されたバイト数だけデータを読み込み、QByteArray で返します。
  • QFile::seek(qint64 offset): ファイルポインタを特定のバイトオフセットに移動させます。
  • QFile::pos(): QIODevice の現在位置をバイト単位で返します。QTextStream::pos() と異なり、これはファイルの物理的なバイト位置を直接反映します。

利点

  • バイナリデータを扱う場合に最適です。
  • ファイル内の厳密なバイト位置を制御できます。
  • QTextStream のバッファリングやエンコーディング変換によるオーバーヘッドがないため、非常に高速です。

欠点

  • 改行コードの自動変換も行われないため、OS間の互換性を考慮する場合は、手動で処理する必要があります。
  • テキストエンコーディングの処理は開発者が手動で行う必要があります。 例えば、UTF-8のテキストを読み込む場合は、QByteArrayQString に変換する際に QTextCodec を使用するなど、明示的な変換が必要です。

使用例

#include <QCoreApplication>
#include <QFile>
#include <QDebug>
#include <QTextCodec> // エンコーディング変換用

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

    QString fileName = "byte_position_example.txt";
    QFile file(fileName);

    // --- ファイルへの書き込み(バイナリモードでUTF-8エンコード) ---
    // QIODevice::Truncate を指定すると、既存ファイルは上書きされる
    if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        QTextCodec *codec = QTextCodec::codecForName("UTF-8");

        QString str1 = "Hello, Qt!\n";
        file.write(codec->fromUnicode(str1));
        qDebug() << "書き込み後のQFile::pos():" << file.pos(); // バイト位置を直接反映

        QString str2 = "これは日本語のテストです。\n";
        file.write(codec->fromUnicode(str2));
        qDebug() << "さらに書き込み後のQFile::pos():" << file.pos();

        file.close();
        qDebug() << "ファイル書き込み完了。";
    } else {
        qCritical() << "ファイルを書き込み用に開けませんでした:" << file.errorString();
        return 1;
    }

    // --- ファイルからの読み込みと位置指定 ---
    if (file.open(QIODevice::ReadOnly)) { // バイナリモード
        QTextCodec *codec = QTextCodec::codecForName("UTF-8");

        qDebug() << "\n読み込み開始時のQFile::pos():" << file.pos(); // 0

        // 最初の数バイトを読み込む
        QByteArray chunk1 = file.read(12); // "Hello, Qt!\n" のバイト数(UTF-8では通常12バイト)
        qDebug() << "最初のチャンク (raw):" << chunk1;
        qDebug() << "最初のチャンク (QString):" << codec->toUnicode(chunk1);
        qDebug() << "読み込み後のQFile::pos():" << file.pos();

        // 特定の位置にシーク(例えば、ファイルの中間あたり)
        qint64 midPos = file.size() / 2;
        qDebug() << "ファイルサイズ:" << file.size();
        qDebug() << "中央位置にシークを試みます:" << midPos;
        if (file.seek(midPos)) {
            qDebug() << "シーク成功。現在のQFile::pos():" << file.pos();
            QByteArray remainingData = file.readAll(); // 残りのデータを全て読み込む
            qDebug() << "シーク位置からのデータ (raw):\n" << remainingData;
            qDebug() << "シーク位置からのデータ (QString):\n" << codec->toUnicode(remainingData);
        } else {
            qCritical() << "シークに失敗しました。";
        }

        file.close();
        qDebug() << "ファイル読み込み完了。";
    } else {
        qCritical() << "ファイルを読み込み用に開けませんでした:" << file.errorString();
        return 1;
    }

    // ファイルのクリーンアップ(オプション)
    // QFile::remove(fileName);

    return a.exec();
}

QDataStream を使用する(バイナリデータのシリアライズ)

もしファイルにテキストだけでなく、構造化されたデータ(数値、リスト、カスタムオブジェクトなど)を保存し、プラットフォームに依存しない形式で読み書きしたい場合は、QDataStream が強力な代替手段となります。

  • QDataStreamQTextStream のようなテキストエンコーディングの概念を持たず、データをバイナリ形式でシリアライズ/デシリアライズします。
  • QDataStream はデータのバイト表現を直接操作し、QDataStream::pos() メソッドも提供します。

利点

  • 高速なI/O操作。
  • プリミティブ型、Qtのデータ型(QString, QList, QMapなど)、およびカスタム型を簡単にシリアライズ/デシリアライズできる。
  • プラットフォーム非依存のバイナリデータ読み書きが可能。

欠点

  • テキストデータのみを扱う場合はオーバーキルになる可能性がある。
  • 人間が直接読み書きできる形式ではない。

使用例

#include <QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDebug>

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

    QString fileName = "data_stream_example.dat";
    QFile file(fileName);

    // --- ファイルへの書き込み ---
    if (file.open(QIODevice::WriteOnly)) {
        QDataStream outStream(&file);
        outStream.setVersion(QDataStream::Qt_6_0); // バージョン設定(互換性のため重要)

        qDebug() << "書き込み開始時のQDataStream::pos():" << outStream.pos(); // 0

        QString name = "山田太郎";
        int age = 30;
        QList<double> scores = {95.5, 88.0, 72.5};

        outStream << name << age << scores; // データを書き込む
        qDebug() << "データ書き込み後のQDataStream::pos():" << outStream.pos();

        file.close();
        qDebug() << "データ書き込み完了。";
    } else {
        qCritical() << "ファイルを書き込み用に開けませんでした:" << file.errorString();
        return 1;
    }

    // --- ファイルからの読み込み ---
    if (file.open(QIODevice::ReadOnly)) {
        QDataStream inStream(&file);
        inStream.setVersion(QDataStream::Qt_6_0); // 書き込み時と同じバージョンを設定

        qDebug() << "\n読み込み開始時のQDataStream::pos():" << inStream.pos(); // 0

        QString nameRead;
        int ageRead;
        QList<double> scoresRead;

        inStream >> nameRead >> ageRead >> scoresRead; // データを読み込む
        qDebug() << "データ読み込み後のQDataStream::pos():" << inStream.pos();

        qDebug() << "読み込んだデータ:";
        qDebug() << "  名前:" << nameRead;
        qDebug() << "  年齢:" << ageRead;
        qDebug() << "  スコア:" << scoresRead;

        file.close();
        qDebug() << "データ読み込み完了。";
    } else {
        qCritical() << "ファイルを読み込み用に開けませんでした:" << file.errorString();
        return 1;
    }

    // ファイルのクリーンアップ(オプション)
    // QFile::remove(fileName);

    return a.exec();
}

手動で進捗を追跡する

QTextStream を使いつつ、そのバッファリングの特性を考慮して、アプリケーションレベルで進捗を管理することも可能です。これは、QTextStream::pos() のパフォーマンス問題が懸念される場合に有効です。

  • ファイルサイズと処理済みバイト数を比較して進捗を計算する。QFile::size() で総バイト数を取得し、QFile::pos() または手動で計算したバイト数を進捗の目安にする。この場合、QTextStream の内部バッファと QFile の物理的な位置がずれないように、QTextStreamQFile の操作を混在させない工夫が必要です。
  • 一定の行数(例えば100行ごと)に一度だけ QTextStream::pos() を呼び出して記録する。
  • 行数や処理済みレコード数をカウンターで管理する。

利点

  • 特定のユースケースに合わせて最適化できる。
  • QTextStream の便利なテキスト処理機能をそのまま利用できる。

欠点

  • コードが複雑になる可能性がある。
  • 位置の厳密性は QFile 直接操作より劣る。
#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QDebug>

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

    QString fileName = "progress_example.txt";
    QFile file(fileName);

    // テストファイルの作成
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream out(&file);
        out.setCodec("UTF-8");
        for (int i = 0; i < 1000; ++i) {
            out << "これは" << i + 1 << "行目のデータです。\n";
        }
        file.close();
    }

    // ファイルの読み込みと進捗の表示
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        in.setCodec("UTF-8");

        qint64 totalBytes = file.size(); // ファイルの総バイト数
        qint64 processedBytes = 0;
        int lineNumber = 0;

        qDebug() << "ファイルサイズ (バイト):" << totalBytes;

        while (!in.atEnd()) {
            qint64 currentTextStreamPos = in.pos(); // QTextStreamの内部位置
            QString line = in.readLine();
            lineNumber++;

            // QTextStream::pos() は読み取り後に進むので、読み取ったバイト数を概算する
            // 厳密ではないが、進捗の目安にはなる
            // 実際には、QFile::pos() を使った方がより正確な物理ファイル位置が得られるが、
            // QTextStreamのバッファリングによりQFile::pos()はQTextStreamの進捗とずれやすい。
            // ここではQTextStreamの読み込み完了後のpos()を基にしている。
            if (lineNumber > 0) { // 最初の行以外で前回の位置との差を計算
                processedBytes = in.pos(); // 現在のストリーム位置を処理済みバイト数とみなす
            }

            int progress = (totalBytes > 0) ? (processedBytes * 100 / totalBytes) : 0;
            if (lineNumber % 100 == 0 || in.atEnd()) { // 100行ごと、またはファイルの最後に進捗を表示
                qDebug() << QString("処理中: %1行目, 進捗: %2% (%3 / %4 バイト)").arg(lineNumber).arg(progress).arg(processedBytes).arg(totalBytes);
            }
        }
        file.close();
        qDebug() << "ファイル処理完了。";
    } else {
        qCritical() << "ファイルを読み込み用に開けませんでした:" << file.errorString();
        return 1;
    }

    return a.exec();
}
  • テキストだけでなく、数値やカスタムデータなど、構造化されたデータを扱う場合

    • QDataStream が最も適しています。バイナリデータとしてシリアライズ/デシリアライズすることで、プラットフォーム非依存で高速なI/Oが可能です。
  • テキストファイルを扱いたいが、QTextStream::pos() のパフォーマンスや正確性の問題が気になる場合

    • 低頻度で位置追跡が必要なら、既存の QTextStream::pos() をそのまま使う。
    • 厳密なバイト位置制御と最高のパフォーマンスが必要な場合、QFile::read() / QFile::write() を直接使用し、手動でエンコーディング変換を行う。
    • QTextStream の便利さを活かしつつ、進捗表示などの目的で大まかな位置情報が必要な場合、行数カウントや QFile::size() と組み合わせた手動の進捗管理を行う。