「Qtの基礎 - ファイル」の版間の差分
12行目: | 12行目: | ||
QString strAppDir = QCoreApplication::applicationDirPath(); | QString strAppDir = QCoreApplication::applicationDirPath(); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br><br> | |||
== QIODevice::Textオプション == | |||
<code>QIODevice::Text</code>を指定する場合、改行コードは自動的にプラットフォームに合わせて変換される。<br> | |||
<br><br> | <br><br> | ||
2025年1月6日 (月) 06:43時点における最新版
概要
ソフトウェアにおいて、ファイルの操作は非常に重要な項目である。
テキストファイルやバイナリファイル、画像ファイル等、各フォーマットにしたがって、ファイルの入出力を行う必要がある。
ファイルフォーマットは、公開・非公開のものがあり、各フォーマットを理解してファイルの入出力を行うことは難しいが、
ライブラリを使用する場合、簡単に入出力を行うことができる。
実行ファイルがあるディレクトリ
実行ファイルが存在するディレクトリを取得するには、以下のように記述する。
QString strAppDir = QCoreApplication::applicationDirPath();
QIODevice::Textオプション
QIODevice::Text
を指定する場合、改行コードは自動的にプラットフォームに合わせて変換される。
ファイルの読み込み
テキストファイルの一括読み込み
以下の例では、一括で読み込んだテキストデータを改行コードで分割して、行数分だけ回して処理している。
ファイルのオープンに失敗した場合、エラーメッセージをデバッグ出力して終了する。
#include <QTextStream>
#include <QFile>
void MainWindow::readTextFileAll(const QString &FileName)
{
QFile File(FileName);
if(!File.open(QIODevice::ReadOnly))
{
QString strErrMsg = "File(" + QFileInfo(File).fileName() + ") Open Error: " + File.errorString();
qDebug << strErrMsg;
return;
}
QTextStream InStream(&File);
QString strTextData = InStream.readAll();
File.close();
QList<QString> Lines = istrTextData.split("\n");
foreach (QString Line, Lines)
{
qDebug() << line;
break;
}
}
テキストファイルの1行読み込み
以下の例では、テキストストリームから1行ずつ読み込んで処理している。
ファイルを閉じた場合、テキストストリームが使用できなくなるため、最後にファイルを閉じる。
#include <QTextStream>
#include <QFile>
void MainWindow::readTextFileLine(const QString &FileName)
{
QFile File(FileName);
if(!File.open(QIODevice::ReadOnly))
{
QString strErrMsg = "File(" + QFileInfo(File).fileName() + ") Open Error: " + File.errorString();
qDebug() << strErrMsg;
return;
}
QTextStream InStream(&File);
while(!InStream.atEnd())
{
qDebug() << InStream.readLine();
break;
}
File.close();
}
文字列の検索
特定の文字列を検索する場合は、テキストファイルを読み込みながら検索する文字列が各行に含まれているかを確認する。
以下の例では、各行が"hoge"という文字列と一致するかどうかを確認している。
一致する行が存在する場合、その行を出力して処理を終了して、一致する行が存在しない場合はその旨を出力する。
bool SearchString(const QString &filePath, const QString &str)
{
QFile File(filePath);
if (!File.open(QIODevice::ReadOnly | QIODevice::Text)) {
std::cerr << QString("ファイルのオープンに失敗 : %1").arg(File.errorString()).toStdString() << std::endl;
return false;
}
QTextStream in(&File);
while (!in.atEnd()) {
QString line = in.readLine();
if (line == str) {
std::cout << QString("%1 が見つかりました").arg(str).toStdString() << std::endl;
File.close();
return true;
}
}
std::cout << QString("%1 は見つかりませんでした").arg(str).toStdString() << std::endl;
File.close();
return false;
}
バイナリデータの読み込み
バイナリデータを読み込む場合は、QFile
クラスのreadLine
メソッドまたはreadAll
メソッドを使用する。
QDataStreamを使用してバイナリデータを書き出す場合、以下の問題が生じる。
QDataStreamの仕様において、バイナリデータを書き込む場合、先頭にバイナリデータのサイズが追加される。
これは、QDataStreamはバイナリデータを直列化するためのものだからである。
#include <QFile>
QFile File(FileName);
if(!File.open(QIODevice::ReadOnly))
{
QString strErrMsg = "File(" + QFileInfo(File).fileName() + ") Open Error: " + File.errorString();
qDebug() << strErrMsg;
return;
}
QByteArray ByAryData = File.readAll();
File.close();
ファイルの書き込み
テキストファイルの一括書き込み
以下の例では、テキストデータをファイルに出力している。
既にファイルが存在する場合は、エラーメッセージを表示して終了する。
void MainWindow::writeTextFile(QString FileName, QString strOutputData)
{
QString strErrMsg = "";
QFileInfo FileInfo(FileName);
QFile File(FileName);
if(!File.open(QIODevice::WriteOnly))
{
QString strErrMsg = "File(" + FileInfo.fileName() + ") Open Error: " + File.errorString();
qDebug() << strErrMsg;
return;
}
QTextStream OutStream(&File);
OutStream << strOutputData;
File.close();
}
以下の例では、QTextStreamクラスを使用して書式を設定している。
// 浮動小数
QFile file(strFName);
QTextStream OutStream(&file);
// 浮動小数 e+形式
OutStream.setRealNumberNotation(QTextStream::ScientificNotation);
// 浮動小数の桁数を固定
OutStream.setRealNumberNotation(QTextStream::FixedNotation);
// 浮動小数点の桁数を設定
OutStream.setRealNumberPrecision(3);
列ごとに列幅を設定する場合は、1つ列を出力するごとに設定する必要がある。
QFile file(strFName);
QTextStream OutStream(&file);
// 列を右寄せに設定
OutStream.setFieldAlignment(QTextStream::AlignRight);
// 1列目の文字数を設定
OutStream.setFieldWidth(20);
OutStream << "1列目"
// 2列目の文字数を設定
// 最終列の改行では、std::endlを使用してはならない
OutStream.setFieldWidth(40);
OutStream << "2列目" + "\n" << flush;
※注意
最終列の改行において、std::endlを使用する場合、それが1つの列として扱われるため、
空列が1つ余計に追加されて余分な空白が末尾に追加されてしまう。
以下の例では、出力エンコーディングを指定する。
// 例. UTF-8にする場合
QFile file(strFName);
QTextStream OutStream(&file);
OutStream.setCodec("UTF-8");
出力エンコードをShift-JISに設定する場合、"~"が文字化けする時がある。
詳細を知りたい場合は、こちらのWebサイトを参照すること。
http://park3.wakwak.com/~ozashin/sw_tips/webapp_tips/sjis_charset.html
この文字化けを直すには、以下のように記述する。(Winodwsのみ)
QFile file(strFName);
QTextStream OutStream(&file);
OutStream << str.replace(QChar(0x301C), QChar(0xFF5E));
以下の例では、ファイルの先頭にテキストを追記している。
- まず、既存のファイルの内容を読み込み、その内容をメモリ(
QString
クラスやQByteArray
)に保存する。 - 次に、追加する内容と元のファイル内容を結合させる。
- 最後に、新しい内容でファイルを上書きする。
#include <QFile>
#include <QTextStream>
QFile File("/tmp/sample.txt");
if (!File.open(QIODevice::ReadOnly | QIODevice::Text)) {
std::cerr << QString("Failed to open file for reading.").toStdString() << std::endl;
return -1;
}
// 元のファイル内容を読み込む
QString oldText;
QTextStream in(&File);
oldText = in.readAll();
File.close();
// 追加する内容と元のファイル内容を結合させる
QString newText = "hoge" + "\n" + oldText;
// ファイルに新しい内容を書き込む
if (!File.open(QIODevice::WriteOnly | QIODevice::Text)) {
std::cerr << QString("Failed to open file for writing.").toStdString() << std::endl;
return -1;
}
QTextStream out(&File);
out << newText;
File.close();
バイナリデータの書き込み
バイナリデータを書き込む場合は、QFile
クラスのwrite
メソッドを使用する。
QDataStreamを使用してバイナリデータを書き出す場合、以下の問題が生じる。
QDataStreamの仕様において、バイナリデータを書き込む場合、先頭にバイナリデータのサイズが追加される。
これは、QDataStreamはバイナリデータを直列化するためのものだからである。
#include <QFile>
QFile File(FileName);
if(!File.open(QIODevice::WriteOnly))
{
QString strErrMsg = "File(" + QFileInfo(File).fileName() + ") Open Error: " + File.errorString();
qDebug() << strErrMsg;
return;
}
QByteArray ByAryData = QString("48656c6c6f").toUtf8().toHex();
File.write(ByAryData);
File.close();
また、QByteArray
クラスは、以下のように宣言することができる。
// 宣言 1
QByteArray data_1 = {0x0c, 0x06, 0x04, 0x04, 0x02, 0x00};
// 宣言 2
QByteArray data_2 = QByteArray("\x0c\x06\x04\x04\x02\x00", 6);
// 宣言 3
QByteArray data_3;
data_3.append((char)0x0c);
data_3.append((char)0x06);
data_3.append((char)0x04);
data_3.append((char)0x04);
data_3.append((char)0x02);
data_3.append((char)0x00);
data_3.append((char)0xFF);
std::cout << data_3.size() << std::endl;
std::cout << data_3.toHex().toStdString().c_str() << std::endl;
// 宣言 4
char v1 = 0x01,
v2 = 0x02;
QByteArray data_4(std::begin<char>({0x00, v1, v2, v2, 0x05, 0x06, '\xf3'}), 7);
ファイル情報
ファイルの存在確認
ファイルが存在すればtrue
、存在しなければfalse
を返す。
#include <QFileInfo>
bool MainWindow::FileExists(const QString &FileName)
{
QFileInfo FileInfo(FileName);
return FileInfo.exists();
}
また、QFile
クラスを使用する場合は、以下のように記述する。
if(QFile::exists("<ファイルのパス>"))
{ // ファイルが存在する場合
}
else
{ // ファイルが存在しない場合
}
ファイルタイプの取得
ファイルタイプの確認において、ファイルは1、ディレクトリは2、その他は0を返す。
int MainWindow::FileType(QString FileName)
{
QFileInfo fileInfo(FileName);
if(FileInfo.isFile())
{
reuturn 1;
}
else if(FileInfo.isDir())
{
reuturn 2;
}
return 0;
}
ファイル情報の取得
以下に、ファイル情報の取得例を示す。
QFileInfo f1("/home/<ユーザ名>/memo.txt");
QFileInfo f2("/home/<ユーザ名>/tmp/arc.tar.gz");
QFileInfo f3("/home/<ユーザ名>/memo.txt_シンボリックリンク");
Windowsのリンクファイルも、リンク先のファイルサイズを返す。
取得する情報 | 使用例 | 出力 |
---|---|---|
ファイルのフルパス | f1.absoluteFilePath() f2.absoluteFilePath() |
"/home/<ユーザ名>/memo.txt" "/home/<ユーザ名>/tmp/arc.tar.gz" |
ファイルが存在するディレクトリのフルパス | f1.absolutePath() f2.absolutePath() |
"/home/<ユーザ名>" "/home/<ユーザ名>/tmp" |
ファイル名のみ | f1.fileName() f2.fileName() |
"memo.txt" "arc.tar.gz" |
ベース名 | f1.baseName() f2.baseName() |
"memo" "arc" |
完全なベース名 | f1.completeBaseName() f2.completeBaseName() |
"memo" "arc.tar" |
拡張子 | f1.suffix() f2.suffix() |
"txt" "gz" |
完全な拡張子 | f1.completeSuffix() f2.completeSuffix() |
"txt" "tar.gz" |
リンクの絶対パス | f3.symLinkTarget() | "/home/<ユーザ名>/memo.txt" |
作成日 | f1.created() | QDateTime |
更新日 | f1.lastModified() | QDateTime |
アクセス日 | f1.lastRead() | QDateTime |
サイズ | f1.size() f3.size() |
5590 5590 |
ファイル情報の確認
以下に、ファイル情報の確認方法を示す。
QFileInfo FileInfo("チェックするパス");
用途 | 使用例 | 出力 |
---|---|---|
絶対パスか | FileInfo.isAbsolute() | true: 絶対パス false: 他 |
相対パス | FileInfo.isRelative() | true: 相対パス false: 他 |
ルートディレクトリに存在するか | FileInfo.isRoot() | true: ある false: ない |
ファイルか | FileInfo.isFile() | true: ファイル false: 他 |
ディレクトリか | FileInfo.isDir() | true: ディレクトリ false: 他 |
隠しファイルか | FileInfo.isHidden() | true: 隠しファイル false: 他 |
リンクファイルか | FileInfo.isSymLink() | true: リンクファイル false: 他 |
読み込み可能か | FileInfo.isReadable() | true: 可能 false: 不可 |
書き込み可能か | FileInfo.isWritable() | true: 可能 false: 不可 |
実行可能か | FileInfo.isExecutable() | true: 可能 false: 不可 |
ファイルの検索
QDirを使用する
QString path = QApplication::applicationDirPath() + "/images/";
QStringList nameFilters;
nameFilters.append("*.png");
nameFilters.append("*.jpg");
QDir dir(path);
QStringList entry = dir.entryList(nameFilters, QDir::Files);
for(QString file : entry)
{ // 検索して見つかったファイル群
qDebug() << dir.filePath(file);
}
QDirIteratorを使用する
QString path = QApplication::applicationDirPath() + "/images/";
QStringList nameFilters;
nameFilters.append("*.png");
nameFilters.append("*.jpg");
QDirIterator itDirs(path, nameFilters, QDir::Files);
while(itDirs.hasNext())
{
QString file = itDirs.next();
qDebug() << file;
}
ファイルの再帰検索
再帰呼び出しを使用して、サブディレクトリのファイル名まで取得する。
各ディレクトリごとに再帰処理を行い、ファイルのパスをリストに追加している。
QStringList MainWindow::SearchFiles(const QString &Path)
{
QDir dir(Path);
QStringList list;
foreach(QString File, dir.entryList(QDir::Files))
{ // ディレクトリ直下に存在するファイルの探索(深さ1のファイル探索)
list.append(Path + QDir::separator() + File);
}
foreach(QString subDir, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
{ // サブディレクトリに存在するファイルの探索
list = list << SearchFiles(Path + QDir::separator() + subDir);
}
return list;
}
再帰呼び出しを使用せずに、サブディレクトリまで検索することもできる。
QString path = QApplication::applicationDirPath() + "/images/";
QStringList nameFilters;
nameFilters.append("*.png");
nameFilters.append("*.jpg");
QDirIterator itDirs(path, nameFilters, QDir::Files, QDirIterator::Subdirectories);
while(itDirs.hasNext())
{
QString file = itDirs.next();
qDebug() << file;
}
一時ファイル
一時ファイルを作成するには、QTemporaryFile
クラスを使用する。
QFile
クラスと異なる点は、以下の通りである。
- 常に読み書きモードで開く。
- ファイルパスが衝突しない。
- デストラクタが呼び出されたら自動的に削除される。
特に、ファイル名には重複した名前が付かない仕組みになっている。(ファイルパスが衝突しないことは重要である)
ファイル名を重複しないように、"<ソフトウェア名>.<xxx>"のような名前を付けて、システムディレクトリに保存される。
以下の例では、既存のファイルをシステムディレクトリの一時ファイルとしてコピーしている。
一時ファイルのパスは、fileTemplate
メソッドから取得できる。
(setFileTemplate
メソッドを使用して、一時ファイルのパスを変更することもできる)
他は、QFile
クラスの使用方法と同様である。
QTemporaryFile TmpFile;
QFile::copy("<既存ファイルのパス>", TmpFile.fileTemplate());
TmpFile.open();
// ...略
メモリマップファイル
メモリマップファイルとは、メモリ空間の一部のように直接読み書きできるファイルのことである。
メモリマップファイルのメリットを以下に示す。
- 高速にアクセスすることが可能である。
- メモリ内容を書き換えることにより、ファイルの内容を更新できる。
- 大きなサイズのファイルを扱う場合、使用するメモリ量が少ない。
メモリマッピングには、QFileDevice
クラスのmap
メソッドを使用する。
QFile
クラスを使用して、ファイルを作成または開く。map
メソッドを使用して、ファイルの内容をメモリにマッピングする。
マッピングに成功した場合、ファイルのメモリ空間をunsigned char
型の配列として返す。
それを介して、ファイルを読み書きすることが可能となる。- マッピングした内容を読み書きする。
- メモリをアンマッピングする。
QFile
クラスのデストラクタが呼ばれた場合、または、同じインスタンスで新しいファイルを開いた場合は、自動的にアンマッピングされる。
以下の例では、既存のファイルを開いて10文字目の文字を書き換えている。
// ファイルを開く
QFile file("Sample.txt");
file.open(QFile::ReadWrite);
// ファイルの内容をメモリにマッピング
unsigned char *pMem = file.map(0, file.size());
// 10文字目を書き換える
pMem[10] = 'A';
// メモリをアンマッピング
file.unmap(memory);
// ファイルを閉じる
file.close();
ファイルパス
絶対パスの検証
ファイルのパスがファイルシステム上で有効な形式を持っているかどうか (例: 不正な文字、パスの構造がシステムの要件を満たしているか等) を検証する。
Qtでは、有効なパスが有効かどうかを検証するメソッドは存在しない。
しかし、QFileInfo
クラスおよびQDir
クラスを使用して間接的にパスの妥当性を検証することができる。
#include <QFileInfo>
#include <QDir>
// QFileInfoを使ってパスの情報を取得
QFileInfo FileInfo("/tmp/sample.txt");
// ファイル名に使用できない文字が含まれているかどうかを確認
QString fileName = FileInfo.fileName();
if (fileName.isEmpty() || fileName.contains(QRegExp("[\/\\0]"))) {
// ファイル名に不適切な文字が含まれている場合
return false;
}
// パスが絶対パスかどうかを確認 (相対パスは不適切とする)
if (!FileInfo.isAbsolute()) {
return false;
}
相対パスの確認
相対パスの場合、基準となるディレクトリからの解決が可能かどうかを確認する。
相対パスは特定のディレクトリを基準として解釈されるため、その基準ディレクトリから相対パスを解決 (絶対パスに変換) することにより、パスが想定通りの形式であるかを検証できる。
以下の例では、指定された基準ディレクトリ(basePath)から相対パス(relativePath)を解決して絶対パスに変換して、その絶対パスに対してファイル名が適切な形式を持つかどうか等の検証を行っている。
実際のシナリオでは、更に詳細な検証が必要になるが、このように相対パスを絶対パスに変換して検証することになる。
#include <QDir>
QString basePath = "<基準ディレクトリ>";
QString relativePath = "<相対パス>";
QDir baseDir(basePath);
QString resolvedPath = baseDir.absoluteFilePath(relativePath);
// 解決されたパスを確認 (不正な文字等)
QFileInfo FileInfo(resolvedPath);
QString fileName = FileInfo.fileName();
if (fileName.isEmpty() || fileName.contains(QRegExp("[\/\\0]"))) {
// ファイル名に不適切な文字が含まれている場合
return false;
}