跟我学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使用调用者提供的缓冲区,不需要主动清理,由原缓冲区管理者处理

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

六、总结

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

相关推荐
大鹏的NLP博客8 小时前
WSL2 资源治理:从 ONNX Runtime CUDA 编译 OOM 到 `.wslconfig` 防御配置
c++·深度学习·onnx runtime
深念Y8 小时前
为什么选 Go:直观、可维护、AI 友好
linux·开发语言·人工智能·golang·agent·harness
Billy121388 小时前
Day25-编译期计算与 constexpr if:跨越编译与运行的边界
开发语言·c++进阶学习
星星.7228 小时前
C++算法竞赛|二分查找与二分答案:边界模板、浮点二分、STL
数据结构·c++·算法
小小龙学IT8 小时前
第七节 C++ 与 QML 交互
开发语言·qt
老当益壮梁奶奶9 小时前
Linux 软件编程学习笔记(六):线程编程入门与实践
linux·c语言·c++·笔记·学习
小小龙学IT9 小时前
Dear ImGui 开源即时模式 GUI 库深度解析
c++·ui·开源
晓晓_za8986689 小时前
Geo 优化源码二次开发:自定义地域规则改造实操文档
java·开发语言·搜索引擎·ci/cd·矩阵
ShineWinsu9 小时前
对于TRAE中配置Qt的解析
开发语言·c++·ide·vscode·qt·ai·trae
wy31362282110 小时前
Git——ignore让项目的 target 目录真正被忽略(忽略其他文件也是同样的道理)
开发语言·git