[Node.js] Buffer & Stream

Hoplin·2023년 6월 17일
0

버퍼와 스트림

파일을 읽거나 쓰는 방식에는 두가지 방식이 있다.

  1. 버퍼를 사용하는 방식
  2. 스트림을 이용하는 방식

흔히 말해 버퍼링이란 영상을 재생할 수 있을때까지 데이터를 모으는 동작을 말하며, 스트리밍은 방송을 시청자 컴퓨터에 전송하는 동작이다. 여기너 나오는 버퍼스트림또한 비슷한 개념으로서 사용된다.

버퍼

Node.js에는 fs모듈이 있다. fs모듈은 파일과 관련된 메소드들이 존재한다. 여기서 readFile메소드는 텍스트를 버퍼로 읽어들이는 기능을 한다.

fs.readFile(`${__dirname}/../readme.txt`, (err, data) => {
  if (err) {
    throw err
  }
  console.log(data)
  console.log(data.toString())
})

/*
<Buffer 52 65 61 64 6d 65 20 70 6c 65 61 73 65>
Readme please
*/

Node.js에는 Buffer를 다룰 수 있는 Buffer클래스가 있다. Buffer객체의 메소드들을 살펴보자

메소드기능
from(문자열)문자열을 버퍼로 바꿀 수 있다. 변경된 버퍼의 length속성은 버퍼의 크기를 알리며 이는 바이트 단위이다.
toString(encodingType)버퍼를 다시 문자열로 바꾼다. base64,hex등 인코딩 타입을 지정해줄 수 있다.
concat(배열)배열안에 든 버퍼들을 하나로 합친다.
alloc(바이트)빈 버퍼를 생성한다. 바이트를 인수로 넣으면 해당 크기의 버퍼가 생성된다.
const buffer = Buffer.from('Hello world')
console.log('from() : ', buffer)
console.log('length : ', buffer.length)
console.log('toString() : ', buffer.toString())

const array = [Buffer.from('Node.js '), Buffer.from('buffer '), Buffer.from('concat '), Buffer.from('array')]
const buffer2 = Buffer.concat(array)
console.log('concat(), toString() : ', buffer2.toString())

const buffer3 = Buffer.alloc(5)
console.log('length : ', buffer3.length)

위의 readFile방식에는 단점이 있다. 용량이 100MB인 파일이 있다면 해당 100MB의 버퍼를 만들어야한다. 서버에서 해당 작업을 10개만 해도 1GB의 메모리를 점유하게 되고, 이는 결코 적은 용량의 메모리 사용량이 아니다.

이 단점을 해결하고자 버퍼의 크기를 작게 만든 후 여러번 나누어 보내는 방식이 생겼다. 100MB를 한번에 보내는것이 아닌 1MB를 100번 나누어 보내는것과 같은 방식으로 말이다. 파일을 읽는 스트림 메소드로는 fscreateReadStream이 있다.

createReadStream은 읽기 스트림을 생성하게 되는데, 첫번째 매개변수는 인수로 읽을 파일 경로를 넣고, 두번쨰 인수는 옵션 객체로서, highWaterMark옵션에는 버퍼의 크기를 정할 수 있다. 기본값은 64kb라고한다.

또한 Read Stream은 Event Emitter를 활용한다.(https://velog.io/@hoplin/Node.js-Event-Stream) 일반적으로 data,end,error 이벤트를 사용하게 된다.

  • data : 파일을 읽기 시작하면 호출된다.
  • end : 파일을 다 읽은 후에 발생한다
  • error : 파일을 읽는 도중 에러가 발생한다.

자세한건 document를 참고하자.

createReadStream을 활용한 파일 읽기를 해본다. data 이벤트가 발생하면 배열에 버퍼를 저장하고, end 이벤트가 발생하면 문자열로 합친다.

예시를 작성해 본다. 예시에서는 16바이트로 하여 나누어지는것을 확인한다.(예시로 사용한 텍스트가 짧기때문)

// readme.txt
this is example readme file. to test create read stream from fs module


// Code
import * as fs from 'fs'

const stream = fs.createReadStream(`${__dirname}/../readme.txt`, { highWaterMark: 16 })
const buffers = new Array<Buffer>();

stream.on('data', (chunk: Buffer) => {
  buffers.push(chunk);
  console.log(`data : `, chunk, chunk.length)
})

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

stream.on('end', () => {
  console.log(Buffer.concat(buffers).toString())
})
/*
data :  <Buffer 74 68 69 73 20 69 73 20 65 78 61 6d 70 6c 65 20> 16
data :  <Buffer 72 65 61 64 6d 65 20 66 69 6c 65 2e 20 74 6f 20> 16
data :  <Buffer 74 65 73 74 20 63 72 65 61 74 65 20 72 65 61 64> 16
data :  <Buffer 20 73 74 72 65 61 6d 20 66 72 6f 6d 20 66 73 20> 16
data :  <Buffer 6d 6f 64 75 6c 65> 6
this is example readme file. to test create read stream from fs module
*/

이번에는 반대로 파일을 작성할때 스트림을 사용해본다. 이를 위해서는 createWriteStream을 사용한다. createReadStream과 동일하게, Event Emitter를 사용한다. 대표적으로 finish이벤트를 사용하며, 이는 데이터를 다 썼을때 발생되게 된다.

데이터를 다 썼다고 알려주기 위해서는 end()메소드를 호출해준다.

const stream2 = fs.createWriteStream(`${__dirname}/../writeme.txt`)

stream2.on('finish', () => {
  console.log("Complete to write file")
})

stream2.write("write line 1\n")
stream2.write("write line 2")
stream2.end()

// writeme.txt
write line 1
write line 2

파이프

createReadStream으로 파일을 읽은 후 해당 스트림을 전달받아 createWriteStream으로 파일을 쓸 수 도 있다. 스트림끼리 연결하는것을 파이핑이라고 한다.

위에서 생성한 writeme.txt파일을 읽어 writeme2.txt파일에 쓰기를 해본다. 파이핑을 하기 위해서는 스트림의 pipe(스트림)메소드를 사용하면 된다.

import * as fs from 'fs'

const rStream = fs.createReadStream(`${__dirname}/../writeme.txt`)
const wStream = fs.createWriteStream(`${__dirname}/../writeme2.txt`)

rStream.pipe(wStream)

물론 현재는 fs모듈의 copyFile()메소드를 사용하여 파일을 복사할 수 있기도 하다.

import * as fs from 'fs/promises'

fs.copyFile(`${__dirname}/../writeme.txt`, `${__dirname}/../writeme3.txt`)
  .then(() => {
    console.log("Complete to copy")
  })
  .catch((err) => {
    console.error(err)
  })

pipe는 스트림 사이에 여러번 연결할 수 있다. 예를 들어 노드에서 zlib는 파일을 압축하는 모듈이다. 이 모듈의 createGzip이라는 메소드는 스트림을 지원한다. createReadStream, createWriteStream중간에 파이프를 놓으면, 버퍼가 전송되다 gzip 압축을 거친 후 파일로 작성된다.

import * as fs from 'fs'
import * as zlib from 'zlib'

const rStream = fs.createReadStream(`${__dirname}/../readme.txt`)
const zStream = zlib.createGzip();
const wStream = fs.createWriteStream(`${__dirname}/../readmezip.gz`)
rStream.pipe(zStream).pipe(wStream)
profile
더 나은 내일을 위해 오늘의 중괄호를 엽니다

0개의 댓글