C++でのファイル入出力と大規模ファイルの分割読み書き方法

C++は強力なファイル操作機能を持つプログラミング言語です。本記事では、C++でのファイル入出力の基本から、大規模ファイルを効率的に分割して読み書きする方法までを詳細に解説します。これにより、ファイル操作の基礎をしっかりと理解し、実践的なファイル処理ができるようになります。

目次

ファイル入出力の基本

C++でのファイル操作は、ストリームを使って行います。ファイルストリームには主に三種類あり、読み込み用のifstream、書き込み用のofstream、そして読み書き両用のfstreamがあります。これらを用いることで、テキストファイルやバイナリファイルの操作が簡単に行えます。

基本的なファイル操作

ファイルを操作するためには、まずファイルストリームを開く必要があります。以下の例では、テキストファイルの読み書き方法を示します。

ファイルのオープン

ファイルを開くためには、openメソッドを使用します。以下のコードは、テキストファイルを読み込むためのifstreamの使用例です。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream infile("example.txt");
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }
    std::string line;
    while (std::getline(infile, line)) {
        std::cout << line << std::endl;
    }
    infile.close();
    return 0;
}

上記のコードでは、example.txtというファイルを開き、内容を一行ずつ読み込んでコンソールに出力しています。

ファイルの書き込み

次に、ファイルへの書き込み例です。ofstreamを使用して、ファイルにテキストを書き込みます。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outfile("output.txt");
    if (!outfile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }
    outfile << "これはテストの文章です。" << std::endl;
    outfile.close();
    return 0;
}

この例では、output.txtというファイルにテキストを一行書き込みます。ファイルが存在しない場合は新しく作成されます。

ファイル入出力の基本操作を理解することで、より複雑なファイル操作にも対応できるようになります。次に、具体的なifstreamofstreamの使い方を詳しく見ていきましょう。

ifstreamとofstreamの使い方

C++のifstreamofstreamは、ファイルの読み込みと書き込みを行うためのクラスです。これらのクラスを使うことで、ファイル操作が簡単に行えるようになります。ここでは、ifstreamofstreamの基本的な使い方を具体的なコード例を交えて解説します。

ifstreamの使い方

ifstream(input file stream)は、ファイルからデータを読み込むために使用します。以下は、ファイルからテキストを読み込む例です。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream infile("input.txt");
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }

    std::string line;
    while (std::getline(infile, line)) {
        std::cout << line << std::endl;
    }

    infile.close();
    return 0;
}

このプログラムでは、input.txtというファイルを開き、その内容を一行ずつ読み込んでコンソールに出力しています。std::getlineを使うことで、一行ずつ読み込むことができます。

ofstreamの使い方

ofstream(output file stream)は、ファイルにデータを書き込むために使用します。以下は、ファイルにテキストを書き込む例です。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outfile("output.txt");
    if (!outfile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }

    outfile << "これはテストの文章です。" << std::endl;
    outfile << "複数行に渡って書き込みます。" << std::endl;

    outfile.close();
    return 0;
}

このプログラムでは、output.txtというファイルに二行のテキストを書き込んでいます。<<演算子を使うことで、簡単にファイルにデータを書き込むことができます。

fstreamの使い方

fstreamは、読み込みと書き込みの両方に使用できるファイルストリームです。以下は、その使い方の例です。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::fstream file("data.txt", std::ios::in | std::ios::out | std::ios::app);
    if (!file) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }

    file << "新しいデータを追加します。" << std::endl;

    file.seekg(0, std::ios::beg);
    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl;
    }

    file.close();
    return 0;
}

このプログラムでは、data.txtというファイルを開き、データを追加し、ファイルの先頭から内容を読み込んでコンソールに出力しています。seekgメソッドを使うことで、ファイルポインタを任意の位置に移動させることができます。

これらの基本操作をマスターすることで、C++でのファイル操作がスムーズに行えるようになります。次に、ファイルのバイナリモードとテキストモードについて解説します。

ファイルのバイナリモードとテキストモード

C++では、ファイルを操作する際にテキストモードとバイナリモードのどちらかを選択することができます。このセクションでは、それぞれのモードの違いと使い分けについて説明します。

テキストモード

テキストモードでは、ファイルの内容が人間が読みやすい形式で保存されます。改行文字やエンディアンなどの処理が自動的に行われるため、テキストファイルの読み書きが簡単になります。デフォルトでC++のファイル操作はテキストモードで行われます。

以下は、テキストモードでファイルに書き込みを行う例です。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outfile("textfile.txt");
    if (!outfile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }

    outfile << "これはテキストモードで書き込まれたファイルです。" << std::endl;
    outfile.close();
    return 0;
}

このコードは、textfile.txtというファイルにテキストを書き込みます。改行文字が自動的に処理されます。

バイナリモード

バイナリモードでは、ファイルの内容がそのままの形式で保存されます。テキストモードのように改行文字の変換が行われないため、特定のバイト列や構造体などのバイナリデータを扱う際に便利です。バイナリモードでファイルを開くには、ios::binaryフラグを使用します。

以下は、バイナリモードでファイルに書き込みを行う例です。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outfile("binaryfile.bin", std::ios::binary);
    if (!outfile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }

    int number = 123456;
    outfile.write(reinterpret_cast<const char*>(&number), sizeof(number));
    outfile.close();
    return 0;
}

このコードは、binaryfile.binというファイルに整数をバイナリ形式で書き込みます。reinterpret_castを使って、整数をバイト配列として扱います。

使い分けのポイント

  • テキストモード: 人間が読みやすい形式でデータを保存する場合(例: 設定ファイル、ログファイル)。
  • バイナリモード: 生データや効率的なデータ保存が必要な場合(例: 画像ファイル、音声ファイル、データベースファイル)。

それぞれのモードを使い分けることで、適切なファイル操作が行えます。次に、ファイルポインタの操作方法について解説します。

ファイルポインタの操作

ファイルポインタは、ファイル内のデータを読み書きする位置を指し示す役割を持ちます。C++では、ファイルポインタを操作することで、ファイル内の任意の位置にアクセスすることができます。このセクションでは、ファイルポインタの基本操作とシークの概念について説明します。

ファイルポインタの基本操作

ファイルポインタを操作するためには、以下のメソッドを使用します。

  • seekg(シークゲット):入力ストリームのファイルポインタを移動します。
  • seekp(シークプット):出力ストリームのファイルポインタを移動します。
  • tellg:入力ストリームの現在のファイルポインタの位置を取得します。
  • tellp:出力ストリームの現在のファイルポインタの位置を取得します。

ファイルポインタの移動

ファイルポインタを移動させるための例を見てみましょう。以下のコードは、ファイルの先頭から任意の位置にファイルポインタを移動させる方法を示しています。

#include <iostream>
#include <fstream>

int main() {
    std::ifstream infile("example.txt", std::ios::binary);
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }

    // ファイルの終わりまでシーク
    infile.seekg(0, std::ios::end);
    std::streampos size = infile.tellg();
    std::cout << "ファイルサイズ: " << size << " バイト" << std::endl;

    // ファイルの先頭に戻る
    infile.seekg(0, std::ios::beg);

    // 任意の位置に移動(例: ファイルの10バイト目)
    infile.seekg(10, std::ios::beg);
    char ch;
    infile.get(ch);
    std::cout << "10バイト目の文字: " << ch << std::endl;

    infile.close();
    return 0;
}

この例では、ファイルの終端までシークしてファイルサイズを取得し、その後、ファイルの10バイト目に移動して1バイトを読み取ります。

ファイルポインタの相対移動

ファイルポインタを現在位置から相対的に移動させることもできます。以下のコードは、ファイルの先頭から5バイト目に移動し、さらに2バイト前に移動する例です。

#include <iostream>
#include <fstream>

int main() {
    std::ifstream infile("example.txt", std::ios::binary);
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }

    // ファイルの先頭から5バイト目に移動
    infile.seekg(5, std::ios::beg);

    // 現在位置から2バイト前に移動
    infile.seekg(-2, std::ios::cur);
    char ch;
    infile.get(ch);
    std::cout << "現在位置から2バイト前の文字: " << ch << std::endl;

    infile.close();
    return 0;
}

この例では、ファイルポインタを相対的に移動させてデータを読み取っています。

ファイルポインタの活用例

ファイルポインタを操作することで、ファイルの任意の位置からデータを効率的に読み書きすることができます。これにより、大規模ファイルの部分的な操作やランダムアクセスが可能になります。

次に、大規模ファイルを効率的に分割して読み書きする方法について説明します。

ファイルの分割読み書き

大規模ファイルを効率的に処理するためには、ファイルを分割して読み書きする方法が有効です。このセクションでは、ファイルを分割して読み書きする基本的な手法を説明します。

分割読み書きの必要性

大規模ファイルを一度にメモリに読み込むと、メモリ不足やパフォーマンスの低下が生じる可能性があります。分割して処理することで、メモリ使用量を抑え、パフォーマンスを向上させることができます。

分割読み書きの基本手法

ファイルを分割して読み書きするには、ファイルを一定サイズのチャンクに分けて操作します。以下のコードは、テキストファイルを分割して読み込む例です。

#include <iostream>
#include <fstream>
#include <vector>

void readFileInChunks(const std::string& filename, std::size_t chunkSize) {
    std::ifstream infile(filename, std::ios::binary);
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::vector<char> buffer(chunkSize);
    while (infile.read(buffer.data(), buffer.size())) {
        std::streamsize bytesRead = infile.gcount();
        // 読み込んだデータを処理する
        std::cout.write(buffer.data(), bytesRead);
    }

    // 残りのデータを処理する
    if (infile.gcount() > 0) {
        std::cout.write(buffer.data(), infile.gcount());
    }

    infile.close();
}

int main() {
    std::size_t chunkSize = 1024; // 1KB
    readFileInChunks("largefile.txt", chunkSize);
    return 0;
}

このコードでは、largefile.txtというファイルを1KBずつ分割して読み込み、その内容をコンソールに出力しています。

ファイルの分割書き込み

分割してファイルに書き込む場合も、同様の手法を用います。以下のコードは、大きなデータを分割してファイルに書き込む例です。

#include <iostream>
#include <fstream>
#include <vector>

void writeFileInChunks(const std::string& filename, const std::vector<char>& data, std::size_t chunkSize) {
    std::ofstream outfile(filename, std::ios::binary);
    if (!outfile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::size_t offset = 0;
    while (offset < data.size()) {
        std::size_t bytesToWrite = std::min(chunkSize, data.size() - offset);
        outfile.write(data.data() + offset, bytesToWrite);
        offset += bytesToWrite;
    }

    outfile.close();
}

int main() {
    std::vector<char> data(10000, 'A'); // 10KBのデータ
    std::size_t chunkSize = 1024; // 1KB
    writeFileInChunks("outputfile.bin", data, chunkSize);
    return 0;
}

このコードでは、10KBのデータを1KBずつ分割してoutputfile.binというファイルに書き込んでいます。

分割読み書きの応用

ファイルの分割読み書きは、ログファイルの解析や大規模データのバックアップ、ネットワーク通信など、さまざまな場面で役立ちます。効率的なファイル操作を実現するために、分割読み書きの手法を活用しましょう。

次に、分割読み書きの具体例をさらに詳しく見ていきます。

分割読み書きの具体例

ここでは、大規模ファイルを分割して読み書きする具体的な例を詳しく解説します。実際のコードを使って、分割読み書きの実装方法をステップバイステップで説明します。

テキストファイルの分割読み込み

以下のコード例は、1GBの大規模テキストファイルを1MBずつ分割して読み込む方法を示します。

#include <iostream>
#include <fstream>
#include <vector>

void readLargeTextFile(const std::string& filename, std::size_t chunkSize) {
    std::ifstream infile(filename, std::ios::binary);
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::vector<char> buffer(chunkSize);
    while (infile.read(buffer.data(), buffer.size())) {
        std::streamsize bytesRead = infile.gcount();
        // 読み込んだデータを処理する
        std::cout.write(buffer.data(), bytesRead);
    }

    // 残りのデータを処理する
    if (infile.gcount() > 0) {
        std::cout.write(buffer.data(), infile.gcount());
    }

    infile.close();
}

int main() {
    std::size_t chunkSize = 1024 * 1024; // 1MB
    readLargeTextFile("largefile.txt", chunkSize);
    return 0;
}

このプログラムは、指定されたファイルを1MBずつ読み込んで処理します。大規模ファイルの処理において、このような分割読み込みはメモリ効率を高めるために非常に有用です。

バイナリファイルの分割書き込み

次に、バイナリファイルへの分割書き込みの例を示します。ここでは、10MBのデータを1MBずつ分割してファイルに書き込みます。

#include <iostream>
#include <fstream>
#include <vector>

void writeLargeBinaryFile(const std::string& filename, const std::vector<char>& data, std::size_t chunkSize) {
    std::ofstream outfile(filename, std::ios::binary);
    if (!outfile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::size_t offset = 0;
    while (offset < data.size()) {
        std::size_t bytesToWrite = std::min(chunkSize, data.size() - offset);
        outfile.write(data.data() + offset, bytesToWrite);
        offset += bytesToWrite;
    }

    outfile.close();
}

int main() {
    std::vector<char> data(10 * 1024 * 1024, 'B'); // 10MBのデータ
    std::size_t chunkSize = 1024 * 1024; // 1MB
    writeLargeBinaryFile("outputfile.bin", data, chunkSize);
    return 0;
}

このプログラムは、バッファ内のデータを1MBずつファイルに書き込みます。これにより、大規模データの効率的な保存が可能になります。

応用例: 巨大ログファイルの分割と解析

ログファイルの分割読み書きは、特に巨大なログファイルを扱う際に役立ちます。以下は、巨大なログファイルを1MBずつ読み込んで解析する例です。

#include <iostream>
#include <fstream>
#include <vector>
#include <string>

void processLogChunk(const std::vector<char>& buffer, std::streamsize size) {
    // ここでログデータを解析する
    std::string logData(buffer.begin(), buffer.begin() + size);
    std::cout << "ログデータのチャンク: " << logData << std::endl;
}

void readLargeLogFile(const std::string& filename, std::size_t chunkSize) {
    std::ifstream infile(filename, std::ios::binary);
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::vector<char> buffer(chunkSize);
    while (infile.read(buffer.data(), buffer.size())) {
        std::streamsize bytesRead = infile.gcount();
        processLogChunk(buffer, bytesRead);
    }

    // 残りのデータを処理する
    if (infile.gcount() > 0) {
        processLogChunk(buffer, infile.gcount());
    }

    infile.close();
}

int main() {
    std::size_t chunkSize = 1024 * 1024; // 1MB
    readLargeLogFile("large_logfile.log", chunkSize);
    return 0;
}

このプログラムは、巨大なログファイルを1MBずつ読み込んで、それぞれのチャンクを解析します。これにより、効率的なログ解析が可能になります。

分割読み書きの具体例を理解することで、大規模ファイルの効率的な処理方法を学ぶことができます。次に、ファイルのエラーハンドリング方法について説明します。

ファイルのエラーハンドリング

ファイル操作時には、さまざまなエラーが発生する可能性があります。これらのエラーを適切に処理することで、プログラムの安定性と信頼性を高めることができます。このセクションでは、ファイル操作時の一般的なエラーハンドリング方法について解説します。

基本的なエラーハンドリング

C++では、ファイル操作中に発生するエラーを検出するためにストリームの状態をチェックする方法があります。以下は、基本的なエラーハンドリングの例です。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream infile("nonexistent.txt");
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }

    std::string line;
    while (std::getline(infile, line)) {
        if (infile.bad()) {
            std::cerr << "読み込み中に致命的なエラーが発生しました。" << std::endl;
            break;
        }
        if (infile.fail()) {
            std::cerr << "読み込み中にエラーが発生しました。" << std::endl;
            infile.clear(); // エラー状態をクリア
            continue;
        }
        std::cout << line << std::endl;
    }

    infile.close();
    return 0;
}

このプログラムでは、ファイルを開く際のエラー、読み込み中の致命的なエラー、およびその他の読み込みエラーを処理しています。infile.bad()は致命的なエラーを、infile.fail()は一般的なエラーを検出します。

エラーコードと例外の使用

C++11以降では、std::error_codeと例外を使用したエラーハンドリングが一般的です。以下の例では、ファイル操作時に例外を使用してエラーを処理します。

#include <iostream>
#include <fstream>
#include <system_error>

void readFile(const std::string& filename) {
    std::ifstream infile(filename);
    if (!infile) {
        throw std::ios_base::failure("ファイルが開けませんでした");
    }

    std::string line;
    while (std::getline(infile, line)) {
        std::cout << line << std::endl;
    }

    if (infile.bad()) {
        throw std::ios_base::failure("読み込み中に致命的なエラーが発生しました");
    }
}

int main() {
    try {
        readFile("example.txt");
    } catch (const std::ios_base::failure& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

このプログラムは、ファイル操作時に発生するエラーを例外として投げ、キャッチして処理します。これにより、エラーハンドリングが簡潔になり、コードの可読性が向上します。

ファイル操作時のリソース管理

ファイル操作時には、リソースの適切な管理も重要です。C++では、std::fstreamのコンストラクタとデストラクタを活用して、ファイルを自動的に閉じることができます。

#include <iostream>
#include <fstream>
#include <string>

void readFile(const std::string& filename) {
    std::ifstream infile(filename);
    if (!infile) {
        throw std::ios_base::failure("ファイルが開けませんでした");
    }

    std::string line;
    while (std::getline(infile, line)) {
        std::cout << line << std::endl;
    }
}

int main() {
    try {
        readFile("example.txt");
    } catch (const std::ios_base::failure& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

このプログラムでは、ifstreamオブジェクトがスコープを抜ける際に自動的にファイルが閉じられます。これにより、リソースの管理が簡単になります。

ファイル操作時のエラーハンドリングを適切に行うことで、プログラムの信頼性と安定性を確保できます。次に、ログファイルの分割と解析の応用例について説明します。

応用例: ログファイルの分割と解析

大規模なログファイルを扱う際には、ファイルを分割して効率的に解析することが重要です。このセクションでは、ログファイルを分割して解析する具体的な方法について解説します。

ログファイルの分割読み込みと解析

ログファイルを分割して読み込むことで、メモリ使用量を抑えつつ効率的にデータを処理することができます。以下は、1GBのログファイルを1MBずつ分割して読み込み、特定のパターンを解析する例です。

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <regex>

void processLogChunk(const std::vector<char>& buffer, std::streamsize size, const std::regex& pattern) {
    std::string logData(buffer.begin(), buffer.begin() + size);
    std::sregex_iterator begin(logData.begin(), logData.end(), pattern);
    std::sregex_iterator end;

    for (std::sregex_iterator i = begin; i != end; ++i) {
        std::smatch match = *i;
        std::cout << "一致したパターン: " << match.str() << std::endl;
    }
}

void readAndProcessLogFile(const std::string& filename, std::size_t chunkSize, const std::regex& pattern) {
    std::ifstream infile(filename, std::ios::binary);
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::vector<char> buffer(chunkSize);
    while (infile.read(buffer.data(), buffer.size())) {
        std::streamsize bytesRead = infile.gcount();
        processLogChunk(buffer, bytesRead, pattern);
    }

    if (infile.gcount() > 0) {
        processLogChunk(buffer, infile.gcount(), pattern);
    }

    infile.close();
}

int main() {
    std::size_t chunkSize = 1024 * 1024; // 1MB
    std::regex pattern(R"(ERROR:\s*(.*))"); // "ERROR:"パターンを探す
    readAndProcessLogFile("large_logfile.log", chunkSize, pattern);
    return 0;
}

このプログラムは、1MBずつログファイルを読み込み、”ERROR:”というパターンを検索して一致する部分を表示します。正規表現を使ってパターンマッチングを行い、効率的にログを解析します。

分割書き込みと解析の組み合わせ

ログファイルの分割書き込みと解析を組み合わせることで、リアルタイムにログを処理しつつ、必要なデータを別ファイルに保存することができます。

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <regex>

void processAndSaveLogChunk(const std::vector<char>& buffer, std::streamsize size, const std::regex& pattern, std::ofstream& outFile) {
    std::string logData(buffer.begin(), buffer.begin() + size);
    std::sregex_iterator begin(logData.begin(), logData.end(), pattern);
    std::sregex_iterator end;

    for (std::sregex_iterator i = begin; i != end; ++i) {
        std::smatch match = *i;
        outFile << match.str() << std::endl;
    }
}

void readProcessAndSaveLogFile(const std::string& inputFilename, const std::string& outputFilename, std::size_t chunkSize, const std::regex& pattern) {
    std::ifstream infile(inputFilename, std::ios::binary);
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::ofstream outfile(outputFilename, std::ios::binary);
    if (!outfile) {
        std::cerr << "出力ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::vector<char> buffer(chunkSize);
    while (infile.read(buffer.data(), buffer.size())) {
        std::streamsize bytesRead = infile.gcount();
        processAndSaveLogChunk(buffer, bytesRead, pattern, outfile);
    }

    if (infile.gcount() > 0) {
        processAndSaveLogChunk(buffer, infile.gcount(), pattern, outfile);
    }

    infile.close();
    outfile.close();
}

int main() {
    std::size_t chunkSize = 1024 * 1024; // 1MB
    std::regex pattern(R"(ERROR:\s*(.*))"); // "ERROR:"パターンを探す
    readProcessAndSaveLogFile("large_logfile.log", "error_logfile.log", chunkSize, pattern);
    return 0;
}

このプログラムは、large_logfile.logから”ERROR:”というパターンを検索し、一致する行をerror_logfile.logに保存します。これにより、エラーログを効率的に抽出し、解析することができます。

応用例のまとめ

ログファイルの分割と解析を組み合わせることで、大規模データを効率的に処理し、必要な情報を迅速に抽出することが可能です。この手法は、システム監視やデバッグ、データ分析など、さまざまな分野で応用できます。

次に、演習問題を通じて、ファイル入出力と分割読み書きに関する理解を深めましょう。

演習問題

ファイル入出力と分割読み書きの理解を深めるために、以下の演習問題を解いてみましょう。これらの問題を通じて、実際にコードを書きながら学んだ内容を実践しましょう。

演習問題1: 基本的なファイル読み書き

  1. テキストファイルexample.txtを作成し、そのファイルに以下の内容を書き込みなさい。
   これは演習問題のテキストです。
   ファイル入出力を学びましょう。
  1. 書き込んだファイルを読み込み、その内容をコンソールに出力するプログラムを書きなさい。

解答例

#include <iostream>
#include <fstream>

void writeToFile() {
    std::ofstream outfile("example.txt");
    if (!outfile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }
    outfile << "これは演習問題のテキストです。" << std::endl;
    outfile << "ファイル入出力を学びましょう。" << std::endl;
    outfile.close();
}

void readFromFile() {
    std::ifstream infile("example.txt");
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }
    std::string line;
    while (std::getline(infile, line)) {
        std::cout << line << std::endl;
    }
    infile.close();
}

int main() {
    writeToFile();
    readFromFile();
    return 0;
}

演習問題2: ファイルの分割読み書き

  1. largefile.txtという名前の1MB以上のテキストファイルを作成し、その内容を1KBずつ読み込んでコンソールに出力するプログラムを書きなさい。

解答例

#include <iostream>
#include <fstream>
#include <vector>

void readFileInChunks(const std::string& filename, std::size_t chunkSize) {
    std::ifstream infile(filename, std::ios::binary);
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::vector<char> buffer(chunkSize);
    while (infile.read(buffer.data(), buffer.size())) {
        std::streamsize bytesRead = infile.gcount();
        std::cout.write(buffer.data(), bytesRead);
    }

    if (infile.gcount() > 0) {
        std::cout.write(buffer.data(), infile.gcount());
    }

    infile.close();
}

int main() {
    std::size_t chunkSize = 1024; // 1KB
    readFileInChunks("largefile.txt", chunkSize);
    return 0;
}

演習問題3: エラーハンドリングの実装

  1. 存在しないファイルnonexistent.txtを読み込もうとし、エラーメッセージを表示するプログラムを書きなさい。

解答例

#include <iostream>
#include <fstream>

int main() {
    std::ifstream infile("nonexistent.txt");
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return 1;
    }
    std::string line;
    while (std::getline(infile, line)) {
        std::cout << line << std::endl;
    }
    infile.close();
    return 0;
}

演習問題4: ログファイルのパターン検索と保存

  1. logfile.txtという名前のログファイルを作成し、その中に”ERROR:”というパターンを含む行を別のファイルerror_log.txtに保存するプログラムを書きなさい。

解答例

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <regex>

void processAndSaveLogChunk(const std::vector<char>& buffer, std::streamsize size, const std::regex& pattern, std::ofstream& outFile) {
    std::string logData(buffer.begin(), buffer.begin() + size);
    std::sregex_iterator begin(logData.begin(), logData.end(), pattern);
    std::sregex_iterator end;

    for (std::sregex_iterator i = begin; i != end; ++i) {
        std::smatch match = *i;
        outFile << match.str() << std::endl;
    }
}

void readProcessAndSaveLogFile(const std::string& inputFilename, const std::string& outputFilename, std::size_t chunkSize, const std::regex& pattern) {
    std::ifstream infile(inputFilename, std::ios::binary);
    if (!infile) {
        std::cerr << "ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::ofstream outfile(outputFilename, std::ios::binary);
    if (!outfile) {
        std::cerr << "出力ファイルが開けませんでした。" << std::endl;
        return;
    }

    std::vector<char> buffer(chunkSize);
    while (infile.read(buffer.data(), buffer.size())) {
        std::streamsize bytesRead = infile.gcount();
        processAndSaveLogChunk(buffer, bytesRead, pattern, outfile);
    }

    if (infile.gcount() > 0) {
        processAndSaveLogChunk(buffer, infile.gcount(), pattern, outfile);
    }

    infile.close();
    outfile.close();
}

int main() {
    std::size_t chunkSize = 1024 * 1024; // 1MB
    std::regex pattern(R"(ERROR:\s*(.*))"); // "ERROR:"パターンを探す
    readProcessAndSaveLogFile("logfile.txt", "error_log.txt", chunkSize, pattern);
    return 0;
}

これらの演習問題を通じて、C++でのファイル入出力と分割読み書きのスキルを実践的に身につけてください。次に、本記事の内容をまとめます。

まとめ

本記事では、C++におけるファイル入出力の基本から、大規模ファイルの効率的な分割読み書き方法について詳細に解説しました。具体的なコード例やエラーハンドリングの方法、応用例としてログファイルの分割と解析の手法も紹介しました。演習問題を通じて、実際にコードを書きながら学びを深めることができました。これらの知識を活用して、実際のプロジェクトでも効率的にファイル操作を行い、信頼性の高いプログラムを開発してください。

コメント

コメントする

目次