file 모듈 그리고 버퍼와 스트림

younoah·2022년 1월 12일
0

[nodeJS]

목록 보기
3/15

Node API의 형태

NodeJS에서 제공하는 모든 api는 아래와 같이 3가지 형태로 제공한다.

  1. 비동기
  2. 동기
  3. 프로미스

예를들어 Node의 fs 제공하는 rename 메서드를 사용한다면 아래와 같이 3가지가 있다

1. 비동기

fs.rename(..., callback(error, data));

비동기적(Non-Blocking)으로 작업을 처리후 callback이 실행된다. 일반적으로 콜백함수의 인자로 errordata 를 전달받는다.

예제

const fs = require('fs');

fs.rename('./text-new.txt', './text.txt', (error) => {
  console.log(error);
});
console.log('hello');

2. 동기

fs.renameSync('./text.txt', './text-new.txt');

동기적(Blocking)으로 작업처리가 끝날 때까지 기다리다 비로소 다음 작업이 진행된다.

Node에서 제공해주는 API는 왠만하면 동기(sync)로는 사용하지 않는것이 좋다. 왜냐하면 비동기의 작업시점과 동기의 작업시점이 어떻게 맞물려서 동작하는지 추적하기 힘들기 때문이다.

예제

const fs = require('fs');

try {
  fs.renameSync('./text.txt', './text-new.txt');
} catch (error) {
  console.error(error);
}

동기적으로 동작하는 메서드는 위와 같이 try-catch 로 에러를 처리해준다.

에러가 발생할 수 있는 코드는 항상 에러처리를 해주어야 뒤의 작업들을 온전하게 진행할 수 있다.

3. 프로미스

fs.promise.rename()

프로미스에서 메서드 체인으로 사용하는 방식이다. 프로미스이기 대문에 비동기적으로 동작한다.

예제

const fs = require('fs');

fs.promises
  .rename('./text2.txt', './text-new.txt') //
  .then(() => console.log('Done!'))
  .catch(console.error);

file 모듈의 다양한 사용법

// text.txt

Hello!
const fs = require('fs').promises; // fs.promise를 가지고 온다.

// reading a file
fs.readFile('./text.txt')
  .then(data => console.log(data))
  .catch(console.error);
// 출력
// <Buffer 48 65 6c 6c 6f 21>
// 파일을 읽어올 때 2번째 인자로 인코딩에 아무런 값을 전달하지 않으면 버퍼로 읽어온다.

fs.readFile('./text.txt', 'utf8') // 파일을 utf-8로 인코딩하여 읽기
  .then((data) => console.log(data))
  .catch(console.error);
// 출력
// Hello!

// writing a file
// 파일에 글 작성하기
fs.writeFile('./file.txt', 'Hello, Dream Coders! :) ') //
  .catch(console.error);

// 기존 파일에 글 추가하기
fs.appendFile('./file.txt', 'Yo!, Dream Coders! :) ') //
  .catch(console.error);

// copy - 파일복사
fs.copyFile('./file.txt', './file2.txt') //
  .catch(console.error);

// folder
// 폴더 생성
fs.mkdir('sub-folder') //
  .catch(console.error);

// 폴더 정보 읽어오기, 폴더 내부에 있는 파일, 폴더들을 리스트 형태로 반환
fs.readdir('./') //
  .then(console.log)
  .catch(console.error);
// 출력
// 
  'app.js',
  'file.txt',
  'file2.txt',
  'sub-folder',
  'text.txt'
]

버퍼와 스트림

버퍼와 스트림은 파일을 읽거나 쓸 때 사용하는 방식이다.

버퍼란

용향이 큰 데이터를 전송할 때 한 번에 전송하지 않고 특정 단위 만큼 자르고 묶어서 전송을 하기 위해 사용되는 개념이다.

버퍼의 크기만큼 데이터를 순차적으로 버퍼에 담아서 전송한다.

버퍼는 숫자의 배열이고 메모리의 데이터를 가리키며 데이터의 바이트 그 자체이다.

img_c_buffer_vs_nobuffer

스트림이란

버퍼를 하나씩 전송하는 중간 다리 역할이다.

잘개 나누어진 버퍼 데이터를 조금씩 보내준것을 스트리밍이라고 한다.

img_c_stream

(이미지 출처: https://tcpschool.com/cpp/cpp_io_streamBuffer)

간단하게 말하면 버퍼는 데이터를 잘개 쪼갠 단위! 스트림은 버퍼를 전송하기 위한 연결통로 정도라고 생각하면 될 것 같다.

NodeJS에서 버퍼

nodeJS에서 버퍼를 어떻게 사용하는지 알아보자.

const buf = Buffer.from('hello');
console.log(buf);
// <Buffer 68 65 6c 6c 6f>,
// 버퍼는 데이터가 있는 메모리를 가리킨다.
// 따라서 유니코드의 16진수 표기법의 형태로 hello가 표현되어있다.

console.log(buf.length); // 5

// 한 글자씩 출력할 경우 아스키코드의 10진수 표기법으로 표현된다.
console.log(buf[0]); // 104
console.log(buf[1]); // 101

console.log(buf.toString('utf-8')); // 기본값은 'utf-8'이므로 생략가능
// hello

// create - 버퍼 생성
const buf2 = Buffer.alloc(2); // 메모리에서 사이즈 2짜리 공간을 할당
const buf3 = Buffer.allocUnsafe(2); 
// 메모리를 찾고 초기화 하지 않는 방식, 따라서 빠르지만 운이 없으면 이상한데이터가 미리 할당되어있을수도 있음
buf2[0] = 72;
buf2[1] = 105;
buf2.copy(buf3); // buf2 를 buf3으로 복사
console.log(buf2.toString()); // Hi
console.log(buf3.toString()); // Hi

// concat
const newBuf = Buffer.concat([buf, buf2, buf3]);
console.log(newBuf.toString());
// helloHiHi

NodeJS에서 스트림

읽기 스트림

const fs = require('fs');

const readStream = fs.createReadStream('./file.txt', {
  highWaterMark: 8, // 스트림의 크기를 8바이트로 지정, 8글자씩 읽어온다.  기본값 64 kbytes
  encoding: 'utf-8', // 기본값 'utf-8'
});

// data 이벤트 리스닝, 데이터가 읽어질 때 마다 발생
readStream.on('data', chunk => {
  data.push(chunk);
});

// 종료 이벤트 리스닝, 딱 처음 한번만 end 이벤트를 캐치한다.
readStream.once('end', () => {
  console.log(data.join(''));
});

// 에러 이벤트 리스닝
readStream.on('error', error => {
  console.error(error);
});

// on은 자기 자신을 리턴한다. 따라서 체이닝해서 구현하면 가독성이 좋아진다.
const data = [];

fs.createReadStream('./file.txt', {
  highWaterMark: 8,
  encoding: 'utf-8',
}).on('data', chunk => {
  data.push(chunk);
}).on('end', () => {
  console.log(data.join(''));
}).on('error', error => {
  console.error(error);
})

작성 스트림

const writeStream = fs.createWriteStream('./text.txt');
writeStream.on('finish', () => {
  console.log('finished!');
});

writeStream.write('hello!');
writeStream.write('world!');
writeStream.end();

파이프

readStream.pipe 메서드를 사용하면 여러 스트림끼리 파이프처럼 연결할 수 도 있다.

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

const readStream = fs.createReadStream('./file.txt');
const zlibStream = zlib.createGzip();
const writeStream = fs.createWriteStream('./result.zip');
const piping = readStream.pipe(zlibStream).pipe(writeStream);
piping.on('finish', () => {
  console.log('done!!');
});
profile
console.log(noah(🍕 , 🍺)); // true

0개의 댓글