跟我学C++中级篇—内存流

一、流和内存流

其实对于大多数的开发者来说,特别是初学者,流这个概念虽然不算陌生,但应用起来相对还是要远的。什么是流?可以简单想象一下河流、气流。会发现什么?流是连续不断的,有序的。而且任何流都朝着某个方向前进,不会静止即方向性。最后,流的内容不可控,即内容是抽象的,可以是水、水溶物或气体。整体上看,流就是数据有序传输序列。

再把它映射到计算机世界,会发现什么?网上的视频数据传输,是不是和它一样?系统中的管道开发是不是也和它一样?各种各样的数据流是不是都和它本质是相同的?所以计算机的开发中就会引入流这个概念。

当然,流也分为好多种。流本身是抽象的,所以它映射到不同的场景下就会有很多具体的流。如字符串中的字符串流、图像中的比特流以及网络传输中的IO流、文件中的文件流等等。

这里面最常见的一种其实是内存流。内存流其实是数据在内存中的有序传输序列。

二、作用

学过计算机的都知道,内存在计算机中算是第二快的存储了。所以内存流的特点就是操作处理的快。这在很多场景下,应用都非常广泛:

  1. 数据缓存
    这种是最常见的,把一些数据缓存在内存中供使用。比如日志、数据块等等
  2. 数据传输
    这也是比较常见的一类,即把一些大的数据写入到内存中,然后进行传输或者通过接口进行接收。常见的如网络传输
  3. 数据处理
    其实不管是数据传输或数据缓存,更多的一种情况是在此基础上进行数据处理。如文本解析、二进制数据过滤、分类以及格式处理等
  4. 单元测试
    这种属于一种特定情况,为了方便快捷,直接在内存中模拟各种IO和文件操作等。既方便又快捷,还不用清理残留
  5. 字符串处理
    这里之所以把字符串处理单独说明,其实也是为了和标准库中保持一致。字符串更像一个"两栖"类的对象,介于类和流之间。把字符串放在内存流中,可以动态的处理(查找、裁剪等)字符串、字符串的编码转换(特别是宽字符)以及国际化和本地化,还有包括各种文本格式的转换、编码等等

内存的自身的特性就决定了其只是一个临时处理的场景,它只是提供了更加高效的数据处理和传输的可能。不过内存流的使用会占用内存,所以在内存空间不足的场景中,还是要谨慎小心的使用。否则OOM之类的错误可不是开玩笑的。

三、常见的接口函数

在不同的平台(库)可能提供了不同的内存流接口。下面对两大主流平台的内存流接口简单说明一下:

  1. Linux

    c 复制代码
     // 打开一个内存流,大小固定。可由用户提供buf或函数自动分配
     FILE *fmemopen(void *buf, size_t size, const char *type);
     // 动态分配内存流,大小可自动分配增长。适合动态构建数据如字符串
     FILE *open_memstream(char **ptr, size_t *sizeloc);
     //与上面的接口类似,只不过用来处理宽字符的动态流
     FILE *open_wmemstream(wchar_t **ptr, size_t *sizeloc);

这三个内存流接口是POSIX定义,C标准库声明的。根据Linux下万物皆文件的情况,返回一个FILE指针即可对内存流进行操作了。

  1. Windows

Windows主要是使用基于COM的IStream接口来实现:

c 复制代码
//基于HGLOBAL内存句柄创建内存流对象
HRESULT CreateStreamOnHGlobal(
[in]  HGLOBAL  hGlobal,
[in]  BOOL     fDeleteOnRelease,
[out] LPSTREAM *ppstm
);
//性能较高的一种内存流创建的方式
IStream* SHCreateMemStream(
[in, optional] const BYTE *pInit,
[in]           UINT        cbInit
);
//创建内存随机访问流
public ref class InMemoryRandomAccessStream sealed : 
 IClosable, IRandomAccessStream

另外在STL中针对字符串还提供了std::stringstream等一系列的操作,有兴趣可以自行查找学习。

四、应用场景

内存流的应用场景很多,主要包括:

  1. 网络通信:大量的数据需要从内存流进行各种转换操作。包括各种序列化等
  2. 大文件加载或游戏加载:大量的资源文件可以通过内存流进行处理,提高加载速度和响应速度
  3. 图形图像以及音视频的处理:内存流中可以更好的对数据进行处理,减少中间环节,提高处理速度
  4. 安全领域:数据的压缩、加解密都会在内存中处理,防止密钥的泄露
  5. 测试和仿真:只需要在内存中对数据进行展现即可,不必持久化的场景

五、例程

下面给出一个简单的例程:

c 复制代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

int test() {
  char *outputBuf = nullptr;
  std::size_t outputSize = 0;

  FILE *output = open_memstream(&outputBuf, &outputSize);
  if (output == nullptr) {
    return EXIT_FAILURE;
  }

  std::fprintf(output, "name: %s\n", "Tom");
  std::fprintf(output, "age: %d\n", 16);
  std::fprintf(output, "score: %.2f\n", 99.5);

  if (std::fflush(output) != 0) {
    std::fclose(output);
    std::free(outputBuf);
    return EXIT_FAILURE;
  }

  std::cout << "open_memstream output size: " << outputSize << std::endl;

  std::cout << "open_memstream output content:" << outputBuf << std::endl;

  std::fclose(output);

  FILE *input = fmemopen(outputBuf, outputSize, "r");
  if (input == nullptr) {
    std::free(outputBuf);
    return EXIT_FAILURE;
  }

  std::cout << "fmemopen read line!" << std::endl;

  char line[128] = {0};
  while (std::fgets(line, sizeof(line), input) != nullptr) {
    std::cout << "read content: " << line;
  }

  std::fclose(input);

  std::free(outputBuf);

  char buffer[128] = {0};

  FILE *readWrite = fmemopen(buffer, sizeof(buffer), "w+");
  if (readWrite == nullptr) {
    std::perror("fmemopen w+");
    return EXIT_FAILURE;
  }

  std::fprintf(readWrite, "test fmemopen\n");

  std::fflush(readWrite);
  std::rewind(readWrite);

  char readBuf[128] = {0};
  if (std::fgets(readBuf, sizeof(readBuf), readWrite) != nullptr) {
    std::cout << "fmemopen result: " << readBuf << std::endl;
  }

  std::fclose(readWrite);
  return 0;
}
int main() {
  auto ret = test();
  return 0;
}

使用Linux下的内存流需要注意几点:

  1. open_memstream、fmemopen两个接口函数属于GNU libc扩展
  2. open_memstream创建的缓冲区必须使用free函数释放
  3. 对open_memstream写入,需配合fflush(),才能得到最新的缓冲区地址和长度
  4. fmemopen使用调用者提供的缓冲区,不需要主动清理,由原缓冲区管理者处理

其实对内存流这种开发,往往不是单独存在的,而和融合在项目整体的工程中。开发者可能在自己的模块中没用到,但不代表使用的库或者其它同事的模块中没有。

六、总结

内存的特点其实就是快。当然快是相对的,是相对于大容量的硬盘而言的。学过计算机体系结构的都知道,计算机其实就是一个平衡效率和成本的设计。而内存流其实就是这种平衡设计的一种具体的体现。通过学习内存流的应用,就可以很好的把握设计中的平衡之道。

相关推荐
Cicada1281 小时前
ccvt:一个用 Rust 写的中国地图坐标系互转命令行工具
开发语言·后端·rust
1001101_QIA1 小时前
工控机网络配置
开发语言·数据库·php
程序员雷欧2 小时前
ThreadPoolExecutor 深度解析:从核心参数到源码实现的全面剖析
java·开发语言·jvm
catchadmin2 小时前
对标 npx 的 CPX PHP 的 Composer 包执行器
开发语言·php·composer
一位狮子座的程序员2 小时前
如何用RAG解决AI智能体的知识盲区?
开发语言·c#
吹什么轩2 小时前
c++复习:模拟实现list来更好的理解和应用list
开发语言·c++
小白学大数据2 小时前
Python 爬虫实战:抓取汽车之家二手车成交价格与里程数据
开发语言·爬虫·python·汽车
AmyLin_20012 小时前
PDF 色彩保真工程实践【5】核心实现(下):拼装 Image XObject、页面资源与内容流
c++·pdf·sdk·颜色空间·印刷·icc·cmyk