06/04/2021, 14:50

ìm hiểu Streams trong NodeJS - NodeJS căn bản

Trong bài viết này chúng ta sẽ cùng nhau đi tìm hiểu về khái niệm Streams trong NodeJS và xem cách nó hoạt động như thế nào ? Streams không phải chỉ có trong NodeJs mà còn có trong rất nhiều các công nghệ khác nữa. Nhưng trong phạm vi bài viết này chúng ta sẽ chủ yếu tìm hiểu trong Nodejs mà ...

Trong bài viết này chúng ta sẽ cùng nhau đi tìm hiểu về khái niệm Streams trong NodeJS và xem cách nó hoạt động như thế nào ? Streams không phải chỉ có trong NodeJs mà còn có trong rất nhiều các công nghệ khác nữa. Nhưng trong phạm vi bài viết này chúng ta sẽ chủ yếu tìm hiểu trong Nodejs mà thôi.

1. Streams trong NodeJS là gì ?

Streams là một collections của dữ liệu giống như strings hay arrays, sự khác nhau duy nhất đó là các streams không tồn tại cùng một lúc do đó không cần chiếm nhiều bộ nhớ. Điều khiến streams thực sự mạnh đó là khả năng làm việc khi xử lý với dữ liệu lớn (big data) hay nguồn dữ liệu đến từ nguồn bên ngoài một chút một.

Streams không chỉ dừng lại ở việc xử lý dữ liệu, nó còn đem lại khả năng kết hợp code một cách tuyệt vời, giống như khi kết hợp các câu lệnh lunix với nhau thành một câu lệnh duy nhất.

Có rất nhiều các module của Nodejs thực hiện dựa trên hơi hướng của streams như:

streams module nodejs using stream png

Streams cho phép bạn đọc dữ liệu từ một nguồn hoặc viết dữ liệu đến một đích đến nào đó. Trong NodeJS, có 4 loại streams khác nhau:

  1. Readable: đây là loại streams chỉ dùng để đọc..
  2. Writable : đây là loại streams chỉ dùng để ghi..
  3. Duplex : sử dụng cả 2 đọc và ghi.
  4. Transform : một loại của Duplex nhưng khác nhau bởi kết quả của đầu ra dựa vào đầu vào.

Mỗi streams đều được cấp một EventEmitter cho phép chúng ta bắt sự kiện theo từng thời điểm cụ thể. Sau đây là một vài sự kiên hay dùng :

  • data: sự kiện này được kích hoạt khi dữ liệu được đọc
  • end: sự kiện này được kích hoạt khi không còn dữ liệu để đọc
  • error: sự kiện này được kích hoạt khi xảy ra lỗi.
  • finish: sự kiện này được kích hoạt khi hoàn thành (dữ liệu đã được đẩy hết về hệ thống cơ sở)

2. Obejct Mode trong Stream

Tất cả các streams được tạo bởi Nodejs API được hoạt động trên các đối tượng kiểu chuỗiBuffer (hoặc Uint8Array). Tuy nhiên có stream đôi lúc cần làm việc với các kiểu giá trị khác nhau của Javascript (ngoại trừ giá trị null).

Bởi vậy chế độ object mode được thêm vào, để sử dụng bạn cần phải chuyển sang chế độ object khi bắt đầu khởi tạo streams. Việc chuyển các streams đã được khởi tạo sang object mode là điểu không an toàn với dữ liệu.

3. Thao tác với Stream trong NodeJS

Streams là một chức năng rất mạnh trong Nodejs, tưởng tượng khi bạn làm việc với dữ liệu lớn mà muốn đọc file đó trực tiếp bạn cần phải có một vùng nhớ đủ lớn để lưu trữ nó. Giả sử chúng ta có một file text 10GB mà trong khi đó server của bạn chỉ có 1GB Ram, để đọc file này ngay thì server sẽ không thể thực hiên được vì thiếu bộ nhớ. Streams cho phép chúng ta đọc các dữ liệu lớn bằng cách chia nhỏ dữ liệu ra và đọc giá trị theo từng phần.

Đọc dữ liệu với Streams

Chúng ta có file dữ liệu dạng text ở file input.txt như sau :

Zaidap.com.net - Học lập trình Nodejs

Tạo file readStreams.js và khai báo sử dụng module fs,và sử dụng phương thức createReadStream() tham số là đường dẫn file bạn muốn đọc ở đây là input.txt

const fs = require("fs");
let data = '';

// Đọc file bằng streams bằng phương thức createReadStream
const readerStream = fs.createReadStream('input.txt');

// Kiểu mã hóa dùng là UTF8
readerStream.setEncoding('UTF8');

// Sự kiện khi đọc data
readerStream.on('data', function(chunk) {
   data += chunk;
});
//Khi kết thúc đọc data và in ra nội dung đã đọc
readerStream.on('end',function(){
   console.log(data)
});
//Khi xảy ra lỗi in ra lỗi
readerStream.on('error', function(err){
   console.log(err.stack);
});

Mở terminal và chạy dòng lệnh để xem kết quả:

node readStream.js

Sau khi chạy câu lệnh bạn sẽ thấy kết quả như hình bên dưới :

readstream trong nodejs png

Ghi dữ liệu với Streams

Để ghi một file từ streams chúng ta sử dụng phương thức createWriteStream() trong module fs. Tạo file writeStreams.js

const fs = require("fs");
let data = 'Zaidap.com.net - học lập trình Nodejs';

//Sử dụng phương thức createWriteStream
const writerStream = fs.createWriteStream('output.txt');

// Ghi dữ liệu vào file
writerStream.write(data);

// Đánh dấu đây là cuối file
writerStream.end();

// Bắt sự kiện finish của Streams
writerStream.on('finish', function() {
    console.log("Write done.");
});
// Bắt sự kiện error khi xảy ra lỗi 
writerStream.on('error', function(err){
   console.log(err.stack);
});
Mở terminal và chay dòng lệnh :
node writeStreams.js

Lúc này chúng ta sẽ nhận được 1 file mới có có tên output.txt có nội dung

Zaidap.com.net - học lập trình Nodejs

Kĩ thuật Piping Stream trong Node.js

Trong 2 phần trước chúng ta đã tìm hiểu về cách đọc và ghi file sử dụng Streams, trong Streams ta còn có một khái niệm khác nữa đó là Piping (đường ống) cho phép chúng ta lấy dữ liệu đầu ra từ một stream làm đầu vào trong streams khác. Nó hoạt động như một đường ống giúp chuyển dữ liệu giữa các streams với nhau.

Gộp 2 ví dụ trước, trong ví dụ này chúng ta lấy dữ liệu của file input.txt làm dữ liệu đầu vào cho file output.txt bằng cách tạo 2 streams và nối với nhau bằng phương thức pipe :

const fs = require("fs");

// Đọc stream đầu vào là file input bằng phương thức createReadStream
let readerStream = fs.createReadStream('input.txt');

// Ghi stream đầu ra là file input bằng phương thức createWriteStream
let writerStream = fs.createWriteStream('output.txt');

//Sử dụng khái niệm về Pipping để dùng dữ liệu đầu ra của readerStream làm giá trị đầu ra của writerStream
readerStream.pipe(writerStream);

Tiến hành chay đoạn code trên ta sẽ thất kết quả ở file output.txt

Zaidap.com.net - học lập trình Nodejs

Kỹ thuật Piping Chaining trong Node.js

Piping Chaining là kĩ thuật để kết nối đầu ra của các streams lại với nhau, nối đầu ra của streams này với streams khác tạo thành một chuỗi bao gồm nhiều các streams. Nó được sử dụng với cách hoạt động của piping. Chúng ta dùng kỹ thuật này để lấy đầu ra của một file, nén nó lại sau đó tiến hành ghi file nén đó ra nhé !

//Sử dụng thư viện fs
const fs = require("fs");

//Sử dụng thư viện zlib dùng để nén file
const zlib = require('zlib')

//Phương thức có nhiệm vụ nén file
const gzip = zlib.createGzip()

//Đọc streams để lấy kết quả đầu ra 
const readStream = fs.createReadStream('input.txt', 'utf8')

//Ghi streams ra file mới
const writeStream = fs.createWriteStream('output.txt.gz')

//Sử dụng kỹ thuật piping Chaining  
readStream.pipe(gzip).pipe(writeStream)

Khi chạy chương trình trên, chương trình sẽ lấy đầu ra của file input.txt để nén file sau đó sẽ tạo ra một file nén mới có tên output.txt.gz. Đây là một kỹ thuật được sử dụng khá nhiều.

3. Một số ví dụ với Streams trong NodeJS

Nếu như phần trên chúng ta tìm hiểu về các thao tác với streams thì trong phần này mình sẽ đưa ra vài ví dụ để chứng minh sức mạnh của Streams trong việc xử lý dữ liệu.

Ghi file dữ liệu lớn sử dụng Streams

Đối với việc ghi một file lớn một cách thông thường bạn cần phải sử dụng rất nhiều bộ nhớ để xử lý việc ghi file. Nhưng đối với streams việc ghi file dữ liệu lớn không tốn quá nhiều bộ nhớ.

const fs = require('fs');
const file = fs.createWriteStream('bigfile.txt');

for(let i=0; i<= 1e6; i++) {
  file.write('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
');
}

file.end();

Khi bạn ghi file bằng streams nó sẽ ghi vào file theo từng mảnh (chunk) chứ không ghi theo thành mộc cục to đùng khiến server của bạn không tải được mà die :))))

Đọc file dữ liệu lớn sử dụng Streams

Chúng ta có một file dữ liệu nhẹ nhàng vài GB, nếu bạn đọc theo cách thông thường thì có lẽ server sẽ quá tải, cách thức hoạt động của streams trong đọc file lớn là sẽ chia đọc theo từng phần. Khái niệm highWaterMark là kích cỡ của dữ liệu của mỗi chunk khi trong streams.

Giả sử dung lượng của file bigText.txt là 40GB, nếu ta đọc file theo cách thông thường và bị ăn khá là nhiều vùng nhớ :

const fs = require("fs");
fs.readFile("bigFile.txt","utf8", (err, data) => {
  if (err) {
    console.log(err);
  } else {
    console.log(data);
  }
});

Kết quả:

bigfile read jpg

còn đây là đọc file sử dụng streams :

const fs = require("fs");

// Đọc file bằng streams bằng phương thức createReadStream
const readerStream = fs.createReadStream('bigFile.txt');

// Kiểu mã hóa dùng là UTF8
readerStream.setEncoding('UTF8');

// Sự kiện khi đọc data
readerStream.on('data', function(chunk) {
   console.log(chunk)
});

bigfile read streams gif

Chắc hẳn qua 2 ví dụ này chúng ta có thể hiểu rõ hơn về Streams và sức mạnh của nó.

Trên đây là những kiến thức cơ bản về Streams trong Nodejs. Mong bài viết này có thể giúp ích cho bạn cho việc lập trình với Nodejs, cảm ơn bạn đã quan tâm bài viết này.

Hoàng Hải Đăng

24 chủ đề

7226 bài viết

Cùng chủ đề
0