一、流和内存流
其实对于大多数的开发者来说,特别是初学者,流这个概念虽然不算陌生,但应用起来相对还是要远的。什么是流?可以简单想象一下河流、气流。会发现什么?流是连续不断的,有序的。而且任何流都朝着某个方向前进,不会静止即方向性。最后,流的内容不可控,即内容是抽象的,可以是水、水溶物或气体。整体上看,流就是数据有序传输序列。
再把它映射到计算机世界,会发现什么?网上的视频数据传输,是不是和它一样?系统中的管道开发是不是也和它一样?各种各样的数据流是不是都和它本质是相同的?所以计算机的开发中就会引入流这个概念。
当然,流也分为好多种。流本身是抽象的,所以它映射到不同的场景下就会有很多具体的流。如字符串中的字符串流、图像中的比特流以及网络传输中的IO流、文件中的文件流等等。
这里面最常见的一种其实是内存流。内存流其实是数据在内存中的有序传输序列。
二、作用
学过计算机的都知道,内存在计算机中算是第二快的存储了。所以内存流的特点就是操作处理的快。这在很多场景下,应用都非常广泛:
- 数据缓存
这种是最常见的,把一些数据缓存在内存中供使用。比如日志、数据块等等 - 数据传输
这也是比较常见的一类,即把一些大的数据写入到内存中,然后进行传输或者通过接口进行接收。常见的如网络传输 - 数据处理
其实不管是数据传输或数据缓存,更多的一种情况是在此基础上进行数据处理。如文本解析、二进制数据过滤、分类以及格式处理等 - 单元测试
这种属于一种特定情况,为了方便快捷,直接在内存中模拟各种IO和文件操作等。既方便又快捷,还不用清理残留 - 字符串处理
这里之所以把字符串处理单独说明,其实也是为了和标准库中保持一致。字符串更像一个"两栖"类的对象,介于类和流之间。把字符串放在内存流中,可以动态的处理(查找、裁剪等)字符串、字符串的编码转换(特别是宽字符)以及国际化和本地化,还有包括各种文本格式的转换、编码等等
内存的自身的特性就决定了其只是一个临时处理的场景,它只是提供了更加高效的数据处理和传输的可能。不过内存流的使用会占用内存,所以在内存空间不足的场景中,还是要谨慎小心的使用。否则OOM之类的错误可不是开玩笑的。
三、常见的接口函数
在不同的平台(库)可能提供了不同的内存流接口。下面对两大主流平台的内存流接口简单说明一下:
-
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指针即可对内存流进行操作了。
- 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等一系列的操作,有兴趣可以自行查找学习。
四、应用场景
内存流的应用场景很多,主要包括:
- 网络通信:大量的数据需要从内存流进行各种转换操作。包括各种序列化等
- 大文件加载或游戏加载:大量的资源文件可以通过内存流进行处理,提高加载速度和响应速度
- 图形图像以及音视频的处理:内存流中可以更好的对数据进行处理,减少中间环节,提高处理速度
- 安全领域:数据的压缩、加解密都会在内存中处理,防止密钥的泄露
- 测试和仿真:只需要在内存中对数据进行展现即可,不必持久化的场景
五、例程
下面给出一个简单的例程:
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下的内存流需要注意几点:
- open_memstream、fmemopen两个接口函数属于GNU libc扩展
- open_memstream创建的缓冲区必须使用free函数释放
- 对open_memstream写入,需配合fflush(),才能得到最新的缓冲区地址和长度
- fmemopen使用调用者提供的缓冲区,不需要主动清理,由原缓冲区管理者处理
其实对内存流这种开发,往往不是单独存在的,而和融合在项目整体的工程中。开发者可能在自己的模块中没用到,但不代表使用的库或者其它同事的模块中没有。
六、总结
内存的特点其实就是快。当然快是相对的,是相对于大容量的硬盘而言的。学过计算机体系结构的都知道,计算机其实就是一个平衡效率和成本的设计。而内存流其实就是这种平衡设计的一种具体的体现。通过学习内存流的应用,就可以很好的把握设计中的平衡之道。