vault backup: 2026-11-07 16:33:44

This commit is contained in:
cheney
2026-11-07 16:33:55 +08:00
commit 7015312702
1208 changed files with 132432 additions and 0 deletions
@@ -0,0 +1,217 @@
**一、调用wasm中导出方法的函数**
在js中,我们可以通过以下五种方式调用wasm中导出的方法:
1、**_函数名**
2、**Module.ccall**
| | |
| --- | -------------------------------------------------------------- |
| 1 | `const ret = Module.ccall(ident, returnType, argTypes, args);` |
参数释义如下:
`ident`:C导出函数的函数名(不含“_”下划线前缀);
`returnType`:C导出函数的返回值类型,可以为'`boolean`'、'`number`'、'`string`'、'`null`',分别表示函数返回值为布尔值、数值、字符串、无返回值;
`argTypes`:C导出函数的参数类型的数组。参数类型可以为'`number`'、'`string`'、'`array`',分别代表数值、字符串、数组;
`args`:参数数组;
使用方式:
| | |
|---|---|
|1|`const ret = Module.ccall('add', 'number', ['number', 'number'], [1, 2]);`|
**Emscripten从**`v1.38`**开始,**`ccall/cwrap`**辅助函数默认没有导出,在编译时需要通过**`-s EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap']"`**选项显式导出。**
3、**Module.cwrap**
| | |
|---|---|
|1<br><br>2|`const func = Module.cwrap(ident, returnType, argTypes);`<br><br>`const ret = func(args);`|
参数释义如下:
`ident`:C导出函数的函数名(不含“_”下划线前缀);
`returnType`:C导出函数的返回值类型,可以为'`boolean`'、'`number`'、'`string`'、'`null`',分别表示函数返回值为布尔值、数值、字符串、无返回值;
`argTypes`:C导出函数的参数类型的数组。参数类型可以为'`number`'、'`string`'、'`array`',分别代表数值、字符串、数组;
返回值:函数
使用方式:
| | |
|---|---|
|1<br><br>2|`const func = Module.cwrap('add', 'number', ['number', 'number']);`<br><br>`const ret = func(1, 2);`|
**Emscripten从**`v1.38`**开始,**`ccall/cwrap`**辅助函数默认没有导出,在编译时需要通过**``-s `EXPORTED_RUNTIME_METHODS`="['ccall', 'cwrap']"``**选项显式导出。**
4、**Module._函数名**
5、**Module.asm.函数名**
上面我们了解了js调用wasm导出方法的5种方式,接下来我们开始介绍js和wasm间各种数据类型的传递。
**二、数字类型的传递**
**C函数定义:**
| | |
|---|---|
|1<br><br>2<br><br>3<br><br>4|`int add(int n1, int n2) {`<br><br>    `int ret = n1 + n2;`<br><br>    `return ret;`<br><br>`}`|
**向**C函数**传递数字:**
| | |
|---|---|
|1<br><br>2<br><br>3<br><br>4|`const r1 = _add(1, 2); // 3`<br><br>`const r2 = Module._add(1, 2); // 3`<br><br>`const r3 = Module.ccall('add', 'number', ['number', 'number'], [1, 2]); // 3`<br><br>`const r4 = Module.cwrap('add', 'number', ['number', 'number'])(1, 2); // 3`|
**获取**C函数**返回的数字:**
由于返回的数字是默认支持的类型,`r1`,`r2`,`r3`,`r4`即为结果。
**三、字符串类型的传递**
**C函数定义:**
| | |
|---|---|
|1<br><br>2<br><br>3<br><br>4<br><br>5<br><br>6<br><br>7<br><br>8<br><br>9|`#include <string.h>`<br><br>`#include <malloc.h>`<br><br>`char * concat_str(char *s1, char *s2) {`<br><br>    `char *ret = (char *)malloc((strlen(s1) + strlen(s2)) * sizeof(char));`<br><br>    `strcat(ret, s1);`<br><br>    `strcat(ret, s2);`<br><br>    `return ret;`<br><br>`}`|
**向C函数内传递**字符串**:**
| | |
|---|---|
|1<br><br>2|`const r1 = Module.ccall('concat_str', 'string', ['string', 'string'], ['Hello ', 'World']); // Hello World`<br><br>`const r2 = Module.cwrap('concat_str', 'string', ['string', 'string'])('Hello ', 'World'); // Hello World`|
**获取**C函数**返回的字符串:**
由于返回的数字是`ccall`,`cwrap`默认支持的类型,`r1`,`r2`即为结果。
上面没有直接使用函数名去调用C函数,因为不支持将字符串通过函数调用的方式直接传递给C函数,和我们预期是不一样的,感兴趣的可以去试一下,但是该如何使用函数名调用C函数呢,这里简单演示一下,作为后面数组传递的基础。
**向**C函数**传递**字符串**:**
| | |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1<br><br>2<br><br>3<br><br>4<br><br>5<br><br>6<br><br>7<br><br>8<br><br>9 | `const s1 = 'Hello ';`<br><br>`const s2 = 'World';`<br><br>`const s1Arr = new TextEncoder().encode(s1);`<br><br>`const buf1 = Module._malloc(s1Arr.byteLength);`<br><br>`Module.HEAPU8.set(s1Arr, buf1);`<br><br>`const s2Arr = new TextEncoder().encode(s2);`<br><br>`const buf2 = Module._malloc(s2Arr.byteLength);`<br><br>`Module.HEAPU8.set(s2Arr, buf2);`<br><br>`const r1 = _concat_str(buf1, buf2); // 地址` |
这里我们将字符串`s1`和`s2`转为类型化数组`Uint8Array`,然后通过wasm提供的内存分配函数申请了内存,并放入了内存中,数据放到内存后将分配内存后得到的地址传递给C函数`_concat_str`即可。
**获取C函数返回的字符串:**
这里需要注意的是返回结果`r1`并不是预期的字符串而是一个内存地址,我们需要根据内存地址去获取返回结果:
| | |
|---|---|
|1<br><br>2<br><br>3<br><br>4<br><br>5<br><br>6<br><br>7<br><br>8<br><br>9|`const memoryView = new Uint8Array(Module.asm.memory.buffer);`<br><br>`const arr = [];`<br><br>`let i = r1;`<br><br>`while (memoryView[i] !== 0) {`<br><br>    `arr.push(memoryView[i]);`<br><br>    `i ++;`<br><br>`}`<br><br>`const ret = new TextDecoder().decode(new Uint8Array(arr));`<br><br>`console.log(ret); // Hello World`|
**四、一维纯数字数组类型的传递**
**C函数定义:**
| | |
|---|---|
|1<br><br>2<br><br>3<br><br>4<br><br>5<br><br>6<br><br>7|`int * mapArray(uint8_t *arr, int arrSize) {`<br><br>    `int *ret = (int *)malloc(arrSize * sizeof(int));`<br><br>    `for (int i=0; i<arrSize; i++) {`<br><br>        `ret[i] = arr[i] * 2;`<br><br>    `}`<br><br>    `return ret;`<br><br>`}`|
**向**C函数**传递一维数组:**
| | |
|---|---|
|1<br><br>2<br><br>3|`const arr = [1, 3, 2, 5, 4];`<br><br>`const r1 = Module.ccall('mapArray', 'number', ['array', 'number'], [arr, arr.length]); // 地址`<br><br>`const r2 = Module.cwrap('mapArray', 'number', ['array', 'number'])(arr, arr.length); // 地址`|
如果想通过函数名直接向C函数传递数据,可以参考上面通过函数名直接向C函数传递字符串的方法。
**获取C函数返回的一维数组:**
| | |
|---|---|
|1<br><br>2<br><br>3<br><br>4<br><br>5|`const { BYTES_PER_ELEMENT } = Float32Array;`<br><br>`const pointer = r1;`<br><br>`const data = new Float32Array(Module.asm.memory.buffer, pointer, arr.length);`<br><br>`const ret = Array.from(data);`<br><br>`console.log(ret); // [2, 6, 4, 10, 8]`|
这里的做法和上面获取直接调用函数名返回的字符串基本类似,但是没有字符串灵活,我们通过`\0`字符来判断字符串的结束进而拿到整个字符串而不需要提前知道返回字符串的长度,但是对于数组的话我们需要提前知道返回的数组的大小,才能准确的从内存中拿到返回结果。
**注意:**C语言中`int`类型数组对应`Uint32Array`,`float`类型数组对应`Float32Array`,`double`类型数组对应`Float64Array`,在获取结果时一定要使用正确的类型化数组,否则获取的结果将是错误的。
**五、二维纯数字数组类型的传递**
**C函数定义:**
| | |
|---|---|
|01<br><br>02<br><br>03<br><br>04<br><br>05<br><br>06<br><br>07<br><br>08<br><br>09<br><br>10<br><br>11<br><br>12|`double ** mapArray2(uint8_t *arr, int row, int col) {`<br><br>    `static double **ret;`<br><br>    `ret = (double **)malloc(row * col * sizeof(double *));`<br><br>    `for (int i=0; i<row; i++) {`<br><br>        `ret[i] = (double *)malloc(col * sizeof(double));`<br><br>        `for (int j=0; j<col; j++) {`<br><br>            `ret[i][j] = arr[i * row + j] * 2;`<br><br>            `printf("%d:%d=%f\n", i, j, ret[i][j]);`<br><br>        `}`<br><br>    `}`<br><br>    `return ret;`<br><br>`}`|
**向C函数传递二维数组:**
由于 C 编写的 WASM 模块只能接受一维数组,因此需要将二维数组转换为一维数组然后再传递。
| | |
|---|---|
|1<br><br>2<br><br>3<br><br>4<br><br>5<br><br>6<br><br>7|`const arr = [`<br><br>    `[1, 2, 3],`<br><br>    `[4, 5, 6],`<br><br>    `[7, 8, 9]`<br><br>`];`<br><br>`const r1 = Module.ccall('mapArray2', 'number', ['array', 'number', 'number'], [arr.flat(), arr.length, arr[0].length]); // 地址`<br><br>`const r2 = Module.cwrap('mapArray2', 'number', ['array', 'number', 'number'])(arr.flat(), arr.length, arr[0].length); // 地址`|
**获取C函数返回的二维数组:**
这里就会麻烦一点,因为二维数组每一行对应的数据都存在一片单独的内存上,而且不一定是连续的,所以我们需要将每一行的起始内存地址返回,我们稍微修改一下C代码:
| | |
|---|---|
|01<br><br>02<br><br>03<br><br>04<br><br>05<br><br>06<br><br>07<br><br>08<br><br>09<br><br>10<br><br>11<br><br>12<br><br>13<br><br>14<br><br>15<br><br>16|`double **ret;`<br><br>`double * getLineAddress(int row) {`<br><br>    `return ret[row];`<br><br>`}`<br><br>`double ** mapArray2(uint8_t *arr, int row, int col) {`<br><br>    `ret = (double **)malloc(row * col * sizeof(double *));`<br><br>    `for (int i=0; i<row; i++) {`<br><br>        `ret[i] = (double *)malloc(col * sizeof(double));`<br><br>        `for (int j=0; j<col; j++) {`<br><br>            `ret[i][j] = arr[i * row + j] * 2;`<br><br>        `}`<br><br>    `}`<br><br>    `return ret;`<br><br>`}`|
在全局定义结果变量,然后提供一个获取每行地址的方法:`getLineAddress`。
然后使用js获取C函数返回的结果:
| | |
|---|---|
|1<br><br>2<br><br>3<br><br>4<br><br>5<br><br>6<br><br>7<br><br>8|`const { BYTES_PER_ELEMENT } = Float64Array;`<br><br>`const ret = [];`<br><br>`for (let i=0; i<arr.length; i++) {`<br><br>    `const pointer = _getLineAddress(i);`<br><br>    `const row = new Float64Array(Module.asm.memory.buffer, pointer, arr[0].length);`<br><br>    `ret.push(Array.from(row));`<br><br>`}`<br><br>`console.log(ret); // [[2, 4, 6], [,8, 10, 12],[14, 16, 18]]`|
**六、字符串数组的传递**
**C函数定义**
| | |
|---|---|
|1<br><br>2<br><br>3<br><br>4<br><br>5<br><br>6|`char ** mapStringArray(char **strings, int length) {`<br><br>  `for (int i = 0; i < length; i++) {`<br><br>    `printf("%s\n", strings[i]);`<br><br>  `}`<br><br>  `return strings;`<br><br>`}`|
**向C函数传递二维数组:**
| | |
|---|---|
|01<br><br>02<br><br>03<br><br>04<br><br>05<br><br>06<br><br>07<br><br>08<br><br>09<br><br>10<br><br>11<br><br>12<br><br>13<br><br>14<br><br>15<br><br>16<br><br>17|`const strings = ['Hello', 'world', '!'];`<br><br>`// 将字符串存到内存中并获得字符串指针`<br><br>`const pointers = strings.map((str) => {`<br><br>  `const buf = Module._malloc(str.length + 1);`<br><br>  `Module.writeStringToMemory(str, buf);`<br><br>  `return buf;`<br><br>`});`<br><br>`// 非常重要,将内存中存储的字符串重新存储到连续的内存`<br><br>`const STRING_BYTE = 4;`<br><br>`const ptr = Module._malloc(pointers.length * STRING_BYTE);`<br><br>`for (let i = 0; i < pointers.length; i++) {`<br><br>  `Module.setValue(ptr + i * STRING_BYTE, pointers[i], 'i32');`<br><br>`}`<br><br>`const pointer = Module.ccall('mapStringArray', 'null', ['number', 'number'], [ptr, strings.length]);`|
在上面的方法中,我们首先将字符串存到内存中并得到了`字符串指针数组`,然后将字符串重新存储到了`连续的内存`中,这一步非常重要,直接影响C函数是否能正常取到字符串的值,需要注意C语言存储字符串时每个字符占4字节。
**获取C函数返回的字符串数组:**
| | |
|---|---|
|01<br><br>02<br><br>03<br><br>04<br><br>05<br><br>06<br><br>07<br><br>08<br><br>09<br><br>10|`const ret = [];`<br><br>`for (let i=0; i<strings.length; i++) {`<br><br>    `const ptr = Module.getValue(pointer + i * 4, 'i32');`<br><br>    `if (ptr === 0) {`<br><br>        `break;`<br><br>    `}`<br><br>    `const str = Module.UTF8ToString(ptr);`<br><br>    `ret.push(str);`<br><br>`}`<br><br>`console.log(ret); // ['Hello', 'world', '!']`|
**注意:**上面使用的`writeStringToMemory`,`setValue`,`getValue`,`UTF8ToString`需要通过`-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap", "writeAsciiToMemory", "writeStringToMemory", "setValue", "getValue", "UTF8ToString"]'` 导出。
不管是一维数组还是多位数组我们都需要提前知道返回数组的大小才能正确取得数据,如果输入的数组大小和输出的数组大小不一致时就很难正确的去除数据,所以我感觉C函数直接使用数组结构返回结果不是太灵活,其实我们可以换一种思路,比如将二维数组转换成字符串传递,接收到数据后再解析,类似下面这样:
| | |
|---|---|
|1|`2,3,1,2,3,4,5,6`|
通过上面的字符串描述一个`2 * 3`的二维数组,然后我们将其转换成真实的数组即可。
js的转换方法:
| | |
|---|---|
|01<br><br>02<br><br>03<br><br>04<br><br>05<br><br>06<br><br>07<br><br>08<br><br>09<br><br>10<br><br>11<br><br>12<br><br>13<br><br>14<br><br>15<br><br>16<br><br>17|`function str2Arr(str) {`<br><br>    `const arr = str.split(',')`<br><br>        `.map(m => Number(m));`<br><br>    `const ret = [];`<br><br>    `const row = arr[0];`<br><br>    `const col = arr[1];`<br><br>    `let index = 2;`<br><br>    `for (let i=0; i<row; i++) {`<br><br>        `ret[i] = [];`<br><br>        `for(let j=0; j<col; j++) {`<br><br>            `ret[i][j] = arr[index++];`<br><br>        `}`<br><br>    `}`<br><br>    `return ret;`<br><br>`}`<br><br>`str2Arr('2,3,1,2,3,4,5,6');`|
C语言的转换方法:
| | |
|---|---|
|01<br><br>02<br><br>03<br><br>04<br><br>05<br><br>06<br><br>07<br><br>08<br><br>09<br><br>10<br><br>11<br><br>12<br><br>13<br><br>14<br><br>15<br><br>16<br><br>17<br><br>18<br><br>19<br><br>20<br><br>21<br><br>22<br><br>23<br><br>24<br><br>25<br><br>26<br><br>27<br><br>28<br><br>29<br><br>30<br><br>31<br><br>32<br><br>33<br><br>34<br><br>35<br><br>36<br><br>37<br><br>38<br><br>39<br><br>40<br><br>41<br><br>42<br><br>43<br><br>44<br><br>45<br><br>46<br><br>47<br><br>48<br><br>49<br><br>50|`#include <stdio.h>`<br><br>`#include <string.h>`<br><br>`#include <stdlib.h>`<br><br>`int * split_int(char *str, char *sep) {`<br><br>    `static int ret[100];`<br><br>    `char *token;`<br><br>    `token = strtok(str, sep);`<br><br>    `int i = 0;`<br><br>    `while (token != NULL) {`<br><br>        `ret[i++] = atoi(token);`<br><br>        `token = strtok(NULL, sep);`<br><br>    `}`<br><br>    `return ret;`<br><br>`}`<br><br>`int ** str_2_matrix(char *str, int *r, int *c) {`<br><br>    `int *arr = split_int(str, ",");`<br><br>    `int row = arr[0];`<br><br>    `int col = arr[1];`<br><br>    `if (r != NULL) {`<br><br>        `*r = row;`<br><br>    `}`<br><br>    `if (c != NULL) {`<br><br>        `*c = col;`<br><br>    `}`<br><br>    `int index = 2;`<br><br>    `static int **ret;`<br><br>    `ret = (int **)malloc(row * sizeof(int *));`<br><br>    `for (int i=0; i<row; i++) {`<br><br>        `ret[i] = (int *)malloc(col * sizeof(int));`<br><br>        `for (int j=0; j<col; j++) {`<br><br>            `ret[i][j] = arr[index++];`<br><br>        `}`<br><br>    `}`<br><br>    `return ret;`<br><br>`}`<br><br>`int main() {`<br><br>    `char s[] = "2,3,1,2,3,4,5,6";`<br><br>    `int r, c;`<br><br>    `int **ret = str_2_matrix(s, &r, &c);`<br><br>    `for (int i=0; i<r; i++) {`<br><br>        `for (int j=0; j<c; j++) {`<br><br>            `printf("%6d", ret[i][j]);`<br><br>        `}`<br><br>        `printf("\n");`<br><br>    `}`<br><br>    `return 0;`<br><br>`}`|
本文到此就结束了,希望大家在对接wasm应用的过程中不再迷茫。
+19
View File
@@ -0,0 +1,19 @@
安装
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
3. **安装指定版本的 Node.js**:
nvm install 18.16.0 # 安装具体版本
nvm install --lts # 安装最新的LTS版本
nvm install node # 安装最新稳定版
4. **列出所有可用版本**:
nvm ls-remote # 查看远程所有版本
nvm ls # 查看本地已安装版本
5. **切换 Node.js 版本**:
nvm use 18.16.0 # 切换到指定版本
nvm use --lts # 切换到LTS版本
nvm use node # 切换到最新稳定版
@@ -0,0 +1,92 @@
Prisma 提供了一系列命令行工具来帮助开发者管理数据库和模型。以下是 Prisma 最常用的基本命令:
## 初始化与设置
1. **初始化 Prisma 项目**:
npx prisma init
创建 `prisma/schema.prisma` 文件和 `.env` 文件
## 数据库迁移相关
2. **创建新迁移**:
npx prisma migrate dev --name [migration_name]
开发环境使用,会自动应用迁移
3. **仅创建迁移文件而不应用**:
npx prisma migrate dev --create-only
4. **应用未执行的迁移**:
npx prisma migrate deploy
生产环境使用
5. **重置数据库并重新应用所有迁移**:
npx prisma migrate reset
6. **检查迁移状态**:
npx prisma migrate status
## 模型与数据库同步
7. **根据 schema 直接推送更改到数据库**:
npx prisma db push
- 不创建迁移记录,适合开发初期
8. **生成 Prisma Client**:
npx prisma generate
## 数据查看与操作
9. **启动 Prisma Studio (GUI 界面)**:
npx prisma studio
- 在浏览器中查看和编辑数据
10. **执行种子脚本**:
npx prisma db seed
## 诊断与维护
11. **验证 Prisma schema**:
npx prisma validate
12. **格式化 Prisma schema 文件**:
npx prisma format
13. **查看 Prisma 版本信息**:
npx prisma --version
14. **生成数据模型关系图**:
npx prisma generate --data-proxy
## 生产环境相关
15. **创建生产环境的迁移**:
npx prisma migrate diff
16. **检查数据库连接**:
npx prisma db execute --file ./script.sql --schema prisma/schema.prisma
## 高级命令
17. **使用 Prisma CLI 直接查询数据库**:
npx prisma db execute --stdin --file=-
18. **导入现有数据库结构**:
npx prisma db pull
19. **导出当前数据库结构**:
npx prisma db push --export
这些命令涵盖了 Prisma 日常开发中的大多数使用场景。根据你的具体需求(开发、测试或生产环境),可以选择合适的命令组合来管理你的数据库。
@@ -0,0 +1,148 @@
流式输入(Stream Input)是一种数据处理方式,其中数据不是一次性全部加载到内存中,而是以连续的数据流形式逐块处理。这种方式特别适用于处理大量数据或实时数据,因为它可以减少内存占用并提高处理效率。
流式输出(Stream Output)是一种数据处理方式,其中数据不是一次性全部生成并发送,而是以连续的数据流形式逐块生成并发送。这种方式特别适用于处理大量数据或实时数据,因为它可以减少内存占用并提高处理效率。
关键概念和应用场景:
## 关键概念
1. **数据流**:
- 数据流是指数据以连续的方式传输,而不是一次性传输完整个数据集。
- 数据流可以是字节流、字符流、文件流等。
1. **逐块处理**:
- 流式输入通常以固定大小的块(例如,几KB或几MB)读取数据,并在读取后立即处理这些块。
- 这种方式允许程序在数据到达时就开始处理,而不需要等待所有数据都加载完毕。
1. **低延迟**:
- 流式输入可以实现实时或近实时的数据处理,因为数据可以在到达时立即被处理。
1. **资源高效**:
- 由于数据是逐块处理的,流式输入可以显著减少内存占用,适用于处理大规模数据集。
## 应用场景
1. **文件读写**:
- 处理大文件时,可以使用流式输入逐块读取文件内容,避免一次性加载整个文件导致内存溢出。
- 例如,使用 Node.js 的 fs.createReadStream 方法读取大文件。
1. **网络通信**:
- 在网络编程中,数据通常以流的形式传输,例如 HTTP 请求和响应。
- 例如,使用 Node.js 的 http 模块处理 HTTP 请求和响应。
1. **实时数据处理**:
- 处理实时数据流,如传感器数据、日志数据等。
- 例如,使用 Apache Kafka 或 AWS Kinesis 处理实时数据流。
1. **多媒体处理**:
- 处理音频、视频等多媒体数据时,通常使用流式输入逐块读取和处理数据。
- 例如,使用 FFmpeg 处理视频流。
## 示例
## Node.js 中的流式文件读取
```text
const fs = require('fs');
// 创建一个可读流
const readStream = fs.createReadStream('large-file.txt', { highWaterMark: 1024 * 1024 }); // 每次读取1MB
// 监听数据事件
readStream.on('data', (chunk) => {
console.log(`Received ${chunk.length} bytes of data.`);
// 处理数据块
});
// 监听结束事件
readStream.on('end', () => {
console.log('No more data to read.');
});
// 监听错误事件
readStream.on('error', (err) => {
console.error('Error reading file:', err);
});
```
## 使用 Express 处理 HTTP 请求流
```text
const express = require('express');
const app = express();
app.get('/stream', (req, res) => {
// 创建一个可读流
const readStream = fs.createReadStream('large-file.txt');
// 将可读流管道到响应对象
readStream.pipe(res);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
```
## Node.js 中的流式文件写入
```text
const fs = require('fs');
// 创建一个可写流
const writeStream = fs.createWriteStream('large-file.txt', { highWaterMark: 1024 * 1024 }); // 每次写入1MB
// 生成数据并写入流
for (let i = 0; i < 1000; i++) {
const data = `This is line ${i}\n`;
writeStream.write(data, (err) => {
if (err) {
console.error('Error writing to file:', err);
}
});
}
// 结束写入
writeStream.end(() => {
console.log('File has been written successfully.');
});
// 监听错误事件
writeStream.on('error', (err) => {
console.error('Error writing to file:', err);
});
```
## 使用 Express 发送 HTTP 响应流
```text
const express = require('express');
const fs = require('fs');
const app = express();
app.get('/stream', (req, res) => {
// 创建一个可读流
const readStream = fs.createReadStream('large-file.txt');
// 将可读流管道到响应对象
readStream.pipe(res);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
```
## 总结
流式输入/出是一种高效的数据处理方式,特别适用于处理大量数据或实时数据。通过逐块处理数据,它可以减少内存占用并提高处理效率。