【流 Stream】数据传输:字节流与字符流详解 文件读写必备基础知识!!!

3 篇文章 0 订阅

请添加图片描述

  • 🙋‍♂️ 作者:海码007
  • 📜 专栏:C++专栏
  • 📜 相关文章:字符编码
  • 💥 标题:【流 Stream】数据传输:字节流与字符流详解 文件读写必备基础知识!!!
  • ❣️ 寄语:书到用时方恨少,事非经过不知难。
  • 🎈 最后:文章作者技术和水平有限,如果文中出现错误,希望大家能指正

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)在不同层次上的抽象。它们主要处理不同类型的数据。

  1. 字节流
    字节流以字节为单位进行数据传输和处理,适合处理二进制数据,如图像、音频、视频等。字节流将数据视为字节序列,在读取和写入时按照字节的方式进行操作,不对数据进行任何解析或处理。常见的字节流类包括InputStream和OutputStream。

  2. 字符流
    字符流以字符为单位进行数据传输和处理,适合处理文本数据,如读写文本文件。字符流将数据视为字符的序列,并根据指定的字符编码将字符转换为字节进行传输。字符流在读取和写入时会自动进行字符编码和解码。常见的字符流类包括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>
  1. 新建函数 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();
}
  1. 新建函数 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();
}
  1. 测试功能,先写入数据,再读取数据
ReadWriteFile myIOFile;
myIOFile.WriteFileByByteStream();
myIOFile.ReadFileByByteStream();
  1. 输出结果如下:
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>
  1. 新建函数 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();
}
  1. 新建函数 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)的存储,在磁盘上保留的是字节。
  • 所有文件都能使用字节流的方式读取,而字符流主要处理文本数据。
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 C# 和 Java 实现 Socket 传输 Protobuf 字节流的过程可以分为以下几个步骤: 1. 定义 Protobuf 消息结构 首先,需要使用 Protocol Buffers 定义消息结构。假设我们要传输的消息结构如下: ``` message Person { string name = 1; int32 age = 2; repeated string phone_number = 3; } ``` 2. 生成代码 使用 protobuf 编译器生成 C# 和 Java 的代码,方法如下: ``` protoc --csharp_out=. person.proto protoc --java_out=. person.proto ``` 3. C# 实现 Socket 发送 首先,在 C# 中创建一个 `Person` 对象,并将其序列化为字节数组,然后将其发送到 Java 服务器: ```csharp using System; using System.Net.Sockets; using Google.Protobuf; class Program { static void Main(string[] args) { // 创建 Person 对象 var person = new Person { Name = "张三", Age = 18, PhoneNumber = { "123456789", "987654321" } }; // 将 Person 对象序列化为字节数组 byte[] data = person.ToByteArray(); // 创建 Socket 连接 var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect("127.0.0.1", 8888); // 发送数据 socket.Send(data); // 关闭连接 socket.Shutdown(SocketShutdown.Both); socket.Close(); } } ``` 4. Java 实现 Socket 接收 在 Java 中,我们需要创建一个 `ServerSocket`,并监听指定的端口。当有连接请求时,我们可以使用 `Socket` 接收数据,并将其反序列化为 `Person` 对象: ```java import com.example.PersonOuterClass.Person; import com.google.protobuf.InvalidProtocolBufferException; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Main { public static void main(String[] args) throws IOException { // 创建 ServerSocket,监听指定端口 ServerSocket serverSocket = new ServerSocket(8888); while (true) { // 等待连接 Socket socket = serverSocket.accept(); // 读取数据 byte[] buffer = new byte[socket.getInputStream().available()]; socket.getInputStream().read(buffer); try { // 将字节数组反序列化为 Person 对象 Person person = Person.parseFrom(buffer); System.out.println(person); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); } // 关闭连接 socket.shutdownInput(); socket.close(); } } } ``` 这样,就完成了 C# 和 Java 之间通过 Socket 传输 Protobuf 字节流的实例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值