在Qt中,QFile
、QByteArray
、QDataStream
和QTextStream
是常用的文件和数据处理类。
主要功能和区别
QFile
:
QFile
是用于读写文本和二进制文件以及资源的I/O设备。可以单独使用QFile
,或者更方便地与QTextStream
或QDataStream
一起使用。
通常在构造函数中传入文件名,但也可以随时使用setFileName()
进行设置。QFile
期望文件分隔符为’/‘,不论操作系统为何。不支持使用分隔符(例如’')。
可以使用exists()
检查文件是否存在,并使用remove()
删除文件。(更高级的文件系统相关操作由QFileInfo
和QDir
提供。)
文件使用open()
打开,使用close()
关闭,并使用flush()
刷新。通常使用QDataStream
或QTextStream
进行数据的读取和写入,但也可以使用QIODevice
继承的函数read()
、readLine()
、readAll()
、write()
进行操作。QFile还继承了getChar()、putChar()和ungetChar(),可以逐个字符地进行操作。
使用size()可以获取文件的大小。可以使用pos()获取当前文件位置,或者使用seek()移动到新的文件位置。如果已经到达文件末尾,atEnd()返回true。
QByteArray
:
QByteArray
类提供了一个字节数组。
QByteArray
可用于存储原始字节(包括’\0’)和传统的8位’\0’终止的字符串。使用QByteArray
比使用const char *
更方便。在幕后,它始终确保数据后跟’\0’终止符,并使用隐式共享(写时复制)来减少内存使用和避免无谓的数据复制。
除了QByteArray
,Qt还提供了QString类用于存储字符串数据。对于大多数情况,应使用QString类。它存储16位Unicode字符,使得在应用程序中存储非ASCII/非Latin-1字符变得容易。此外,在Qt API中广泛使用QString。QByteArray
适用的两种主要情况是当需要存储原始二进制数据时,以及内存保护至关重要的情况(例如,在Qt for Embedded Linux中)。
初始化QByteArray的一种方法是将const char *传递给其构造函数。例如,以下代码创建一个包含数据"Hello"的大小为5的字节数组:
QByteArray ba("Hello");
尽管size()为5,字节数组还在末尾保留了一个额外的’\0’字符,以便在使用需要指向底层数据的指针的函数(例如调用data())时,所指向的数据保证是以’\0’结尾的。
QByteArray会对const char *数据进行深拷贝,因此您可以稍后进行修改而不会出现副作用。(如果出于性能原因,您不想对字符数据进行深拷贝,请改用QByteArray::fromRawData()。)
另一种方法是使用resize()设置数组的大小,并逐个字节初始化数据。QByteArray使用基于0的索引,就像C++数组一样。要访问特定索引位置的字节,可以使用operator。对于非const字节数组,operator返回一个字节的引用,可以在赋值的左侧使用。例如:
QByteArray ba;
ba.resize(5);
ba[0] = 0x3c;
ba[1] = 0xb8;
ba[2] = 0x64;
ba[3] = 0x18;
ba[4] = 0xca;
对于只读访问,使用at()的替代语法:
for (int i = 0; i < ba.size(); ++i) {
if (ba.at(i) >= 'a' && ba.at(i) <= 'f')
cout << "Found character in range [a-f]" << endl;
}
at()可能比operator更快,因为它永远不会导致深拷贝发生。
要一次提取多个字节,请使用left()
、right()
或mid()
。
QByteArray可以嵌入’\0’字节。size()函数始终返回整个数组的大小,包括嵌入的’\0’字节,但不包括QByteArray添加的终止’\0’。例如:
QByteArray ba1("ca\0r\0t");
ba1.size(); // 返回2。
ba1.constData(); // 返回带有终止\0的"ca"。
QByteArray ba2("ca\0r\0t", 3);
ba2.size(); // 返回3。
ba2.constData(); // 返回带有终止\0的"ca\0"。
QByteArray ba3("ca\0r\0t", 4);
ba3.size(); // 返回4。
ba3.constData(); // 返回带有终止\0的"ca\0r"。
const char cart[] = {'c', 'a', '\0', 'r', '\0', 't'};
QByteArray ba4(QByteArray::fromRawData(cart, 6));
ba4.size(); // 返回6。
ba4.constData(); // 返回不带终止\0的"ca\0r\0t"。
如果要获取数据长度,直到但不包括第一个’\0’字符,可以在字节数组上调用qstrlen()。
在调用resize()后,新分配的字节的值是未定义的。要将所有字节设置为特定值,请调用fill()。
要获取指向实际字符数据的指针,请调用data()或constData()。这些函数返回指向数据开头的指针。该指针保证在QByteArray上调用非const函数之前保持有效。还保证除非QByteArray是从原始数据创建的,否则数据以’\0’字节结尾。QByteArray自动提供此’\0’字节,并且不计入size()中。
QByteArray提供了以下基本函数来修改字节数据:append()、prepend()、insert()、replace()和remove()。例如:
QByteArray x("and");
x.prepend("rock "); // x == "rock and"
x.append(" roll"); // x == "rock and roll"
x.replace(5, 3, "&"); // x == "rock & roll"
replace()
和remove()
函数的前两个参数是要开始擦除的位置和应该擦除的字节数。
当将数据追加到非空数组时数组将被重新分配并将新数据复制到其中。您可以通过调用reserve()来避免此行为,它预分配一定数量的内存。您还可以调用capacity()
来了解QByteArray实际分配了多少内存。追加到空数组中的数据不会被制。
经常要求从字节数组中删除空白字符(‘\n’、‘\t’、''等)。如果要从QByteArray两端删除空白字符,请使用trimmed()
。如果要从QByteArray两端删除空白字符,并且在字节数组中多个连续的空白字符替换为单个空格字符,请使用simplified()。
如果要在QByteArray中查找特定字符或子字符串的所有出现,可以使用indexOf()或lastIndexOf()。前者从给定的索引位置向前搜索,后者向后搜索。如果找到了字符或子字符串,两者都返回其索引位置;否则,它们返回-1。例如,以下是查找特定子字符串的典型循环:
QByteArray ba("We must be <b>bold</b>, very <b>bold</b>");
int j = 0;
while ((j = ba.indexOf("<b>", j)) != -1) {
cout << "Found <b> tag at index position " << j << endl;
++j;
}
如果只是想检查一个QByteArray
是否包含特定字符或子字符串,请使用contains()
。如果想要找出特定字符或子字符串在字节数组中出现的次数,请使用count()。如果想要用另一个值替换所有出现的特定值,请使用带有两个参数的replace()重载之一。
可以使用诸如operator<(), operator<=(), operator==(), operator>=()等重载运算符来比较QByteArray。比较仅基于字符的数值值,非常快速,但不符合人的预期。对于排序用户界面字符串,QString::localeAwareCompare()是更好的选择。
由于历史原因,QByteArray
区分空字节数组和空字节数组。空字节数组是通过使用QByteArray的默认构造函数或通过将(const char *)0传递给构造函数进行初始化的字节数组。大小为0的任何字节数组都是空字节数组。空字节数组不一定是null字节数组:
QByteArray().isNull(); // 返回true
QByteArray().isEmpty(); // 返回true
QByteArray("").isNull(); // 返回false
QByteArray("").isEmpty(); // 返回true
QByteArray("abc").isNull(); // 返回false
QByteArray("abc").isEmpty(); // 返回false
除了isNull()函数外,所有其他函数都将null字节数组视为与空字节数组相同。例如,对于null字节数组,data()返回指向 ‘\0’ 字符的指针(非空指针),并且QByteArray()与QByteArray(“”)相等。我们建议始终使用isEmpty(),避免使用isNull()。
QDataStream
:
QDataStream
类提供了将二进制数据序列化到QIODevice
的功能。
数据流是一个二进制编码信息流,完全独立于主机计算机的操作系统、CPU或字节顺序。例如,由Windows PC编写的数据流可以被运行Solaris系统的Sun SPARC读取。
您还可以使用数据流来读写原始的未编码的二进制数据。如果您需要一个"解析"输入流,请参见QTextStream
。
QDataStream
类实现了C++基本数据类型(如char、short、int、char *等)的序列化。更杂数据的序列化是通过将数据分解为基本单元来完成的。
数据流与QIODevice紧密配合。QIODevice代表可以从中读取数据或向其写入数据的输入/输出介质。QFile类是一个I/O设备的示例。
示例(将二进制数据写入数据流):
QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file); // 将数据序列化到文件中
out << QString("the answer is"); // 序列化字符串
out << (qint32)42; // 序列化整数
示例(从数据流读取二进制数据):
QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file); // 从文件中读取已序列化的数据
QString str;
qint32 a;
in >> str >> a; // 提取"the answer is"和42
每个写入流中的项目都以预定义的二进制格式写入,该格式取决于项目的类型。支持的Qt类型包括QBrush
、QColor、QDateTime、QFont、QPixmap、QString、QVariant等。有关支持数据流的所有Qt类型的完整列表,请参见序列化Qt数据类型。
对于整数,最好始终将其强制转换为Qt整数类型进行写入,并读取回到相同的Qt整数类型中。这样可以确保您获得所需的整数大小,并将您与编译器和平台的差异隔离开来。
举个例子,一个char *字符串被写为一个32位整数,该整数等于字符串长度,包括’\0’字节,后跟字符串的所有字符,包括’\0’字节。当读取一个char *字符串时,首先读取4个字节以创建32位长度值,然后读取相同数量的字符来创建包含’\0’终止符的char *字符串。
初始的I/O设备通常在构造函数中设置,但可以使用setDevice()
进行更改。如果已到达数据的末尾(或者没有设置I/O设备),atEnd()将返回true。
版本控制
自Qt 1.0以来,QDataStream
的二进制格式已发生了演变,并且可能会继续着Qt的变化而发展。在输入或输出复杂类型,非常重要的一点是确保在读取和写入时使用相同版本的(version())。如果您需要前向和后向兼容性,您可以在应用程序中硬编码版本号:
stream.setVersion(QDataStream::Qt_4_0);
如果您正在生成一种新的二进制数据格式,例如您的应用程序创建的文档文件格式,您可以使用QDataStream以便于移植的格式写入数据。通常,您会写一个简短的头部,包含一个标识字符串和一个版本号,以便为将来的扩展留出空间。例如:
QFile file("file.xxx");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
// 写入包含"魔术数字"和版本号的头部
out << (quint32)0xA0B0C0D0;
out << (qint32)123;
out.setVersion(QDataStream::Qt_4_0);
//入数据
out << lots_of_interest_data;
然后使用以下代码进行读取:
QFile file("file.xxx");
file.open(QIODevice::ReadOnly);
QDataStream (&infile);
// 读取并检查头部
quint32 magic;
in >> magic;
if (magic != 0xA0B0C0D0)
return XXX_BAD_FILE_FORMAT;
// 读取版本号
qint32 version;
in >> version;
if (version < 100)
return XXX_FILE_TOO_OLD;
if (version > 123)
return XXX_BAD_FILE_TOO_NEW;
if (version <= 110)
in.setVersion(QDataStream::Qt_3_2);
else
in.setVersion(QDataStream::Qt_4_0);
// 取数据
in >> lots_of_interesting_data;
if (version >= 120)
in >> data_new_in_XXX_version_1_2;
in >> other_interesting_data;
在序列化数据时,您可以选择使用哪种字节顺序。默认设置为big endian(最高位在前)。将其更改为little endian会破坏可移植性(除非读取方也更改为little endian)。我们建议保持此设置,除非有特殊需求。
读取和写入原始二进制数据
您可能希望直接从数据流中读取/写入自己的原始二进制数据。可以使用readRawData()
将数据从流中读取到预先分配的char *
中。类似地,可以使用writeRawData()
将数据写入到流中。注意,数据的任何编码/解码都必须由您自己完成。
另一对类似的函数是readBytes()
和writeBytes()
。与原始函数的不同之处在于:readBytes()读取一个quint32,将其视为要读取的长度,然后将该数量的字节读入预先分配的char *中;writeBytes()写入包含数据长度的quint32,然后是数据本身。请注意,数据的任何编码/解码(除了长度quint32之外)都必须由您自己完成。
QTextStream
:
QTextStream
类提供了一个方便的界面,用于读写文本。
QTextStream
可以操作QIODevice
、QByteArray
或QString
。使用QTextStream的流操作符,您可以方便地读写单词、行和数字。对于生成文本,QTextStream支持字段填充和对齐的格式选项,以及数字的格式化。示例:
QFile data("output.txt");
if (data.open(QFile::WriteOnly | QFile::Truncate)) {
QTextStream out(&data);
out << "Result: " << qSetFieldWidth(10) << left << 3.14 << 2.7;
// 输出 "Result: 3.14 2.7 "
}
通常还会使用QTextStream来读取控制台输入和写入控制台输出。QTextStream具有区域设置意识,并将自动使用正确的编解码器对标准输入进行解码。示例:
QTextStream stream(stdin);
QString line;
while (stream.readLineInto(&line)) {
...
}
除了使用QTextStream
的构造函数外,还可以通过调用setDevice()或setString()设置QTextStream操作的设备或字符串。可以通过调用seek()来定位到某个位置,当没有数据可以读取时,调用atEnd()将返回true。如果调用flush(),QTextStream将会将其写缓冲区中的所有数据清空到设备,并调用设备上的flush()。
在内部,QTextStream使用基于Unicode的缓冲区,QTextCodec用于自动支持不同的字符集。默认情况下,读取和写入使用QTextCodec::codecForLocale()
,但也可以通过调用setCodec()来设置编解码器。还支持自动Unicode检测。当启用此功能时(默认行为),QTextStream将检测UTF-16或UTF-32的BOM(字节顺序标记),并在读取时切换到适当的UTF编解码器。QTextStream默认不会写入BOM,但可以通过调用setGenerateByteOrderMark(true)
启用。当QTextStream
直接操作QString
时,编解码器被禁用。
使用QTextStream
读取文本文件有三种常见方式:
- 按块读取,通过调用readLine()或readAll()。
- 逐个单词读取。QTextStream支持流式读取到QString、QByteArray和char*缓冲区中。单词由空格分隔,并自动跳过前导空格。
- 逐个字符读取,通过流式读取到QChar或char类型中。这种方法通常用于方便地处理输入,无论字符编码和行尾的语义如何。要跳过空格,调用skipWhiteSpace()。\
由于文本流使用缓冲区,您不应该使用超类的实现从流中读取。例如,如果您有一个QFile并直接使用QFile::readLine()从中读取,而不使用流,文本流的内部位置将与文件的位置不同步。
默认情况下,当从文本流中读取数字时,QTextStream会自动检测数字的进制表示。例如,如果数字以"0x"开头,它被认为是十六进制形式。如果以1-9的数字开头,它被认为是十进制形式,依此类推。您可以通过调用setIntegerBase()设置整数的进制,从而禁用自动检测。示例:
QTextStream in("0x50 0x20");
int firstNumber, secondNumber;
in >> firstNumber; // firstNumber == 80
in >> dec >> secondNumber; // secondNumber == 0
char ch;
in >> ch; // ch == 'x'
QTextStream支持许多用于生成文本的格式选项。您可以通过调用setFieldWidth()和setPadChar()设置字段宽度和填充字符。使用setFieldAlignment()设置每个字段内的对齐方式。对于实数,可以调用setRealNumberNotation()和setRealNumberPrecision()设置所生成数字的表示(智能表示、科学表示、定点表示)和精度(数字位数)。还可以通过setNumberFlags()设置其他一些数字格式选项。
与标准C++库中的一样,QTextStream还定义了几个全局操纵函数:
操纵函数 | 描述 |
---|---|
bin | 与setIntegerBase(2)相同。 |
oct | 与setIntegerBase(8)相同。 |
dec | 与setIntegerBase(10)相同。 |
hex | 与setIntegerBase(16)相同。 |
showbase | 与setNumberFlags(numberFlags() |
forcesign | 与setNumberFlags(numberFlags() |
forcepoint | 与setNumberFlags(numberFlags() |
noshowbase | 与setNumberFlags(numberFlags() & ~ShowBase)相同。 |
noforcesign | 与setNumberFlags(numberFlags() & ~ForceSign)相同。 |
noforcepoint | 与setNumberFlags(numberFlags() & ~ForcePoint)相同。 |
uppercasebase | 与setNumberFlags(numberFlags() |
uppercasedigits | 与setNumberFlags(numberFlags() |
lowercasebase | 与setNumberFlags(numberFlags() & ~UppercaseBase)相同。 |
lowercasedigits | 与setNumberFlags(numberFlags() & ~UppercaseDigits)相同。 |
fixed | 与setRealNumberNotation(FixedNotation)相同。 |
scientific | 与setRealNumberNotation(ScientificNotation)同。 |
left | 与setFieldAlignment(AlignLeft)相同。 |
right | 与setFieldAlignment(AlignRight)相同。 |
center | 与setFieldAlignment(AlignCenter)相同。 |
endl | 与operator<<(‘\n’)和flush()相同。 |
flush | 与flush()相同。 |
reset | 与reset()相同。 |
ws | 与skipWhiteSpace()相同。 |
bom | 与setGenerateByteOrderMark(true)相同。 |
此外,Qt提供了三个带有参数的全局操纵符:qSetFieldWidth()
、qSetPadChar()
和qSetRealNumberPrecision()
。
总结就是,QFile
用于文件的读写操作,QByteArray
用于处理二进制数据,QDataStream
用于二进制数据的序列化和反序列化,而QTextStream
用于文本数据的读写和处理。它们各自有不同的功能和适用场景,根据具体需求选择合适的类进行操作。
用法及示例
当使用Qt中的QFile、QByteArray、QDataStream和QTextStream时,可以按照以下示例来使用它们的成员函数:
QFile
的使用示例:
#include <QFile>
#include <QDebug>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建一个QFile对象
QFile file("data.txt");
// 打开文件以进行写入
if (file.open(QIODevice::WriteOnly))
{
// 写入数据到文件
QString data = "Hello, World!";
file.write(data.toUtf8());
// 关闭文件
file.close();
}
// 打开文件以进行读取
if (file.open(QIODevice::ReadOnly))
{
// 读取文件中的数据
QByteArray data = file.readAll();
// 将字节数组转换为字符串并输出
QString str(data);
qDebug() << str;
// 关闭文件
file.close();
}
return a.exec();
}
QByteArray
的使用示例:
#include <QByteArray>
#include <QDebug>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建一个QByteArray对象
QByteArray byteArray;
// 向字节数组添加数据
byteArray.append("Hello");
byteArray.append(" ");
byteArray.append("World!");
// 输出字节数组中的数据
qDebug() << byteArray;
// 清空字节数组
byteArray.clear();
return a.exec();
}
QDataStream
的使用示例:
#include <QDataStream>
#include <QFile>
#include <QDebug>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建一个QFile对象
QFile file("data.bin");
// 打开文件以进行写入
if (file.open(QIODevice::WriteOnly))
{
// 创建一个QDataStream对象,并传入QFile对象
QDataStream stream(&file);
// 写入整数到流中
int value = 42;
stream << value;
// 关闭文件
file.close();
}
// 打开文件以进行读取
if (file.open(QIODevice::ReadOnly))
{
// 创建一个QDataStream对象,并传入QFile对象
QDataStream stream(&file);
// 从流中读取整数
int value;
stream >> value;
// 输出取到的整数
qDebug() << value;
//闭文件
file.close();
}
return a.exec();
}
QTextStream
的使用示例:
#include <QTextStream>
#include <QFile>
#include <QDebug>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建一个QFile对象
QFile file("data.txt");
// 打开文件以进行写
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
{
// 创建一个QTextStream对象,并传入QFile对象
QTextStream stream(&file);
// 写入文本到流中
stream << "Hello, World!";
// 关闭文件
file.close();
}
// 打开文件以进行读取
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
// 创建一个QTextStream对象,并传入QFile对象
QTextStream stream(&file);
// 从流中读取文本
QString text = stream.readAll();
// 输出读取到的文本
qDebug() << text;
// 关闭文件
file.close();
}
return a.exec();
}
示例分别使用QFile来读写文件、使用QByteArray处理字节数组、使用QDataStream进行二进制数据的序列化和反序列化、以及使用QTextStream进行文本数据的读写处理。
综合用法
#include <QFile>
#include <QByteArray>
#include <QDataStream>
#include <QTextStream>
#include <QDebug>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建一个QByteArray对象
QByteArray byteArray;
// 向字节数组添加数据
byteArray.append("Hello, World!");
// 创建一个QFile对象
QFile file("data.bin");
// 打开文件以进行写入
if (file.open(QIODevice::WriteOnly))
{
// 创建一个QDataStream对象,并传入QFile对象
QDataStream dataStream(&file);
// 将字节数组写入流
dataStream << byteArray;
// 关闭文件
file.close();
}
// 打开文件以进行读取
if (file.open(QIODevice::ReadOnly))
{
// 创建一个QDataStream对象,并传入QFile对象
QDataStream dataStream(&file);
// 创建一个新的QByteArray对象
QByteArray newDataArray;
// 从流中读取字节数组
dataStream >> newDataArray;
// 创建一个QTextStream对象
QTextStream textStream(&newDataArray);
// 读取字符串数据
QString text = textStream.readAll();
// 输出读取到的字符串
qDebug() << text;
// 关闭文件
file.close();
}
return a.exec();
}
输出结果
示例中,首先创建一个QByteArray对象并向它添加数据。然后,将这个字节数组写入到一个文件中(使用QDataStream),并将文件关闭。接下来,再次打开文件并使用QDataStream从中读取字节数组。最后,使用QTextStream读取字节数组中的字符串数据,并将其输出到控制台。