1주차 파일 시스템

리프깅·2023년 6월 26일

node.js 스터디

목록 보기
1/3

💻 3.6 파일 시스템 접근

파일 시스템 접근이란 파일/폴더의 생성, 삭제, 읽기, 쓰기가 가능하다는 것

텍스트 파일 내용 불러오기

  1. 아무거나.txt 생성 -> 텍스트 저장

  2. 같은 폴더에 read.js 생성

const fs = require('fs');

fs.readFile('./아무거나.txt', (err, data) => {
	if(err) {
		throw eff;
	}
	console.log(data);
	console.log(data.toString());
});
  • 텍스트 파일 접근 경로 지정에 주의하기!
  • node read 실행 시 버퍼 값(data)과, 아무거나.txt 파일의 텍스트 내용이 나온다.

프로미스 형식

fs는 콜백 형식 모듈이니 사용하기 편하게 프로미스 형식으로 바꿔준다. 프로미스는 비동기 코드를 다루기에 에러 처리가 편리하고, 코드의 가독성을 높여준다.

const fs = require('fs').promises;

fs.readFile('./아무거나.txt')
	.then((data) => { //then, catch문으로 에러 처리 따로 빼기
		console.log(data);
		console.log(data.toString());
	}).catch((err) => {
		console.log(err);
	});

텍스트 파일 생성하기

const fs = reauire('fs').promises;

fs.writeFile('./writeme.txt', '글이 입력됩니다')
	.then(() => {
		return fs.readFile('./writeme.txt');
	}).then((data) => {
		console.log(data.toString());
	}).catch((err) => {
		console.error(err);
	});

3.6.1 동기 메서드와 비동기 메서드

노드는 기본적으로 비동기 방식으로 메소드를 처리하지만, fs 모듈이 가진 몇몇 메소드는 동기 방식으로 사용한다.

비동기

const fs = require('fs');

console.log('시작');
fs.readFile('./readme2.txt', (err, data) => {
	if(err) {
		throw err;
	}
	console.log('1번', data.toString());
}));
fs.readFile('./readme2.txt', (err, data) => {
	if(err) {
		throw err;
	}
	console.log('2번', data.toString());
}));
fs.readFile('./readme2.txt', (err, data) => {
	if(err) {
		throw err;
	}
	console.log('3번', data.toString());
}));
console.log('끝');
  • 실행 시 시작, 끝을 제외하면 번호의 출력 순서는 다 다르며, 실행할 때마다 계속 결과가 달라진다.

  • I/O 요청이 많이 들어와도 메인 스레드는 요청 처리를 백그라운드에게 위임하기 때문에 요청을 더 받아도 상관없다. 백그라운드가 처리 후 메인 스레드에게 알리면 메인은 그때 콜백 함수를 처리한다. -> 서버 같이 요청 수를 예측하기 어려울 때 사용하기 적합

동기

const fs = require('fs');

console.log('시작');
let data = fs.readFileSync('./readme2.txt');
console.log('1번', data.toString());
data = fs.readFileSync('./readme2.txt');
console.log('2번', data.toString());
data = fs.readFileSync('./readme2.txt');
console.log('3번', data.toString());
console.log('끝');
  • 동기 메소드인 readFileSync를 사용하면 코드는 직관적이지만, 요청이 많이 들어오면 readFile만큼 완벽히 요청을 처리하지 못한다. 이전 작업이 완료 되어야 다음 작업 시작이 가능하기 때문이다.

  • 백그라운드에서 열심히 작업을 처리하는 동안 메인 스레드는 백수... 비효율적이다. -> 프로그램 처음 실행할 때 초기화 용도로 적합
    ex) readFileSync는 자바스크립트로 백준 문제 풀 때 사용함

비동기 방식으로 순서를 유지하면서 출력하기

const fs = require('fs');

console.log('시작');
fs.readFile('./readme2.txt', (err, data) => {
	if (err) {
		throw err;
	}
	console.log('2번', data.toString());
	fs.readFile('./readme2.txt', (err, data) => {
		if (err) {
			throw err;
		}
		console.log('3번', data.toString());
		fs.readFile('./readme2.txt', (err, data) => {
			if (err) {
				throw err;
			}
			console.log('1번', data.toString());
		});
	});
});

이전 readFile의 콜백에 다음 readFile을 넣는다.

  • 위 방식은 코드가 길어지면 콜백이 엄청나게 길어진다. 이 문제는 Promise와 async/await로 어느정도 해결 가능함
const fs = require('fs').promises;

console.log('시작');
fs.readFile('./readme2.txt')
	.then((data) => {
		console.log('1번', data.toString());
		return fs.readFile('./readme2.txt');
	})
	// ...(2번 생략)...
	.then((data) => {
		console.log('3번', data.toString());
		console.log('끝');
	})
	.catch((err) => {
		console.error(err);
	});

3.6.2 버퍼와 스트림

왜 아까부터 data 뒤에 toString()을 붙이나요? data를 그대로 출력하면 안 되나요?

노드가 파일을 읽을 때 메모리에 파일 크기만큼 공간을 마련하고, 파일 데이터를 메모리에 저장한다. 버퍼는 이 메모리에 저장된 데이터이다. 우리가 코드에 쓰는 data는 이 버퍼를 출력시키는 것이다.
그런데 날것 그대로의 버퍼는 사용자가 읽기 몹시 불편한 형태를 가지고 있기 때문에 toString()으로 문자화를 시켜야 한다.

readFile 방식의 버퍼를 쓰면 편하긴 하지만, 100MB 파일을 읽을 때 메모리에 똑같이 100MB 버퍼 만들어야 한다. 서버에 이를 적용한다면, 10번의 요청이 들어올 경우 약 1GB의 메모리가 사용된다. 버퍼 대신 버퍼의 크기를 작게 만든 후 여러 번으로 나눠 보내는 스트림을 사용한다면 메모리 문제를 줄일 수 있다. 스트림은 100MB짜리 파일을 읽을 때 1MB 크기의 버퍼를 생성하고, 파일을 백 번 나눠서 읽는다.

3.6.3 파일/폴더 생성/삭제

const fs = require('fs').promises;
const constants = require('fs').constants;

fs.access('./folder', constants.F_OK || constants.W_OK || constants.R_OK)
	.then(() => {
		return Promise.reject('이미 폴더 있음');
	})
	.catch((err) => {
		if (err.code === 'ENOENT') {
			console.log('폴더 없음');
			return fs.mkdir('./folder');
		}
		return Promise.reject(err);
	})
  1. fs.access(경로, 옵션, 콜백): 폴더나 파일 접근 가능 체크
  • F_OK: 파일 존재 여부
  • R_OK: 읽기 권한 여부
  • W_OK: 쓰기 권한 여부
  • 권한 없으면 에러남(ENOENT)

  1. fs.mkdir(경로, 콜백): 폴더 생성
  • 폴더 이미 있으면 에러. access로 먼저 확인해야함

  1. fs.open(경로, 옵션, 콜백): 파일 아이디 가져오는 메서드. 아이디로 read/write 가능.
  • w 쓰기
  • r 읽기
  • 기존 파일에 추가 a

    만약 파일 아이디를 가져오는데 파일이 없다면? 옵션 w로 하면 파일 없을 때 새로 만듦


  1. fs.rename(기존 경로, 새 경로, 콜백): 파일 이름 수정.
  • 새 경로를 지정하지 않을 경우 잘라내기 가능

  1. fs.readdir(경로, 콜백): 폴더 안의 내용 확인

  1. fs.unlink(경로, 콜백): 파일 삭제
  • 파일 없으면 에러

  1. fs.rmdir(경로, 콜백): 폴더 삭제
  • 폴더 안에 파일 있으면 에러

  1. fs.copyFile(복사할 파일, 파일 경로, 콜백): 파일 복사

파일의 변경사항을 감지하는 watch 메서드

const fs = require('fs');

fs.watch('./target.txt', (eventType, filename) => { //target.txt는 비어있음
	console.log(eventType, filename);
});

node로 파일 실행 후 target.txt 수정 시, 콘솔 창에 change 이벤트 발생한다. 파일명이 변경되거나 삭제된 경우에는 watch 메소드가 더이상 실행되지 않는다.

3.6.4 스레드풀

fs 모듈을 포함한 여러 비동기 메서드들은 실행 시 백그라운드에서 실행되고, 실행이 끝나면 메인 스레드의 콜백 함수나 프로미스의 then 부분을 실행한다.

비동기 메서드를 여러 번 실행해도 백그라운드에서 동시에 처리될 수 있는 이유는 스레드풀이 있기 때문이다. fs 모듈은 내부에 스레드풀을 사용하는데, 이 외에도 내부적으로 스레드풀을 사용하는 모듈은 crypto(암호화 관련 모듈), zlib(파일 압출 관련 모듈), dns.lookup(DNS 조회 관련 모듈) 등이 있다.
3.6.2에서 비동기 메서드로 처리했을 때 번호 순서가 불규칙하게 나왔던 것이 바로 스레드풀이 작업을 동시에 처리하기 때문이다. 스레드풀의 개수에 따라 출력이 조절되는데, 스레드풀이 1개라면 비동기식이어도 순서대로 출력된다. 왜냐하면 작업이 한 번에 하나씩 처리되기 때문이다.

profile
대학교 마지막 학기 공부기록

0개의 댓글