0 引言
本文介绍一下字节流与字符流的概念以及区别,需要一些预备知识,可以更好的阅读本文。相关前置知识文章链接:预备知识文章链接
1 流 Stream
1.1 定义与作用
在计算机编程中,流(stream) 是一种抽象概念,用于表示数据在程序中的传输和处理方式。流可以看作是数据的通道,通过这个通道,数据可以按照特定的顺序从源头流向目的地。
流主要用于解决数据传输和处理中的一些基本问题,例如:
- 输入和输出:流提供了一种统一的方式来处理输入和输出。程序可以从输入流中读取数据,或将数据写入到输出流中。这使得程序可以与不同的数据源(如文件、网络、设备等)进行交互,而无需关心底层的细节。
- 数据传输:流可以将数据分割成小的数据块(如字节或字符),并按照顺序逐个传输。这种分块传输的方式可以提高效率,特别是当处理大量数据时。
- 缓冲机制:流还可以提供缓冲机制,将数据暂时存储在内存中,以提高读写的效率。通过缓冲,程序可以一次读取或写入多个数据块,减少了与底层数据源的频繁交互,从而提高了性能。
1.2 流的分类
根据流的不同分类标准,可以将流分为以下几类:
- 按照流向分类:
- 输入流(Input Stream):从数据源(如文件、键盘、网络等)读取数据的流,程序可以通过输入流来接收外部数据。
- 输出流(Output Stream):向目标位置(如文件、屏幕、网络等)写入数据的流,程序可以通过输出流将数据发送到外部。
- 按照传输单位分类:
- 字节流(Byte Stream):以字节为单位进行数据传输的流。字节流主要用于处理二进制数据,如图像、音频等。在字节流中,数据被看作是一系列的字节流,可以按照字节的方式进行读取和写入。
- 字符流(Character Stream):以字符为单位进行数据传输的流。字符流主要用于处理文本数据,如读写文本文件。在字符流中,数据被视为字符的序列,并根据字符编码将字符转换为字节进行传输。
1.3 字节流与字符流
字节流(Byte Stream)和字符流(Character Stream)是输入流(Input Stream)和输出流(Output Stream)在不同层次上的抽象。它们主要处理不同类型的数据。
-
字节流
字节流以字节为单位进行数据传输和处理,适合处理二进制数据,如图像、音频、视频等。字节流将数据视为字节序列,在读取和写入时按照字节的方式进行操作,不对数据进行任何解析或处理。常见的字节流类包括InputStream和OutputStream。 -
字符流
字符流以字符为单位进行数据传输和处理,适合处理文本数据,如读写文本文件。字符流将数据视为字符的序列,并根据指定的字符编码将字符转换为字节进行传输。字符流在读取和写入时会自动进行字符编码和解码。常见的字符流类包括Reader和Writer。
总结一下两者的区别:
- 数据类型:字节流处理原始字节数据,而字符流处理字符数据。
- 处理内容:字节流适合处理任意类型的二进制数据,如图像、音频等。字符流适合处理文本数据,如读写文本文件。
- 编码和解码:字节流原封不动地传输数据,不进行编码和解码操作。字符流在传输数据之前会进行字符编码(将字符转换为字节)和解码(将字节转换为字符)。
- 接口方法:字节流提供了一些读取和写入原始字节的方法。字符流提供了一些读取和写入字符的方法,以及一些处理字符数据的辅助方法(如换行符转换)。
总体而言,字节流适用于处理底层的原始数据,而字符流适用于处理更高层次的文本数据。选择使用哪种类型的流取决于所处理数据的类型和需求。
扩展知识
所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,虽然我们看到的是字符文字(这是使用字符编码将二进制数转换成了字符),也是一个字节一个字节地读取以形成字节序列。只不过字符流会会先进行编码和解码操作。
- 如何区分使用字节流还是字符流
- 最简单的方法,就是使用记事本打开文件。在计算机中所有的都是一个文件,假如使用记事本打开,是看的懂的字符,那么就使用字符流传输。如果乱码的话就使用字节流传输。(前提是,使用了正确的字符编码,不然有些事文本文件的,但是没使用对应的字符编码打开也会乱码)
例如:我打开了CompileVer.pri 和 shellbrd.dll 两个文件,不需要关心文件后缀是什么。只要关心打开之后乱不乱码就行。.dll文件就乱码了。(Tips:使用记事本打开任何文件时,右下角会显示当前使用的编码方式)
2 IO编程实践
接下来使用的是QT库实现对数据的IO操作。
预备知识:
- 在Qt中,使用的是Unicode字符集(这个字符集基本上将世界上所有的字符都涵盖了),QString类和QChar类都使用Unicode编码来表示字符。
- 可以使用QString的toUtf8()和fromUtf8()进行编码和解码操作(以UTF-8的规则)。
- QByteArray类是用于处理原始字节数据的类,它并不对字节数据应用任何特定的编码方式。QByteArray只是简单地存储字节序列,不涉及解码或编码操作。所以QString转换成QByteArray需要进行编码操作。
2.1 字节流的使用
首先添加 QFile 和 QDebug 头文件。
#include <QFile>
#include <QDebug>
- 新建函数 ReadFileByByteStream 实现通过字节流的方式读取文件
void ReadWriteFile::ReadFileByByteStream()
{
// 打开文件进行读取
QFile inputFile("output.bin");
if (!inputFile.open(QIODevice::ReadOnly)) {
qDebug() << "无法打开输入文件";
return;
}
// 读取字节流数据
QByteArray readData = inputFile.readAll();
QString readData_UTF8 = QString::fromUtf8(readData); // 将读取的字节流数据以UTF-8编码规则解码为Unicode字符
qDebug() << "readData = " << readData;
qDebug() << "readData_UTF8 = " << readData_UTF8;
// 关闭输入文件
inputFile.close();
}
- 新建函数 WriteFileByByteStream 实现通过字节流的方式写入文件
void ReadWriteFile::WriteFileByByteStream()
{
// 打开文件进行写入
QFile outputFile("output.bin");
if (!outputFile.open(QIODevice::WriteOnly)) {
qDebug() << "无法打开输出文件";
return;
}
// 写入字节流数据
QByteArray data;
data.append("hello你好"); // Unicode字符,UTF-8编码
outputFile.write(data);
// 关闭输出文件
outputFile.close();
}
- 测试功能,先写入数据,再读取数据
ReadWriteFile myIOFile;
myIOFile.WriteFileByByteStream();
myIOFile.ReadFileByByteStream();
- 输出结果如下:
readData = "hello\xE4\xBD\xA0\xE5\xA5\xBD"
readData_UTF8 = "hello你好"
结果分析:
- 结果一:readData = “hello\xE4\xBD\xA0\xE5\xA5\xBD”
- 可以看到hello是正确显示了,但是 “你好” 字符串(以UTF-8编码)缺没有正常显示,是因为 QByteArray 是没有编码解码这种操作的,存储的是原始的字符。所以,存储了什么数据就直接输出了什么数据。但是你可以会疑惑,明明我写入的时候是 "hello你好"字符串,为什么变成这种形式。
- 这是因为将 “hello你好” 数据添加给 QByteArray 时,QT的append函数将"hello你好"转换成了UTF-8编码格式(这对应了编码操作),也就是说 “hello\xE4\xBD\xA0\xE5\xA5\xBD” 这一串字符是"hello你好"的UTF-8编码格式。
拓展:
- QString的字符串是有默认的编码格式的(采用了国际标准Unicode字符集,UTF-8字符编码)
QByteArray中存储的数据,是没有字符编码的。- 所以如何将QString类型的数据转换成QByteArray数据呢,很显然只需要对QString中存储的字符串进行编码操作,得到UTF-8编码后的数据也就可以转换成QByteArray数据了。
- 下面来看下程序中是如何实现的吧:
QString str = "你好";
// toUtf8() 函数是将str字符串进行UTF-8编码操作,
// 因为QByteArray是没有字符编码格式的,只存储字节数据,所以需要将str字符串先转换为字节数据
QByteArray ba_str = str.toUtf8();
qDebug() << "str = " << str;
qDebug() << "ba_str = " << ba_str;
程序输出结果如下:
可以看到 “你好” 字符串采用UTF-8编码后得到的是 “\xE4\xBD\xA0\xE5\xA5\xBD” 数据
str = "你好"
ba_str = "\xE4\xBD\xA0\xE5\xA5\xBD"
- 结果二:readData_UTF8 = “hello你好”
- 为什么结果二正确显示了,因为我们使用了QString::fromUtf8()函数将 QByteArray 存储的原始数据进行UTF-8规则的解码。所以就可以正常显示中文了。(注意假如解码的规则和编码规则不同,也是会乱码的)
小结
Tips:编码是将字符转换为二进制形式的过程(但是上面程序编码后输出的结果并不是二进制数据,这是输出格式决定的,可能是将二进制数转换成了十进制数然后输出),而解码则是将二进制形式的字符转换为可读的字符。
2.2 字符流的使用
同样添加 QFile 和 QDebug 头文件。
#include <QFile>
#include <QDebug>
- 新建函数 ReadFileByCharacterStream 实现通过字符流的方式读取文件
void ReadWriteFile::ReadFileByCharacterStream()
{
// 打开文件进行读取
QFile inputFile("output.txt");
if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "无法打开输入文件";
return;
}
// 创建QTextStream对象,并与输入文件关联
QTextStream inputStream(&inputFile);
// 读取字符流数据
QString readData = inputStream.readAll();
qDebug() << "readData = " << readData;
// 关闭输入文件
inputFile.close();
}
- 新建函数 WriteFileByCharacterStream 实现通过字符流的方式写入文件
void ReadWriteFile::WriteFileByCharacterStream()
{
// 打开文件进行写入
QFile outputFile("output.txt");
if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "无法打开输出文件";
return;
}
// 创建QTextStream对象,并与输出文件关联
QTextStream outputStream(&outputFile);
// 写入字符流数据
outputStream << "Hello, World!";
// 关闭输出文件
outputFile.close();
}
2.3 总结
- 所有文件的存储都是字节(byte)的存储,在磁盘上保留的是字节。
- 所有文件都能使用字节流的方式读取,而字符流主要处理文本数据。