C++でのファイル入出力とアーカイブファイル操作(tar、zip)の完全ガイド

C++プログラミングにおいて、ファイル入出力とアーカイブファイル(tarやzip)の操作は非常に重要です。本記事では、C++を用いた基本的なファイル入出力の方法から、tarやzipといったアーカイブファイルの作成・展開方法までを詳しく解説します。これにより、効率的なデータ管理やファイル操作が可能となり、実務でも活用できるスキルを習得できます。

目次
  1. C++での基本的なファイル入出力
    1. ファイルの読み込み
    2. ファイルへの書き込み
    3. 入出力を同時に行う
  2. ファイルストリームの種類
    1. ifstream (入力ファイルストリーム)
    2. ofstream (出力ファイルストリーム)
    3. fstream (入出力ファイルストリーム)
  3. ファイル入出力のエラーハンドリング
    1. ファイルを開く際のエラー処理
    2. ファイル読み込み時のエラー処理
    3. ファイル書き込み時のエラー処理
    4. ストリームの状態フラグ
  4. バイナリファイルの操作
    1. バイナリファイルの読み込み
    2. バイナリファイルへの書き込み
    3. バイナリモードの重要性
    4. シーク操作の利用
  5. ファイルのシーク操作
    1. シーク操作の基本
    2. ファイルの現在位置を取得
    3. シーク操作を使ったファイルの更新
  6. tarファイルの作成と展開
    1. libarchiveのインストール
    2. tarファイルの作成
    3. tarファイルの展開
    4. 注意点
  7. zipファイルの作成と展開
    1. minizipのインストール
    2. zipファイルの作成
    3. zipファイルの展開
    4. 注意点
  8. ライブラリの利用方法
    1. zlibの利用方法
    2. Boost.IOStreamsの利用方法
    3. libarchiveの利用方法
    4. まとめ
  9. 応用例: ファイルバックアップシステムの構築
    1. バックアップシステムの設計
    2. 必要なライブラリのインストール
    3. バックアップシステムの実装
    4. コードの解説
  10. 演習問題
    1. 演習問題1: テキストファイルのコピー
    2. 演習問題2: バイナリファイルの読み書き
    3. 演習問題3: ファイルの圧縮と展開
    4. 演習問題のまとめ
  11. まとめ

C++での基本的なファイル入出力

C++でファイルを扱う際の基本的な入出力操作について解説します。C++標準ライブラリには、ファイル操作のためのストリームクラスが用意されており、これを使用することで簡単にファイルの読み書きを行うことができます。

ファイルの読み込み

ファイルからデータを読み込むには、ifstreamクラスを使用します。以下は、テキストファイルからデータを読み込む例です。

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

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

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

    inputFile.close();
    return 0;
}

ファイルへの書き込み

ファイルにデータを書き込むには、ofstreamクラスを使用します。以下は、テキストファイルにデータを書き込む例です。

#include <iostream>
#include <fstream>

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

    outputFile << "これはテストの書き込みです。" << std::endl;
    outputFile.close();
    return 0;
}

入出力を同時に行う

読み込みと書き込みを同時に行う場合は、fstreamクラスを使用します。

#include <iostream>
#include <fstream>

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

    // ファイルから読み込み
    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl;
    }

    // ファイルに書き込み
    file.clear(); // 状態フラグをクリア
    file.seekp(0, std::ios::end); // ファイルの終わりに移動
    file << "新しい行を追加します。" << std::endl;

    file.close();
    return 0;
}

これらの基本操作を理解することで、C++でのファイル入出力の基礎を習得できます。次に、ファイルストリームの種類とその使い分けについて解説します。

ファイルストリームの種類

C++では、ファイル操作のために標準ライブラリから提供される3つの主要なファイルストリームクラスがあります。それぞれの用途と使い分けについて説明します。

ifstream (入力ファイルストリーム)

ifstreamはファイルからデータを読み込むためのクラスです。ファイルを開くときにファイル名を指定し、ストリームを使ってデータを読み込みます。

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

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

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

    inputFile.close();
    return 0;
}

ofstream (出力ファイルストリーム)

ofstreamはファイルにデータを書き込むためのクラスです。ファイルを開くときにファイル名を指定し、ストリームを使ってデータを書き込みます。

#include <iostream>
#include <fstream>

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

    outputFile << "これはテストの書き込みです。" << std::endl;
    outputFile.close();
    return 0;
}

fstream (入出力ファイルストリーム)

fstreamは、ファイルの読み書きを同時に行うためのクラスです。ファイルを開くときにファイル名とモード(読み込み、書き込み、またはその両方)を指定します。

#include <iostream>
#include <fstream>

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

    // ファイルから読み込み
    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl;
    }

    // ファイルに書き込み
    file.clear(); // 状態フラグをクリア
    file.seekp(0, std::ios::end); // ファイルの終わりに移動
    file << "新しい行を追加します。" << std::endl;

    file.close();
    return 0;
}

これらのストリームクラスを適切に使い分けることで、C++でのファイル操作が効率的に行えます。次に、ファイル入出力のエラーハンドリングについて説明します。

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

ファイル操作を行う際には、エラーハンドリングが非常に重要です。適切にエラーを処理することで、プログラムの信頼性と安定性が向上します。C++では、標準ライブラリの機能を使用してファイル操作中のエラーを検出し、処理することができます。

ファイルを開く際のエラー処理

ファイルを開く際には、ファイルが正常に開けたかどうかを確認する必要があります。ファイルが存在しない場合や、アクセス権がない場合にはエラーが発生します。

#include <iostream>
#include <fstream>

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

    // ファイル操作コード
    inputFile.close();
    return 0;
}

ファイル読み込み時のエラー処理

ファイルからデータを読み込む際にもエラーチェックを行います。読み込み操作が失敗した場合、ストリームの状態を確認することでエラーを検出できます。

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

int main() {
    std::ifstream inputFile("example.txt");
    if (!inputFile) {
        std::cerr << "エラー: ファイルを開けませんでした。" << std::endl;
        return 1;
    }

    std::string line;
    while (std::getline(inputFile, line)) {
        if (inputFile.bad()) {
            std::cerr << "エラー: 読み込み中にエラーが発生しました。" << std::endl;
            return 1;
        }
        std::cout << line << std::endl;
    }

    inputFile.close();
    return 0;
}

ファイル書き込み時のエラー処理

ファイルにデータを書き込む際にも、エラーが発生する可能性があります。書き込み操作が失敗した場合には、ストリームの状態を確認します。

#include <iostream>
#include <fstream>

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

    outputFile << "これはテストの書き込みです。" << std::endl;
    if (outputFile.fail()) {
        std::cerr << "エラー: 書き込み中にエラーが発生しました。" << std::endl;
        return 1;
    }

    outputFile.close();
    return 0;
}

ストリームの状態フラグ

ストリームの状態は以下のフラグで確認できます:

  • good(): 全ての操作が成功している場合に真を返します。
  • eof(): ファイルの終わりに到達した場合に真を返します。
  • fail(): 非致命的なエラーが発生した場合に真を返します。
  • bad(): 致命的なエラーが発生した場合に真を返します。

これらのフラグを利用して、ファイル操作中のエラーハンドリングを適切に行うことで、プログラムの安定性を高めることができます。次に、バイナリファイルの操作方法について説明します。

バイナリファイルの操作

バイナリファイルは、テキストデータではなく、バイナリデータを扱うファイルです。バイナリファイルの読み書きは、テキストファイルと異なる点があります。ここでは、C++でバイナリファイルを操作する方法を解説します。

バイナリファイルの読み込み

バイナリファイルを読み込むには、ifstreamクラスをバイナリモードで開きます。バイナリモードを指定するには、std::ios::binaryを使用します。

#include <iostream>
#include <fstream>

int main() {
    std::ifstream inputFile("data.bin", std::ios::binary);
    if (!inputFile) {
        std::cerr << "エラー: ファイルを開けませんでした。" << std::endl;
        return 1;
    }

    // バッファを準備
    char buffer[256];

    // ファイルからバイナリデータを読み込む
    inputFile.read(buffer, sizeof(buffer));
    if (inputFile) {
        std::cout << "全データを読み込みました。" << std::endl;
    } else {
        std::cout << "部分的にしか読み込めませんでした。" << std::endl;
    }

    inputFile.close();
    return 0;
}

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

バイナリファイルにデータを書き込むには、ofstreamクラスをバイナリモードで開きます。以下は、バイナリデータを書き込む例です。

#include <iostream>
#include <fstream>

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

    // 書き込むバイナリデータを準備
    char buffer[256] = { 'A', 'B', 'C', 'D' };

    // バイナリデータを書き込む
    outputFile.write(buffer, sizeof(buffer));
    if (!outputFile) {
        std::cerr << "エラー: データの書き込みに失敗しました。" << std::endl;
        return 1;
    }

    outputFile.close();
    return 0;
}

バイナリモードの重要性

バイナリモードを使用しない場合、特定の文字(例えば、Windowsでは0x0A、すなわち改行文字)が自動的に変換される可能性があります。これを避けるために、バイナリファイルを扱うときは必ずstd::ios::binaryモードを使用します。

シーク操作の利用

バイナリファイルでは、ファイル内の特定の位置に移動してデータを読み書きすることが重要です。シーク操作にはseekg(読み取り位置の変更)とseekp(書き込み位置の変更)を使用します。

#include <iostream>
#include <fstream>

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

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

    // データの読み込み
    char buffer[256];
    file.read(buffer, sizeof(buffer));

    // 読み込んだデータを表示
    std::cout << "読み込んだデータ: " << buffer << std::endl;

    file.close();
    return 0;
}

バイナリファイルの操作方法を理解することで、より複雑なデータの保存や読み込みが可能となります。次に、ファイルのシーク操作について詳細に説明します。

ファイルのシーク操作

ファイルのシーク操作とは、ファイル内の特定の位置にポインタを移動させることです。これにより、ファイルの任意の場所からデータの読み書きが可能になります。C++では、シーク操作を行うためにseekgseekpメソッドを使用します。

シーク操作の基本

シーク操作には、以下の3つの基準点があります:

  • std::ios::beg:ファイルの先頭
  • std::ios::cur:現在の位置
  • std::ios::end:ファイルの終わり

例:ファイルの先頭から10バイト目に移動

#include <iostream>
#include <fstream>

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

    // ファイルの先頭から10バイト目にシーク
    inputFile.seekg(10, std::ios::beg);
    char buffer[100];
    inputFile.read(buffer, sizeof(buffer));

    std::cout << "読み込んだデータ: " << buffer << std::endl;
    inputFile.close();
    return 0;
}

ファイルの現在位置を取得

ファイル内の現在の読み取り位置または書き込み位置を取得するには、tellgtellpメソッドを使用します。

#include <iostream>
#include <fstream>

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

    // ファイルの先頭から10バイト目にシーク
    inputFile.seekg(10, std::ios::beg);

    // 現在位置を取得
    std::streampos pos = inputFile.tellg();
    std::cout << "現在の位置: " << pos << std::endl;

    inputFile.close();
    return 0;
}

シーク操作を使ったファイルの更新

シーク操作を使うことで、ファイルの特定の位置にデータを書き込むことも可能です。以下は、ファイルの先頭から10バイト目にデータを書き込む例です。

#include <iostream>
#include <fstream>

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

    // ファイルの先頭から10バイト目にシーク
    file.seekp(10, std::ios::beg);

    // データを書き込む
    file.write("HELLO", 5);

    file.close();
    return 0;
}

シーク操作を理解し活用することで、ファイル内の任意の位置にアクセスし、効率的にデータを操作することができます。次に、C++からtarファイルを操作する方法について説明します。

tarファイルの作成と展開

tarファイルは、複数のファイルを一つのアーカイブファイルにまとめるための形式です。C++からtarファイルを作成・展開するためには、外部ライブラリを利用するのが一般的です。ここでは、libarchiveライブラリを使用してtarファイルを操作する方法を紹介します。

libarchiveのインストール

まず、libarchiveライブラリをインストールします。以下のコマンドを使用してインストールできます。

# Linux
sudo apt-get install libarchive-dev

# MacOS
brew install libarchive

tarファイルの作成

以下は、libarchiveを使用してtarファイルを作成するC++コードの例です。

#include <archive.h>
#include <archive_entry.h>
#include <iostream>
#include <fstream>

void create_tar(const char* tarname, const char* filename) {
    struct archive* a;
    struct archive_entry* entry;
    std::ifstream file(filename, std::ios::binary);

    a = archive_write_new();
    archive_write_set_format_pax_restricted(a);
    archive_write_open_filename(a, tarname);

    entry = archive_entry_new();
    archive_entry_set_pathname(entry, filename);
    archive_entry_set_size(entry, file.tellg());
    archive_entry_set_filetype(entry, AE_IFREG);
    archive_entry_set_perm(entry, 0644);

    archive_write_header(a, entry);

    char buffer[8192];
    while (file.read(buffer, sizeof(buffer))) {
        archive_write_data(a, buffer, file.gcount());
    }
    file.clear(); // Clear the fail bit
    file.read(buffer, sizeof(buffer)); // Read remaining bytes
    archive_write_data(a, buffer, file.gcount());

    archive_entry_free(entry);
    archive_write_close(a);
    archive_write_free(a);

    file.close();
}

int main() {
    create_tar("output.tar", "example.txt");
    return 0;
}

tarファイルの展開

次に、tarファイルを展開するC++コードの例を示します。

#include <archive.h>
#include <archive_entry.h>
#include <iostream>
#include <fstream>

void extract_tar(const char* tarname) {
    struct archive* a;
    struct archive_entry* entry;
    int r;

    a = archive_read_new();
    archive_read_support_format_tar(a);
    r = archive_read_open_filename(a, tarname, 10240); // Note 1

    if (r != ARCHIVE_OK) {
        std::cerr << "エラー: tarファイルを開けませんでした。" << std::endl;
        return;
    }

    while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
        const char* currentFile = archive_entry_pathname(entry);
        std::ofstream outFile(currentFile, std::ios::binary);

        const void* buff;
        size_t size;
        la_int64_t offset;

        while (archive_read_data_block(a, &buff, &size, &offset) == ARCHIVE_OK) {
            outFile.write((const char*)buff, size);
        }

        outFile.close();
        archive_read_data_skip(a); // Note 2
    }

    archive_read_close(a);
    archive_read_free(a);
}

int main() {
    extract_tar("output.tar");
    return 0;
}

注意点

  • Note 1: archive_read_open_filename の第三引数はブロックサイズです。一般的に10240が使用されます。
  • Note 2: archive_read_data_skip は、現在のエントリをスキップして次のエントリに進むための関数です。

libarchiveを使うことで、C++から簡単にtarファイルを作成・展開できます。次に、C++からzipファイルを操作する方法について説明します。

zipファイルの作成と展開

zipファイルは、ファイルやディレクトリを圧縮して保存するための形式です。C++からzipファイルを操作するには、zlibライブラリを利用します。ここでは、minizipというzlibのフロントエンドを使用してzipファイルを作成・展開する方法を紹介します。

minizipのインストール

まず、minizipライブラリをインストールします。以下のコマンドを使用してインストールできます。

# Linux
sudo apt-get install zlib1g-dev

# MacOS
brew install minizip

zipファイルの作成

以下は、minizipを使用してzipファイルを作成するC++コードの例です。

#include <iostream>
#include <fstream>
#include <minizip/zip.h>

void create_zip(const char* zipname, const char* filename) {
    zipFile zf = zipOpen(zipname, APPEND_STATUS_CREATE);
    if (zf == nullptr) {
        std::cerr << "エラー: zipファイルを作成できませんでした。" << std::endl;
        return;
    }

    zip_fileinfo zi = { 0 };
    if (zipOpenNewFileInZip(zf, filename, &zi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION) != ZIP_OK) {
        std::cerr << "エラー: zipファイルに新しいファイルを追加できませんでした。" << std::endl;
        zipClose(zf, nullptr);
        return;
    }

    std::ifstream inputFile(filename, std::ios::binary);
    if (!inputFile) {
        std::cerr << "エラー: 入力ファイルを開けませんでした。" << std::endl;
        zipCloseFileInZip(zf);
        zipClose(zf, nullptr);
        return;
    }

    char buffer[8192];
    while (inputFile.read(buffer, sizeof(buffer))) {
        zipWriteInFileInZip(zf, buffer, inputFile.gcount());
    }
    zipWriteInFileInZip(zf, buffer, inputFile.gcount());

    inputFile.close();
    zipCloseFileInZip(zf);
    zipClose(zf, nullptr);
}

int main() {
    create_zip("output.zip", "example.txt");
    return 0;
}

zipファイルの展開

次に、minizipを使用してzipファイルを展開するC++コードの例を示します。

#include <iostream>
#include <fstream>
#include <minizip/unzip.h>

void extract_zip(const char* zipname) {
    unzFile uf = unzOpen(zipname);
    if (uf == nullptr) {
        std::cerr << "エラー: zipファイルを開けませんでした。" << std::endl;
        return;
    }

    if (unzGoToFirstFile(uf) != UNZ_OK) {
        std::cerr << "エラー: zipファイルの最初のファイルにアクセスできませんでした。" << std::endl;
        unzClose(uf);
        return;
    }

    do {
        char filename[256];
        unz_file_info fileInfo;
        if (unzGetCurrentFileInfo(uf, &fileInfo, filename, sizeof(filename), nullptr, 0, nullptr, 0) != UNZ_OK) {
            std::cerr << "エラー: zipファイルの情報を取得できませんでした。" << std::endl;
            break;
        }

        if (unzOpenCurrentFile(uf) != UNZ_OK) {
            std::cerr << "エラー: zipファイル内のファイルを開けませんでした。" << std::endl;
            break;
        }

        std::ofstream outputFile(filename, std::ios::binary);
        if (!outputFile) {
            std::cerr << "エラー: 出力ファイルを作成できませんでした。" << std::endl;
            unzCloseCurrentFile(uf);
            break;
        }

        char buffer[8192];
        int bytesRead;
        while ((bytesRead = unzReadCurrentFile(uf, buffer, sizeof(buffer))) > 0) {
            outputFile.write(buffer, bytesRead);
        }

        outputFile.close();
        unzCloseCurrentFile(uf);

        if (bytesRead < 0) {
            std::cerr << "エラー: zipファイル内のファイルを読み込む際にエラーが発生しました。" << std::endl;
            break;
        }
    } while (unzGoToNextFile(uf) == UNZ_OK);

    unzClose(uf);
}

int main() {
    extract_zip("output.zip");
    return 0;
}

注意点

  • zipOpenNewFileInZipunzOpenCurrentFileなどの関数は、エラー処理を適切に行う必要があります。
  • バッファサイズは必要に応じて調整してください。

minizipを使うことで、C++から簡単にzipファイルを作成・展開できます。次に、ファイル圧縮や展開に使える他のライブラリの利用方法について説明します。

ライブラリの利用方法

ファイル圧縮や展開には、さまざまなライブラリが利用できます。ここでは、zlib、Boost.IOStreams、libarchiveなどの主要なライブラリについて紹介し、それぞれの使い方を説明します。

zlibの利用方法

zlibは、圧縮と展開をサポートする非常に人気のあるライブラリです。以下は、zlibを使ってデータを圧縮および展開する基本的な例です。

データの圧縮

#include <iostream>
#include <fstream>
#include <zlib.h>

void compress_data(const char* source, const char* dest) {
    std::ifstream inputFile(source, std::ios::binary);
    std::ofstream outputFile(dest, std::ios::binary);

    if (!inputFile || !outputFile) {
        std::cerr << "エラー: ファイルを開けませんでした。" << std::endl;
        return;
    }

    std::vector<char> buffer((std::istreambuf_iterator<char>(inputFile)), std::istreambuf_iterator<char>());
    uLong sourceLen = buffer.size();
    uLong destLen = compressBound(sourceLen);
    std::vector<char> compressedData(destLen);

    if (compress(reinterpret_cast<Bytef*>(&compressedData[0]), &destLen, reinterpret_cast<const Bytef*>(&buffer[0]), sourceLen) != Z_OK) {
        std::cerr << "エラー: データの圧縮に失敗しました。" << std::endl;
        return;
    }

    outputFile.write(compressedData.data(), destLen);
    inputFile.close();
    outputFile.close();
}

int main() {
    compress_data("example.txt", "compressed.dat");
    return 0;
}

データの展開

#include <iostream>
#include <fstream>
#include <zlib.h>

void decompress_data(const char* source, const char* dest) {
    std::ifstream inputFile(source, std::ios::binary);
    std::ofstream outputFile(dest, std::ios::binary);

    if (!inputFile || !outputFile) {
        std::cerr << "エラー: ファイルを開けませんでした。" << std::endl;
        return;
    }

    std::vector<char> compressedData((std::istreambuf_iterator<char>(inputFile)), std::istreambuf_iterator<char>());
    uLong compressedLen = compressedData.size();
    uLong destLen = compressedLen * 4; // 適切な展開後のサイズを見積もる
    std::vector<char> buffer(destLen);

    if (uncompress(reinterpret_cast<Bytef*>(&buffer[0]), &destLen, reinterpret_cast<const Bytef*>(&compressedData[0]), compressedLen) != Z_OK) {
        std::cerr << "エラー: データの展開に失敗しました。" << std::endl;
        return;
    }

    outputFile.write(buffer.data(), destLen);
    inputFile.close();
    outputFile.close();
}

int main() {
    decompress_data("compressed.dat", "decompressed.txt");
    return 0;
}

Boost.IOStreamsの利用方法

Boost.IOStreamsは、C++でストリーム操作を強化するためのライブラリです。gzipやbzip2の圧縮と展開をサポートしています。以下は、Boost.IOStreamsを使ってgzip圧縮と展開を行う例です。

データのgzip圧縮

#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <fstream>

void compress_gzip(const char* source, const char* dest) {
    std::ifstream inputFile(source, std::ios_base::in | std::ios_base::binary);
    std::ofstream outputFile(dest, std::ios_base::out | std::ios_base::binary);

    boost::iostreams::filtering_streambuf<boost::iostreams::output> out;
    out.push(boost::iostreams::gzip_compressor());
    out.push(outputFile);
    boost::iostreams::copy(inputFile, out);
}

int main() {
    compress_gzip("example.txt", "compressed.gz");
    return 0;
}

データのgzip展開

#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <fstream>

void decompress_gzip(const char* source, const char* dest) {
    std::ifstream inputFile(source, std::ios_base::in | std::ios_base::binary);
    std::ofstream outputFile(dest, std::ios_base::out | std::ios_base::binary);

    boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
    in.push(boost::iostreams::gzip_decompressor());
    in.push(inputFile);
    boost::iostreams::copy(in, outputFile);
}

int main() {
    decompress_gzip("compressed.gz", "decompressed.txt");
    return 0;
}

libarchiveの利用方法

libarchiveは、tarやcpioなどのアーカイブフォーマットに対応した強力なライブラリです。tarファイルの作成と展開には、前述のコードを参照してください。

まとめ

これらのライブラリを利用することで、C++プログラムから効率的にファイルの圧縮や展開を行うことができます。次に、これらの技術を応用してファイルバックアップシステムの構築方法を紹介します。

応用例: ファイルバックアップシステムの構築

ここでは、これまでに学んだファイル入出力とアーカイブ操作の技術を応用して、簡単なファイルバックアップシステムを構築する方法を紹介します。このシステムでは、指定したディレクトリ内のファイルを圧縮し、バックアップを作成します。

バックアップシステムの設計

このバックアップシステムは以下の機能を持ちます:

  1. 指定されたディレクトリ内のファイルを再帰的に検索
  2. 各ファイルをzip形式で圧縮
  3. 圧縮されたファイルを指定されたバックアップディレクトリに保存

必要なライブラリのインストール

このシステムには、zlibおよびBoost.Filesystemライブラリを使用します。以下のコマンドでインストールできます。

# Linux
sudo apt-get install zlib1g-dev libboost-filesystem-dev

# MacOS
brew install zlib boost

バックアップシステムの実装

以下は、バックアップシステムのC++コードの例です。

#include <iostream>
#include <fstream>
#include <boost/filesystem.hpp>
#include <zlib.h>
#include <minizip/zip.h>

namespace fs = boost::filesystem;

void compress_file(const fs::path& source, const fs::path& dest) {
    std::ifstream inputFile(source.string(), std::ios::binary);
    zipFile zf = zipOpen(dest.string().c_str(), APPEND_STATUS_CREATE);
    if (!zf) {
        std::cerr << "エラー: zipファイルを作成できませんでした。" << std::endl;
        return;
    }

    zip_fileinfo zi = { 0 };
    if (zipOpenNewFileInZip(zf, source.filename().string().c_str(), &zi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION) != ZIP_OK) {
        std::cerr << "エラー: zipファイルに新しいファイルを追加できませんでした。" << std::endl;
        zipClose(zf, nullptr);
        return;
    }

    char buffer[8192];
    while (inputFile.read(buffer, sizeof(buffer))) {
        zipWriteInFileInZip(zf, buffer, inputFile.gcount());
    }
    zipWriteInFileInZip(zf, buffer, inputFile.gcount());

    zipCloseFileInZip(zf);
    zipClose(zf, nullptr);
    inputFile.close();
}

void backup_directory(const fs::path& sourceDir, const fs::path& backupDir) {
    if (!fs::exists(backupDir)) {
        fs::create_directories(backupDir);
    }

    for (fs::recursive_directory_iterator end, dir(sourceDir); dir != end; ++dir) {
        if (fs::is_regular_file(*dir)) {
            fs::path destFile = backupDir / dir->path().filename().replace_extension(".zip");
            compress_file(dir->path(), destFile);
            std::cout << "バックアップ: " << dir->path().string() << " -> " << destFile.string() << std::endl;
        }
    }
}

int main() {
    fs::path sourceDir = "source_directory";
    fs::path backupDir = "backup_directory";

    backup_directory(sourceDir, backupDir);

    return 0;
}

コードの解説

  • compress_file 関数は、指定されたファイルをzip形式で圧縮します。
  • backup_directory 関数は、指定されたディレクトリ内の全ファイルを検索し、各ファイルを圧縮してバックアップディレクトリに保存します。
  • main 関数では、ソースディレクトリとバックアップディレクトリを指定し、backup_directory 関数を呼び出します。

このバックアップシステムを使用することで、簡単にファイルのバックアップを作成することができます。次に、学習内容を定着させるための演習問題を提供します。

演習問題

これまでに学んだC++のファイル入出力およびアーカイブファイル操作に関する知識を深めるために、以下の演習問題を解いてみましょう。

演習問題1: テキストファイルのコピー

テキストファイルを読み込み、別のファイルにコピーするプログラムを作成してください。ファイルの読み込みと書き込みには、ifstreamofstreamを使用します。

ヒント

  • ifstreamで元ファイルを開きます。
  • ofstreamでコピー先のファイルを開きます。
  • 1行ずつ読み込み、書き込みます。
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inputFile("source.txt");
    std::ofstream outputFile("copy.txt");

    if (!inputFile || !outputFile) {
        std::cerr << "エラー: ファイルを開けませんでした。" << std::endl;
        return 1;
    }

    std::string line;
    while (std::getline(inputFile, line)) {
        outputFile << line << std::endl;
    }

    inputFile.close();
    outputFile.close();
    return 0;
}

演習問題2: バイナリファイルの読み書き

バイナリファイルを読み込み、内容を別のバイナリファイルに書き込むプログラムを作成してください。

ヒント

  • ifstreamofstreamをバイナリモードで使用します。
  • バッファを使用してデータを読み書きします。
#include <iostream>
#include <fstream>

int main() {
    std::ifstream inputFile("source.bin", std::ios::binary);
    std::ofstream outputFile("copy.bin", std::ios::binary);

    if (!inputFile || !outputFile) {
        std::cerr << "エラー: ファイルを開けませんでした。" << std::endl;
        return 1;
    }

    char buffer[8192];
    while (inputFile.read(buffer, sizeof(buffer))) {
        outputFile.write(buffer, inputFile.gcount());
    }
    outputFile.write(buffer, inputFile.gcount());

    inputFile.close();
    outputFile.close();
    return 0;
}

演習問題3: ファイルの圧縮と展開

指定されたディレクトリ内の全ファイルを圧縮し、別のディレクトリに展開するプログラムを作成してください。minizipライブラリを使用します。

ヒント

  • zipOpenzipWriteInFileInZipzipCloseを使用してファイルを圧縮します。
  • unzOpenunzReadCurrentFileunzCloseを使用してファイルを展開します。

圧縮コード例

#include <iostream>
#include <fstream>
#include <minizip/zip.h>
#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

void compress_directory(const fs::path& sourceDir, const fs::path& zipFile) {
    zipFile zf = zipOpen(zipFile.string().c_str(), APPEND_STATUS_CREATE);
    if (!zf) {
        std::cerr << "エラー: zipファイルを作成できませんでした。" << std::endl;
        return;
    }

    for (fs::recursive_directory_iterator end, dir(sourceDir); dir != end; ++dir) {
        if (fs::is_regular_file(*dir)) {
            std::ifstream inputFile(dir->path().string(), std::ios::binary);
            if (!inputFile) {
                std::cerr << "エラー: 入力ファイルを開けませんでした。" << std::endl;
                continue;
            }

            zip_fileinfo zi = { 0 };
            if (zipOpenNewFileInZip(zf, dir->path().filename().string().c_str(), &zi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION) != ZIP_OK) {
                std::cerr << "エラー: zipファイルに新しいファイルを追加できませんでした。" << std::endl;
                continue;
            }

            char buffer[8192];
            while (inputFile.read(buffer, sizeof(buffer))) {
                zipWriteInFileInZip(zf, buffer, inputFile.gcount());
            }
            zipWriteInFileInZip(zf, buffer, inputFile.gcount());

            zipCloseFileInZip(zf);
            inputFile.close();
        }
    }

    zipClose(zf, nullptr);
}

int main() {
    fs::path sourceDir = "source_directory";
    fs::path zipFile = "backup.zip";

    compress_directory(sourceDir, zipFile);

    return 0;
}

展開コード例

#include <iostream>
#include <fstream>
#include <minizip/unzip.h>
#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

void extract_zip(const fs::path& zipFile, const fs::path& destDir) {
    unzFile uf = unzOpen(zipFile.string().c_str());
    if (!uf) {
        std::cerr << "エラー: zipファイルを開けませんでした。" << std::endl;
        return;
    }

    if (unzGoToFirstFile(uf) != UNZ_OK) {
        std::cerr << "エラー: zipファイルの最初のファイルにアクセスできませんでした。" << std::endl;
        unzClose(uf);
        return;
    }

    do {
        char filename[256];
        unz_file_info fileInfo;
        if (unzGetCurrentFileInfo(uf, &fileInfo, filename, sizeof(filename), nullptr, 0, nullptr, 0) != UNZ_OK) {
            std::cerr << "エラー: zipファイルの情報を取得できませんでした。" << std::endl;
            break;
        }

        fs::path outputPath = destDir / filename;
        if (unzOpenCurrentFile(uf) != UNZ_OK) {
            std::cerr << "エラー: zipファイル内のファイルを開けませんでした。" << std::endl;
            break;
        }

        std::ofstream outputFile(outputPath.string(), std::ios::binary);
        if (!outputFile) {
            std::cerr << "エラー: 出力ファイルを作成できませんでした。" << std::endl;
            unzCloseCurrentFile(uf);
            break;
        }

        char buffer[8192];
        int bytesRead;
        while ((bytesRead = unzReadCurrentFile(uf, buffer, sizeof(buffer))) > 0) {
            outputFile.write(buffer, bytesRead);
        }

        outputFile.close();
        unzCloseCurrentFile(uf);

        if (bytesRead < 0) {
            std::cerr << "エラー: zipファイル内のファイルを読み込む際にエラーが発生しました。" << std::endl;
            break;
        }
    } while (unzGoToNextFile(uf) == UNZ_OK);

    unzClose(uf);
}

int main() {
    fs::path zipFile = "backup.zip";
    fs::path destDir = "restored_directory";

    extract_zip(zipFile, destDir);

    return 0;
}

演習問題のまとめ

これらの演習問題を通じて、C++でのファイル操作とアーカイブファイルの扱いに関するスキルを実践的に身につけることができます。学んだ知識を活用して、さまざまなファイル操作を行ってみてください。

次に、記事全体のまとめを行います。

まとめ

この記事では、C++を用いたファイル入出力とアーカイブファイル(tar、zip)の操作方法について詳しく解説しました。基本的なファイルの読み書きから、エラーハンドリング、バイナリファイルの操作、シーク操作まで、実際に役立つコード例を通して学習しました。また、libarchiveやminizipなどの外部ライブラリを利用したtar、zipファイルの操作方法も紹介しました。

さらに、これらの技術を応用して、簡単なファイルバックアップシステムを構築し、実践的な知識を深めるための演習問題も提供しました。これにより、C++プログラムにおけるファイル操作の理解が深まったことでしょう。

今後のプロジェクトで、これらの技術を活用し、効率的なファイル管理やデータのアーカイブ・展開を実現してください。ファイル操作のスキルは、多くのアプリケーションで重要な要素となるため、これを機にさらに深掘りして学習を続けていきましょう。

コメント

コメントする

目次
  1. C++での基本的なファイル入出力
    1. ファイルの読み込み
    2. ファイルへの書き込み
    3. 入出力を同時に行う
  2. ファイルストリームの種類
    1. ifstream (入力ファイルストリーム)
    2. ofstream (出力ファイルストリーム)
    3. fstream (入出力ファイルストリーム)
  3. ファイル入出力のエラーハンドリング
    1. ファイルを開く際のエラー処理
    2. ファイル読み込み時のエラー処理
    3. ファイル書き込み時のエラー処理
    4. ストリームの状態フラグ
  4. バイナリファイルの操作
    1. バイナリファイルの読み込み
    2. バイナリファイルへの書き込み
    3. バイナリモードの重要性
    4. シーク操作の利用
  5. ファイルのシーク操作
    1. シーク操作の基本
    2. ファイルの現在位置を取得
    3. シーク操作を使ったファイルの更新
  6. tarファイルの作成と展開
    1. libarchiveのインストール
    2. tarファイルの作成
    3. tarファイルの展開
    4. 注意点
  7. zipファイルの作成と展開
    1. minizipのインストール
    2. zipファイルの作成
    3. zipファイルの展開
    4. 注意点
  8. ライブラリの利用方法
    1. zlibの利用方法
    2. Boost.IOStreamsの利用方法
    3. libarchiveの利用方法
    4. まとめ
  9. 応用例: ファイルバックアップシステムの構築
    1. バックアップシステムの設計
    2. 必要なライブラリのインストール
    3. バックアップシステムの実装
    4. コードの解説
  10. 演習問題
    1. 演習問題1: テキストファイルのコピー
    2. 演習問題2: バイナリファイルの読み書き
    3. 演習問題3: ファイルの圧縮と展開
    4. 演習問題のまとめ
  11. まとめ