Overview
Qt provides a unified file I/O API that works on all platforms — Windows, Linux, embedded Linux — with the same code.
QIODevice (base)
├── QFile ← files on disk
├── QBuffer ← in-memory I/O
├── QTcpSocket ← network I/O
└── QSerialPort ← serial port I/O
QTextStream ← text layer over any QIODevice
QDataStream ← binary layer over any QIODevice
QFile — Read a Text File
#include <QFile>
#include <QTextStream>
QFile file("/etc/hostname");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "Cannot open:" << file.errorString();
return;
}
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
qDebug() << line;
}
file.close();
QFile — Write a Text File
QFile out("/tmp/sensor_log.txt");
if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "Write failed:" << out.errorString();
return;
}
QTextStream stream(&out);
stream << "timestamp,temp,volt\n";
stream << "2026-03-15T10:00:00,36.5,3.30\n";
stream << "2026-03-15T10:00:01,36.7,3.31\n";
out.close();
Append Mode
QFile log("/var/log/device.log");
if (log.open(QIODevice::Append | QIODevice::Text)) {
QTextStream s(&log);
s << QDateTime::currentDateTime().toString(Qt::ISODate)
<< " INFO Device started\n";
}
QDataStream — Binary I/O
#include <QDataStream>
// Write binary
QFile binFile("config.bin");
binFile.open(QIODevice::WriteOnly);
QDataStream out(&binFile);
out.setVersion(QDataStream::Qt_6_0);
out << QString("device-001")
<< (float)36.5f
<< (quint32)115200
<< true;
binFile.close();
// Read binary
QFile inFile("config.bin");
inFile.open(QIODevice::ReadOnly);
QDataStream in(&inFile);
in.setVersion(QDataStream::Qt_6_0);
QString name; float temp; quint32 baud; bool active;
in >> name >> temp >> baud >> active;
qDebug() << name << temp << baud << active;
QDir — Directory Operations
#include <QDir>
QDir dir("/var/log/sensors");
// List files
QStringList logs = dir.entryList({"*.log"}, QDir::Files);
for (const QString &f : logs)
qDebug() << f;
// Create directory
if (!dir.exists())
dir.mkpath("."); // creates all parent dirs
// File paths
QString path = dir.absoluteFilePath("latest.log");
// Iterate recursively
QDirIterator it("/var/log", QStringList("*.csv"),
QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
qDebug() << it.next();
}
QFileInfo — File Metadata
#include <QFileInfo>
QFileInfo info("/etc/qt/config.ini");
qDebug() << info.exists(); // true/false
qDebug() << info.size(); // bytes
qDebug() << info.suffix(); // "ini"
qDebug() << info.baseName(); // "config"
qDebug() << info.absolutePath(); // "/etc/qt"
qDebug() << info.isReadable();
qDebug() << info.lastModified(); // QDateTime
QFileSystemWatcher — Monitor Changes
#include <QFileSystemWatcher>
QFileSystemWatcher *watcher = new QFileSystemWatcher(this);
watcher->addPath("/etc/device/config.ini");
watcher->addPath("/var/log/sensors");
connect(watcher, &QFileSystemWatcher::fileChanged,
[](const QString &path) {
qDebug() << "File changed:" << path;
// Re-read config
});
connect(watcher, &QFileSystemWatcher::directoryChanged,
[](const QString &path) {
qDebug() << "Directory changed:" << path;
});
Qt Resource System (.qrc)
The Qt Resource System embeds files inside the application binary — images, icons, configs, QML files — so they’re always available regardless of installation path.
1. Create resources.qrc
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/icons">
<file>connect.svg</file>
<file>disconnect.svg</file>
</qresource>
<qresource prefix="/config">
<file alias="defaults.ini">config/defaults.ini</file>
</qresource>
</RCC>
2. Add to CMakeLists.txt
qt_add_resources(MyApp "resources"
PREFIX "/"
FILES
icons/connect.svg
icons/disconnect.svg
config/defaults.ini
)
3. Use with : prefix
// Load embedded icon
QIcon icon(":/icons/connect.svg");
btn->setIcon(icon);
// Read embedded config file
QFile cfg(":/config/defaults.ini");
cfg.open(QIODevice::ReadOnly | QIODevice::Text);
QTextStream in(&cfg);
qDebug() << in.readAll();
Visual: File I/O Paths
┌───────────────────────────────────────────────────────┐
│ Qt File I/O │
│ │
│ QFile(":/icons/icon.svg") ← embedded in binary │
│ QFile("config.ini") ← relative to CWD │
│ QFile("/etc/device/config") ← absolute path │
│ QFile("C:/config.ini") ← Windows absolute │
│ │
│ QStandardPaths::writableLocation(...) │
│ AppDataLocation → /home/user/.config/AppName/ │
│ TempLocation → /tmp/ │
│ HomeLocation → /home/user/ │
└───────────────────────────────────────────────────────┘
QStandardPaths — Platform Paths
#include <QStandardPaths>
// Write config to platform-correct location
QString configDir = QStandardPaths::writableLocation(
QStandardPaths::AppConfigLocation);
QDir().mkpath(configDir);
QFile cfg(configDir + "/settings.ini");
cfg.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream s(&cfg);
s << "[uart]\nbaud=115200\n";
Lab 1 — CSV Sensor Logger
class CsvLogger : public QObject {
Q_OBJECT
QFile m_file;
QTextStream m_stream;
qint64 m_rowCount = 0;
public:
CsvLogger(const QString &path, QObject *p = nullptr)
: QObject(p), m_file(path)
{
if (!m_file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "Cannot open log:" << m_file.errorString();
return;
}
m_stream.setDevice(&m_file);
m_stream << "row,timestamp,temperature,voltage\n";
}
~CsvLogger() {
if (m_file.isOpen()) m_file.close();
}
public slots:
void log(float temp, float volt) {
++m_rowCount;
m_stream << m_rowCount << ","
<< QDateTime::currentDateTime().toString(Qt::ISODate) << ","
<< QString::number(temp, 'f', 2) << ","
<< QString::number(volt, 'f', 3) << "\n";
m_stream.flush();
}
qint64 rowCount() const { return m_rowCount; }
};
// Usage
CsvLogger logger("/var/log/sensors/data.csv");
connect(&sensor, &Sensor::dataReady, &logger, &CsvLogger::log);
Lab 2 — Binary Config File
struct DeviceConfig {
QString deviceId;
quint32 baudRate;
float sampleRate;
bool autoConnect;
};
void saveConfig(const QString &path, const DeviceConfig &cfg) {
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return;
QDataStream ds(&f);
ds.setVersion(QDataStream::Qt_6_0);
ds << cfg.deviceId << cfg.baudRate << cfg.sampleRate << cfg.autoConnect;
}
DeviceConfig loadConfig(const QString &path) {
DeviceConfig cfg{"uart-0", 115200, 10.0f, true};
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) return cfg;
QDataStream ds(&f);
ds.setVersion(QDataStream::Qt_6_0);
ds >> cfg.deviceId >> cfg.baudRate >> cfg.sampleRate >> cfg.autoConnect;
return cfg;
}
// Round-trip
DeviceConfig cfg{"uart-0", 115200, 100.0f, true};
saveConfig("/etc/device/config.bin", cfg);
auto loaded = loadConfig("/etc/device/config.bin");
qDebug() << loaded.deviceId << loaded.baudRate;
Interview Questions
Q1: What is the difference between QTextStream and QDataStream?
QTextStreamreads/writes human-readable text (UTF-8/locale encoded).QDataStreamreads/writes binary data in a platform-independent format — useful for config files, network protocols, and firmware images.
Q2: How do Qt resources (: prefix) differ from regular files?
Resources are compiled into the application binary. They’re always available regardless of the working directory or installation path. They’re read-only and can’t be modified at runtime. Ideal for icons, default configs, and QML files.
Q3: What does QFile::flush() vs QFile::close() do?
flush()writes buffered data to the OS without closing the file — useful for partial writes in long-running loggers.close()flushes and releases the file handle. Always close files when done.
Q4: How do you find platform-correct write locations on any OS?
Use
QStandardPaths::writableLocation()with types likeAppConfigLocation,AppDataLocation, orTempLocation. This returns the correct platform path automatically on Windows, Linux, macOS, and embedded.
Q5: What is the risk of not setting QDataStream::setVersion()?
The stream format may vary between Qt versions. If writer and reader use different Qt versions and no version is set, reading may fail silently. Always call
setVersion(QDataStream::Qt_6_0)(or appropriate version) on both sides.
Application: Firmware Update Logger
class FirmwareUpdateLogger : public QObject {
Q_OBJECT
struct UpdateRecord {
QDateTime timestamp;
QString firmwarePath;
quint64 size;
bool success;
QString errorMsg;
};
QList<UpdateRecord> m_history;
QString m_historyPath;
public:
FirmwareUpdateLogger(const QString &path, QObject *p = nullptr)
: QObject(p), m_historyPath(path) { load(); }
void recordUpdate(const QString &fwPath, quint64 size,
bool success, const QString &err = {})
{
m_history.append({
QDateTime::currentDateTime(), fwPath, size, success, err
});
save();
emit recordAdded(m_history.last().success);
}
void printHistory() const {
qDebug() << "=== Firmware Update History ===";
for (const auto &r : m_history) {
qDebug() << r.timestamp.toString(Qt::ISODate)
<< (r.success ? "OK" : "FAIL")
<< r.firmwarePath
<< r.size << "bytes"
<< (r.errorMsg.isEmpty() ? "" : "(" + r.errorMsg + ")");
}
}
signals:
void recordAdded(bool success);
private:
void save() {
QFile f(m_historyPath);
if (!f.open(QIODevice::WriteOnly)) return;
QDataStream ds(&f);
ds.setVersion(QDataStream::Qt_6_0);
ds << (qint32)m_history.size();
for (const auto &r : m_history)
ds << r.timestamp << r.firmwarePath << r.size << r.success << r.errorMsg;
}
void load() {
QFile f(m_historyPath);
if (!f.open(QIODevice::ReadOnly)) return;
QDataStream ds(&f);
ds.setVersion(QDataStream::Qt_6_0);
qint32 n; ds >> n;
for (int i = 0; i < n; ++i) {
UpdateRecord r;
ds >> r.timestamp >> r.firmwarePath >> r.size >> r.success >> r.errorMsg;
m_history.append(r);
}
}
};
References
| Resource | Link |
|---|---|
| Qt Docs: QFile | https://doc.qt.io/qt-6/qfile.html |
| Qt Docs: QTextStream | https://doc.qt.io/qt-6/qtextstream.html |
| Qt Docs: QDataStream | https://doc.qt.io/qt-6/qdatastream.html |
| Qt Docs: Resource System | https://doc.qt.io/qt-6/resources.html |
| Qt Docs: QStandardPaths | https://doc.qt.io/qt-6/qstandardpaths.html |
Next tutorial → Custom Widgets & QPainter