Skip to content

简单谈谈Buffer和Stream在空间和时间效率上的比较 #45

@wangning0

Description

@wangning0

首先让我们来看看buffer的模式: 所有从resource传来的数据都会被收集到一个buffer,当所有的数据收集完毕之后就会触发到一个回调函数中

15113343678528

而流可以允许我们边读边写,不会等到所有的数据都获取到后才会消费者

d1bf6071-d26f-4714-94a3-88e5d29249ce

上面的图告诉我们当每块数据从生产者哪里拿到后,会立刻发送给消费者,没有先将所有数据收集到buffer的

所以,这两种方法的区别是什么呢?总结来看,主要有两个:

  • 空间效率
  • 时间效率

而且,Node 中的strem还有一个很重要的优点:可组合性

空间效率

首先,流能够使我们将一些通过buffer不可能做的事情变的可能,比如,去获取一个很大的文件,几十MB或者是几十GB,很明显的,通过创造一个大的buffer去做是不合理的.(注意的是buffer使用的是v8的堆外内存)

加下来通过例子来说明

const fs = require('fs');
const zlib = require('zlib');

const file = process.argv[2];
const time = Date.now();
fs.readFile(file, (err, buffer) => {
    zlib.gzip(buffer, (err, buffer) => {
        fs.writeFile(file + '.gz', buffer, err => {
            console.log(Date.now() - time);
            console.log('file successfully compressed');
        })
    })
})

15113365580333

const fs = require('fs');
const zlib = require('zlib');
const file = process.argv[2];
const time = Date.now();
fs.createReadStream(file)
    .pipe(zlib.createGzip())
    .pipe(fs.createWriteStream(`${file}.gz`))
    .on('finish', () => {
        console.log(Date.now() - time);
        console.log('file successfully compressed');
    })

15113364980817

通过top命令我们可以和明显的看出node使用内存的变化,相差几很大,用buffer对于空间是一个很大的浪费

时间效率

现在让我们考虑一个应用场景,压缩一个文件然后上传到线上的http服务器上,然后解压保存到文件系统中,如果我们的客户端使用的是bufferAPI,那么只有当整个文件都读取并压缩了才开始上传,同样的,解压过程只有当接收到了所有的压缩文件才会开始解压。

如果是使用流,是一个更好的解决方案,因为我们可以一边读取一边压缩一边发送给服务端,服务端就可以一边读取一边解压

// 使用流的方法
// client.js
const fs = require('fs');
const zlib = require('zlib');
const http =  require('http');
const path = require('path');
const crypto = require('crypto');

const file = process.argv[2];
const server = process.argv[3];

const options = {
    protocol: "http:",
    hostname: server,
    path: '/',
    port: 3000,
    method: 'PUT',
    headers: {
        filename: path.basename(file),
        'Content-Type': 'application/octet-stream',
        'Content-Encoding': 'gzip',
        time: Date.now()
    }
}

const req = http.request(options, res => {
    console.log('Server response: ' + res.statusCode);
})
fs.createReadStream(file)
    .pipe(zlib.createGzip())
    .pipe(crypto.createCipher('aes192', 'secret'))
    .pipe(req)
    .on('finish', () => {
        console.log('File successfully sent');
    })
// server.js
const fs = require('fs');
const http = require('http');
const zlib = require('zlib');
const crypto = require('crypto');

const server = http.createServer((req, res) => {
    const filename = req.headers.filename;
    const time = req.headers.time;
    console.log('File request received: ' + filename);
    let index = 0;
    req
        .pipe(crypto.createDecipher('aes192', 'secret'))
        .pipe(zlib.createGunzip())
        .pipe(fs.createWriteStream(`server-${filename}`))
        .on('finish', () => {
            res.writeHead(201, {'Content-Type': 'text/plain'});
            res.end('over');
            console.log('File saved');
            console.log(Date.now() - time);
        })
})

server.listen(3000, () => {
    console.log('started');
})

进行上传一个1.3G左右的文件,运行后的时间可以看出时间差为

15114881570327

接下来我们用buffer的形式来试一试

// client.js
const fs = require('fs');
const http = require('http');
const zlib = require('zlib');
const path = require('path');

const filename = process.argv[2];
const server = process.argv[3];

const options = {
    port: 3001,
    hostname: server,
    method: 'PUT',
    protocol: 'http:',
    headers: {
        filename: path.basename(filename),
        'Content-Encoding': 'gzib',
        time: Date.now()
    }
}

const req = http.request(options, (res) => {
    console.log('client done');
})
fs.readFile(filename, (err, buffer) => {
    console.log('获取文件完毕\n');
    console.log('开始压缩');
    zlib.gzip(buffer, (err, buffer) => {
        console.log('压缩完毕');
        req.write(buffer);
        req.end();
    })
})

req.on('error', (err) => {
    console.log(err);
})

req.on('data', (data) => {
    console.log(data, 'data');
})
// server.js
const fs = require('fs');
const http = require('http');
const zlib = require('zlib');

const buffer_lists = [];
let size = 0;
const server = http.createServer((req, res) => {
    let index = 0;
    const filename = req.headers.filename;
    const time = req.headers.time;
    req.on('data', function(chunk) {
        buffer_lists.push(chunk);
        size += chunk.length;
    });
    req.on('end', function() {
        res.end('OK');
        const buffer = Buffer.concat(buffer_lists, size);
        zlib.gunzip(buffer, (err, buffer) => {
            fs.writeFile(`server-${filename}`, buffer, (err) => {
                console.log(Date.now() - time);
            })
        })
    })
})

server.listen(3001, () => {
    console.log('started');
});

上传同样的大小的文件,运行后看看所需要的时间

15114883699491

前后比较下可以得出,流的时间效率还是要高于buffer的时间效率的

15113530030783

上图可以比较好的描述这两种方法在时间上的效率

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions