MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Qtの基礎 - CSVファイルのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Qtの基礎 - CSVファイル
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == CSV (Comma-Separated Values) ファイルは、テキストベースのデータ形式である。<br> 各行がデータレコードを表しており、フィールドはカンマで区切られている。<br> <br> Qtでは、CSVファイルの読み書きを簡単に行うことができる。<br> <br> 主に<code>QFile</code>クラス、<code>QTextStream</code>クラス、<code>QStringList</code>を使用してCSVファイルを操作する。<br> <code>QFile</code>クラスはファイルのオープン・クローズを行い、<code>QTextStream</code>クラスはテキストの読み書きを行う。<br> <code>QStringList</code>クラスは、CSVの各行を分割して扱う場合に便利である。<br> <br> ファイルの読み込み時は、<code>QFile</code>オブジェクトでファイルを開き、<code>QTextStream</code>オブジェクトを使用して1行ずつ読み込む。<br> 各レコードは、<code>QString::split</code>メソッドでフィールドに分割することができる。<br> <br> 書き込み時も同様、<code>QFile</code>オブジェクトと<code>QTextStream</code>オブジェクトを使用する。<br> データをカンマで連結して、<code>QTextStream</code>クラスの<code><<</code>演算子を使用して書き込む。<br> <br> また、ファイルがオープンできない、書き込むことができない等の状況に対応するため、適切な例外処理やエラー処理を実装することが推奨される。<br> <br> なお、CSVファイルの形式が統一されていない場合 (例: フィールド内にカンマが含まれる)、パースが複雑になることがある。<br> このような場合は、正規表現やより高度なパーシング技術が必要になることもある。<br> <br><br> == CSVファイル == 以下の例では、CSVファイルを作成している。<br> <br> 処理の流れを以下に示す。<br> # 数値データを生成する。<br>ここでは、sin曲線である <math>y = \sin(x)</math> を生成する。 # 数値データをテキストデータに変換する。 # ファイル保存ダイアログを開いて、保存するファイル名を入力する。 # 全テキストデータを書き込む。 <br> <syntaxhighlight lang="c++"> #include <QFileDialog> #include <QMessageBox> #include <QTextStream> #include <QFile> #include <vector> #include <math.h> #include <sstream> #include "mainwindow.h" #include "ui_mainwindow.h" using namespace std; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::OnButtonClicked() { // Input File Name QString FileName = QFileDialog::getSaveFileName(this, tr("Save CSV File"), "", tr("CSV (*.csv);;All Files (*)")); if(FileName.isEmpty()) { // Cancel return; } // Generate CSV Data int iSize = 100; vector<double> lx(iSize, 0.0f), ly(iSize, 0.0f); double dx = 2 * 3.15159 / iSize; for(int i = 0; i < iSize; i++) { lx[i] = i * dx; ly[i] = sin(vx); } // Convert from Double to String QString strFileData = ""; //stringstream ss; for(int i = 0; i < iSize; i++) { strFileData += lx[i] + ", " + ly[i] + "\n"; //ss << lx[i] << "," << ly[i] << endl; } // Write to File QFile File(FileName); if(!File.open(QIODevice::WriteOnly)) { QMessageBox::information(this, tr("Unable to open file"), File.errorString()); return; } QTextStream OutStream(&File); // 浮動小数点をe形式にする //OutStream.setRealNumberNotation(QTextStream::ScientificNotation); // 浮動小数点の桁数を固定 //OutStream.setRealNumberNotation(QTextStream::FixedNotation); // 浮動小数点の桁数を設定 OutStream.setRealNumberPrecision(3); OutStream << strFileData; //OutStream << ss.str().c_str(); File.close(); } </syntaxhighlight> <br><br> == CSVファイル (RFC4180準拠) の読み込み == RFC4180は、CSVファイルの標準的な形式を定義しており、これに従うことで互換性の高いCSV処理が可能になる。<br> <br> 以下の例では、RFC4180の規則に従ってCSVファイルを読み込んでいる。<br> * ダブルクォートで囲まれたフィールド内のカンマと改行を正しく処理する。 * フィールド内の二重引用符 ("") を単一の引用符 ('') として解釈する。 * 各行の末尾の空白を除去する。 <br> <syntaxhighlight lang="c++"> #include <QFile> #include <QTextStream> #include <QStringList> #include <QDebug> QList<QStringList> readCSV(const QString &filename) { QList<QStringList> result; QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "ファイルを開けません:" << filename; return result; } QTextStream in(&file); in.setCodec("UTF-8"); QString line; QStringList fields; bool inQuotes = false; QString field; while (!in.atEnd()) { QChar ch; in >> ch; if (inQuotes) { if (ch == '"') { if (in.peek() == '"') { field += ch; in >> ch; // 次の文字(二重引用符)を読み飛ばす } else { inQuotes = false; } } else { field += ch; } } else { if (ch == '"') { inQuotes = true; } else if (ch == ',' || ch == '\n' || in.atEnd()) { fields.append(field.trimmed()); field.clear(); if (ch == '\n' || in.atEnd()) { result.append(fields); fields.clear(); } } else { field += ch; } } } file.close(); return result; } </syntaxhighlight> <br><br> == CSVファイル (RFC4180準拠) の書き込み == RFC4180は、CSVファイルの標準的な形式を定義しており、これに従うことで互換性の高いCSV処理が可能になる。<br> <br> 以下の例では、RFC4180の規則に従ってCSVファイルを書き込んでいる。<br> * カンマ、引用符、改行を含むフィールドを適切にダブルクォートで囲む。 * フィールド内の引用符を二重引用符 ("") にエスケープする。 * 各行の終わりに改行を挿入する。 <br> <syntaxhighlight lang="c++"> #include <QFile> #include <QTextStream> #include <QStringList> #include <QDebug> bool writeCSV(const QString &filename, const QList<QStringList> &data) { QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "ファイルを開けません:" << filename; return false; } QTextStream out(&file); out.setCodec("UTF-8"); for (const QStringList &row : data) { for (int i = 0; i < row.size(); ++i) { QString field = row[i]; bool needQuotes = field.contains(QRegExp("[,\"\n\r]")); if (needQuotes) { out << '"' << field.replace("\"", "\"\"") << '"'; } else { out << field; } if (i < row.size() - 1) { out << ","; } } out << "\n"; } file.close(); return true; } </syntaxhighlight> <br><br> == ストリーミング処理を使用したCSVファイルの読み込み == * ストリーミング処理 *: ファイル全体を1度にメモリに読み込むのではなく、1レコードずつ処理する。 *: これにより、大規模なファイルでもメモリ使用量を抑えることができる。 * コールバック関数 *: 各レコードの処理をコールバック関数として渡すことにより、柔軟な処理が可能になる。 <br> <syntaxhighlight lang="c++"> #include <QFile> #include <QTextStream> #include <QStringList> #include <functional> #include <QDebug> enum class CSVError { NoError, FileOpenError, ReadError, ParseError }; class CSVReader { private: QString m_filename; public: CSVReader(const QString &filename) : m_filename(filename) {} CSVError readCSV(std::function<void(const QStringList&)> rowCallback) { QFile file(m_filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "ファイルのオープンに失敗: " << m_filename; return CSVError::FileOpenError; } QTextStream in(&file); in.setCodec("UTF-8"); QString line; QStringList fields; bool inQuotes = false; QString field; qint64 lineNumber = 0; while (!in.atEnd()) { QChar ch; in >> ch; if (in.status() != QTextStream::Ok) { qDebug() << "読み込みエラー:" << m_filename << "行:" << lineNumber; return CSVError::ReadError; } if (inQuotes) { if (ch == '"') { if (in.peek() == '"') { field += ch; in >> ch; // 次の文字(二重引用符)を読み飛ばす } else { inQuotes = false; } } else { field += ch; } } else { if (ch == '"') { inQuotes = true; } else if (ch == ',' || ch == '\n' || in.atEnd()) { fields.append(field.trimmed()); field.clear(); if (ch == '\n' || in.atEnd()) { rowCallback(fields); fields.clear(); lineNumber++; } } else { field += ch; } } } if (inQuotes) { qDebug() << "パースエラー: 閉じていない引用符があります。行:" << lineNumber; return CSVError::ParseError; } file.close(); return CSVError::NoError; } }; </syntaxhighlight> <br> <syntaxhighlight lang="c++"> int main() { CSVReader reader("example.csv"); CSVError error = reader.readCSV([](const QStringList& row) { // 各レコードの処理 qDebug() << row; }); switch (error) { case CSVError::NoError: qDebug() << "CSVファイルの読み込みに成功"; break; case CSVError::FileOpenError: qDebug() << "ファイルのオープンに失敗"; break; case CSVError::ReadError: qDebug() << "ファイルの読み込み中にエラーが発生"; break; case CSVError::ParseError: qDebug() << "CSVのパース中にエラーが発生"; break; } return 0; } </syntaxhighlight> <br><br> == ストリーミング処理を使用したCSVファイルの書き込み == 以下の例では、ストリーミング処理およびQtConcurrentを使用して非同期でCSVファイルを書き込みしている。<br> <br> * ストリーミング処理 *: QTextStreamクラスを使用して、データを逐次的に書き込む。 *: これにより、大規模なデータセットでもメモリ使用量を抑えることができる。 * 非同期処理 *: 書き込み処理をバックグラウンドで行うことにより、メインスレッドのブロッキングを防ぎ、アプリケーションの応答性を向上させることができる。 * キューイング *: enqueueRowメソッドを使用して、書き込む行をキューに追加する。 *: これにより、メインスレッドはブロックされることなく、次の処理に進むことができる。 * スレッドセーフ *: ミューテックスとウェイトコンディションを使用して、スレッド間の同期を行う。 <br> <u>※注意</u><br> * メモリ使用量 *: キューに大量のデータが蓄積される時、メモリ使用量が増大する可能性がある。 *: 必要に応じて、キューのサイズに制限を設けることを検討する。 * エラー処理 *: 以下の例では、エラーが発生してもすぐには検出されない。 *: リアルタイムでエラーを検出する場合は、追加機能が必要になる。 <br> <syntaxhighlight lang="c++"> #include <QFile> #include <QTextStream> #include <QtConcurrent> #include <QFuture> #include <QMutex> #include <QQueue> #include <QDebug> enum class CSVError { NoError, FileOpenError, WriteError }; class CSVWriter { private: QString m_filename; QFile m_file; QTextStream m_stream; QMutex m_mutex; QQueue<QStringList> m_queue; QWaitCondition m_condition; QFuture<void> m_future; bool m_isRunning; CSVError m_error = CSVError::NoError; private: void writeWorker() { while (true) { QStringList row; { QMutexLocker locker(&m_mutex); while (m_queue.isEmpty() && m_isRunning) { m_condition.wait(&m_mutex); } if (!m_isRunning && m_queue.isEmpty()) { break; } row = m_queue.dequeue(); } if (!writeRow(row)) { m_error = CSVError::WriteError; break; } } } bool writeRow(const QStringList &row) { for (int i = 0; i < row.size(); i++) { if (!writeField(row[i])) { return false; } if (i < row.size() - 1) { m_stream << ","; } } m_stream << "\n"; return m_stream.status() == QTextStream::Ok; } bool writeField(const QString &field) { bool needQuotes = field.contains(QRegExp("[,\"\n\r]")); if (needQuotes) { m_stream << '"' << field.replace("\"", "\"\"") << '"'; } else { m_stream << field; } return m_stream.status() == QTextStream::Ok; } public: CSVWriter(const QString &filename) : m_filename(filename), m_isRunning(false) {} ~CSVWriter() { if (m_isRunning) { waitForFinished(); } } CSVError open() { if (!m_file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "ファイルのオープンに失敗: " << m_filename; return CSVError::FileOpenError; } m_stream.setDevice(&m_file); m_stream.setCodec("UTF-8"); m_isRunning = true; m_future = QtConcurrent::run(&CSVWriter::writeWorker, this); return CSVError::NoError; } void enqueueRow(const QStringList &row) { QMutexLocker locker(&m_mutex); m_queue.enqueue(row); m_condition.wakeOne(); } void close() { { QMutexLocker locker(&m_mutex); m_isRunning = false; m_condition.wakeOne(); } waitForFinished(); m_file.close(); } void waitForFinished() { m_future.waitForFinished(); } bool hasError() const { return m_error != CSVError::NoError; } CSVError error() const { return m_error; } }; </syntaxhighlight> <br> <syntaxhighlight lang="c++"> int main() { CSVWriter writer("example.csv"); CSVError error = writer.open(); if (error != CSVError::NoError) { qDebug() << "ファイルのオープンに失敗"; return -1; } QStringList headers = {"Name", "Age", "City"}; writer.enqueueRow(headers); QList<QStringList> data = { {"John Doe", "30", "New York"}, {"Jane Smith", "25", "Los Angeles"}, {"Mike Johnson", "35", "Chicago"} }; for (const auto &row : data) { writer.enqueueRow(row); } writer.close(); if (writer.hasError()) { qDebug() << "CSVファイルの書き込み中にエラーが発生"; return -1; } qDebug() << "CSVファイルの書き込みに成功"; return 0; } </syntaxhighlight> <br><br> == 分割処理を使用したCSVファイルの読み込み == 以下の例では、RFC4180に準拠したCSVファイルを分割読み込みしている。<br> 分割処理は、大規模なCSVファイルを効率的に処理する場合に特に有効である。<br> <br> * 分割読み込み *: ファイルを1度に全て読み込むのではなく、指定されたチャンクサイズごとに読み込む。 *: これにより、大規模なファイルでもメモリ使用量を抑えることができる。 * 行番号の追跡 *: 各レコード番号を追跡して、コールバック関数に渡す。 *: これにより、エラーが発生した場合に正確な位置を特定することができる。 <br> <u>※注意</u><br> <u>チャンクサイズの選択はパフォーマンスに影響を与える可能性がある。</u><br> <u>適切なサイズは使用環境やファイルサイズによって異なる場合がある。</u><br> <br> <syntaxhighlight lang="c++"> #include <QFile> #include <QTextStream> #include <functional> #include <QDebug> enum class CSVError { NoError, FileOpenError, ReadError, ParseError }; class CSVReader { private: QString m_filename; qint64 m_chunkSize; private: QStringList parseLine(const QString &line) { QStringList fields; QString field; bool inQuotes = false; for (int i = 0; i < line.length(); i++) { if (line[i] == '"') { if (inQuotes && i + 1 < line.length() && line[i + 1] == '"') { field += '"'; i++; } else { inQuotes = !inQuotes; } } else if (line[i] == ',' && !inQuotes) { fields.append(field); field.clear(); } else { field += line[i]; } } fields.append(field); return fields; } public: CSVReader(const QString &filename, qint64 chunkSize = 1024 * 1024) : m_filename(filename), m_chunkSize(chunkSize) {} CSVError readCSV(std::function<bool(const QStringList&, qint64)> rowCallback) { QFile file(m_filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "ファイルのオープンに失敗: " << m_filename; return CSVError::FileOpenError; } QTextStream in(&file); in.setCodec("UTF-8"); QString buffer; QString remainder; qint64 lineNumber = 0; bool inQuotes = false; while (!in.atEnd()) { buffer = remainder + in.read(m_chunkSize); remainder.clear(); int start = 0; for (int i = 0; i < buffer.length(); i++) { if (buffer[i] == '"') { inQuotes = !inQuotes; } else if (buffer[i] == '\n' && !inQuotes) { QString line = buffer.mid(start, i - start); QStringList fields = parseLine(line); if (fields.isEmpty() && !line.isEmpty()) { qDebug() << "パースエラー: 行" << lineNumber + 1; return CSVError::ParseError; } if (!rowCallback(fields, lineNumber)) { // ユーザが処理を中断した場合 return CSVError::NoError; } start = i + 1; lineNumber++; } } remainder = buffer.mid(start); } if (!remainder.isEmpty()) { QStringList fields = parseLine(remainder); if (!fields.isEmpty()) { if (!rowCallback(fields, lineNumber)) { // ユーザが処理を中断した場合 return CSVError::NoError; } } else if (!remainder.trimmed().isEmpty()) { qDebug() << "パースエラー: 最終行"; return CSVError::ParseError; } } if (inQuotes) { qDebug() << "パースエラー: 閉じていない引用符があります"; return CSVError::ParseError; } return CSVError::NoError; } }; </syntaxhighlight> <br> <syntaxhighlight lang="c++"> int main() { CSVReader reader("example.csv"); CSVError error = reader.readCSV([](const QStringList& row, qint64 lineNumber) { qDebug() << "行" << lineNumber + 1 << ":" << row; // 例: 100行まで読み込みして中断 if (lineNumber >= 99) { qDebug() << "100行読み込んだので中断します"; return false; } return true; // 続けて読み込む }); switch (error) { case CSVError::NoError: qDebug() << "CSVファイルの読み込みに成功"; break; case CSVError::FileOpenError: qDebug() << "ファイルのオープンに失敗"; break; case CSVError::ReadError: qDebug() << "ファイルの読み込み中にエラーが発生"; break; case CSVError::ParseError: qDebug() << "CSVのパース中にエラーが発生"; break; } return 0; } </syntaxhighlight> <br><br> __FORCETOC__ [[カテゴリ:Qt]]
Qtの基礎 - CSVファイル
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse