파일 시스템에 접근하는 모듈로,
파일 생성, 삭제, 읽기, 쓰기 모두 가능하다.
일단 readme.txt 파일을 같은 폴더 내에 생성해줬다!
문구는 "저를 읽어주세요" 라고 작성했다.
const fs = require('fs');
fs.readFile('./node.js_test/Test9/readme.txt', (err,data) => {
if(err) {
throw err;
}
console.log(data);
console.log(data.toString());
});
파일 경로는 본인 파일 경로로 설정해주면 된다~
오류가 발생할 경우 throw err 해준다.
<Buffer ec a0 80 eb a5 bc 20 ec 9d bd ec 96 b4 ec a3 bc ec 84 b8 ec 9a 94>
저를 읽어주세요
readFile의 결과물은 버퍼라는 형식으로 제공된다.
(메모리의 데이터 형식이라고 이해하깅)
버퍼는 사람이 읽을 수 있는 형식이 아니므로, toStrung()을 이용해
문자열 형식으로 변환해준다.
const fs = require('fs');
fs.writeFile('./node.js_test/Test9/writeme.txt', '글이 입력됩니다.', (err) => {
if(err) {
throw err;
}
fs.readFile('./node.js_test/Test9/writeme.txt',(err,data) => {
if(err) {
throw err;
}
console.log(data.toString());
});
});
글이 입력됩니다.
노드는 대부분 메서드를 비동기 방식
으로 처리하지만,
몇몇 메서드는 동기 방식
으로 처리가 가능하다.
특히 fs모듈
이 그러한 메서드를 많이 가지고 있다고 한다!
일단 예제를 확인하기 위해 같은 폴더 내에 readme2.txt 파일을 생성했다.
파일 내에 "저를 여러번 읽어보세요" 라고 작성했다.
const fs = require('fs');
console.log('시작');
fs.readFile('./node.js_test/Test9/readme2.txt',(err, data) => {
if(err) {
throw err;
}
console.log('1번', data.toString());
});
fs.readFile('./node.js_test/Test9/readme2.txt',(err, data) => {
if(err) {
throw err;
}
console.log('2번', data.toString());
});
fs.readFile('./node.js_test/Test9/readme2.txt',(err, data) => {
if(err) {
throw err;
}
console.log('3번', data.toString());
});
console.log('끝');
시작
끝
2번 저를 여러번 읽어보세요
3번 저를 여러번 읽어보세요
1번 저를 여러번 읽어보세요
시작과 끝을 제외한 결과의 순서는 다를 수 있다!
-> 비동기 메서드
들은 백그라운드에 해당 파일을 읽으라고 요청만 하고, 다음 작업으로 넘어간다.
따라서, 파일 읽기 요청만 3번을 보내고 끝을 찍어준다.
나중에 읽기가 완료 되면, 백그라운드가 다시 메인 스레드에 알림을 주게되는데, 메인 스레드는 그제서야 등록된 콜백 함수를 실행한다.
이로인해, 수백개의 I/O 요청
이 들어와도 메인스레드는 백그라운드에 요청 처리를 위임하기 때문에 얼마든지 요청을 더 받을 수 있다.
나중에 백그라운드가 각각의 요청 처리가 완료 되었다고 알리면, 그때 콜백 함수를 처리하면 된다.
또한, readFileSync()
란 메서드를 사용할 경우 원하는 순서대로 작업 수행이 가능하지만, 요청이 수백개 이상 들어올 경우 성능에 문제가 생긴다.
(이름 뒤에 Sync가 붙을 경우 동기메서드)
비동기 방식으로 하되 순서를 유지하고 싶은 경우에는,
const fs = require('fs');
console.log('시작');
fs.readFile('./node.js_test/Test9/readme2.txt',(err, data) => {
if(err) {
throw err;
}
console.log('1번', data.toString());
fs.readFile('./node.js_test/Test9/readme2.txt',(err, data) => {
if(err) {
throw err;
}
console.log('2번', data.toString());
fs.readFile('./node.js_test/Test9/readme2.txt',(err, data) => {
if(err) {
throw err;
}
console.log('3번', data.toString());
});
});
});
console.log('끝');
이런 식으로 콜백 다음 또 다른 콜백을 넣어주면 된다.
이럴 경우 소위 말하는 콜백지옥
이 펼쳐지지만 적어도 순서는 어긋나지 않는다.
(콜백 지옥은 Promise나 async/await로 어느 정도 해결 가능하다.)
버퍼
와스트림
모두 파일을 읽거나 쓰는 방식으로,
버퍼링과 스트리밍이라는 용어와 비슷한 개념이다.
노드는 파일을 읽을 때 메모리에 파일 크기만큼 공간을 마련해두며, 파일 데이터를 메모리에 저장한 뒤 사용자가 조작할 수 있도록 해준다.
여기서 메모리에 저장된 데이터를버퍼
라고 한다.
버퍼는 Buffer 클래스
를 이용해서 다룰 수 있다.
const buffer = Buffer.from('저를 버퍼로 바꿔보세요');
console.log('from() : ',buffer);
console.log('length() : ',buffer.length);
console.log('toString() : ',buffer.toString());
const array = [Buffer.from('띄엄'),Buffer.from('띄엄'),Buffer.from('띄어쓰기')];
const buffer2 = Buffer.concat(array);
console.log('concat() : ',buffer2.toString());
const buffer3 = Buffer.alloc(5);
console.log('alloc() : ',buffer3);
실행 결과
from() : <Buffer ec a0 80 eb a5 bc 20 eb b2 84 ed 8d bc eb a1 9c 20 eb b0 94 ea bf 94 eb b3 b4 ec 84 b8 ec 9a 94>
length() : 32
toString() : 저를 버퍼로 바꿔보세요
concat() : 띄엄띄엄띄어쓰기
alloc() : <Buffer 00 00 00 00 00>
from(문자열) : 문자열을 버퍼로 바꾼다
length : 버퍼의 크기 출력
toString(버퍼) : 버퍼를 다시 문자열로 바꾼다
concat(배열) : 배열 안에 든 버퍼들을 하나로 합친다
alloc(바이트) : 빈 버퍼를 생성한다
readFile()
방식의 버퍼가 편하기는 하지만, 용량이 큰 파일이 있으면 그만큼의 버퍼를 만들어야 한다는 단점이 있다.
또한, 모든 내용을 버퍼에 다 쓴 후에야 다음 동작으로 넘어가기 때문에 파일 읽기, 압축, 쓰기 등의 조작을 연달아 할때마다 매번 전체 용량을 버퍼로 처리해야 한다는 단점이 있다.
따라서 버퍼의 크기를 작게 만들어서 여러번에 나눠서 보내는 방식이 등장했다. 이를 편리하게 만든것을 스트림
이라고 한다.
여기서 조금씩 나눠진 조각을 chunk
라고 부른다.
예제 테스트를 위해서, readme3.txt
라는 파일을 만들어주고 파일 내에 자유롭게 문구를 기입해줬다.
그리고 createReadStream.js
파일을 생성해서 코드를 입력했다! (createReadStream 매서드
는 파일을 읽을 때 사용한다.)
const fs = require('fs');
const readStream = fs.createReadStream('./node.js_test/Test10/readme3.txt',{highWaterMark : 16});
const data = [];
readStream.on('data',(chunk) => {
data.push(chunk);
console.log('data : ',chunk, chunk.length);
});
readStream.on('end',() => {
console.log('end :',Buffer.concat(data).toString());
});
readStream.on('error',(err) => {
console.log('error :',err);
});
data : <Buffer ec a0 80 eb a5 bc 20 ec a0 84 eb 8b ac ed 95 b4> 16
data : <Buffer ec a3 bc ec 84 b8 ec 9a 94 2e 20 ec a1 b0 ea b8> 16
data : <Buffer 88 ec a1 b0 ea b8 88 ec 94 a9 20 eb 82 98 eb 88> 16
data : <Buffer a0 ec 84 9c 20 ec a0 84 eb 8b ac ed 95 b4 ec a3> 16
data : <Buffer bc ec 84 b8 ec 9a 94 2e> 8
end : 저를 전달해주세요. 조금조금씩 나눠서 전달해주세요.
여기서, highWaterMark
라는 옵션에 버퍼의 크기(바이트 단위)를 지정해준다. 위 코드에서는 16B로 지정하였는데, 기본값은 64KB 정도이다.
readStream
은 이벤트 리스너를 붙여서 사용하는데, 보통 data,end,error 이벤트를 사용한다.
위 예제의 readStream.on('data')
같이 이벤트 리스너를 붙이면 되는데, 파일을 읽는 도중 에러가 발생하면 error 이벤트가 호출되고, 파일 읽기가 시작되면 data 이벤트가, 파일을 다 읽으면 end 이벤트가 발생한다.
다음은 pipe 메서드
를 이용해볼건데, pipe 메서드
스트림끼리 연결을 하기위해 사용한다.
연결을 하기 위해, readme4.txt
파일을 만들어줬는데, 파일 내에는 전달하고 싶은 문구를 작성해줬다.
이를 통해서 writeme3.txt
파일에 문구를 복사 해 줄것이다.
const fs = require('fs');
const readStream = fs.createReadStream('readme4.txt');
const writeStream = fs.createWriteStream('writeme3.txt');
readStream.pipe(writeStream);
writeme3.txt
파일이 생성되었고, readme4.txt
파일에 작성했던 문구가 이동되었다.
해당 메서드는 파일 복사
를 위해 자주 사용된다고 한다.
또한, zlib 모듈
을 이용하면 파일 압축 후 저장도 가능하다고 하니 참고하면 좋을 것 같다!
fs.access(경로, 옵션, 콜백) : 폴더나 파일에 접근할 수 있는지를 체크
-> F_OK : 파일 존재 여부
-> R_OK : 읽기 권한 여부
-> W_OK : 쓰기 권한 여부
-> ENOENT : 에러 코드
fs.mkdir(경로, 콜백) : 폴더를 만드는 메서드
fs.open(경로, 옵션, 콜백) : 파일의 아이디(fd 변수)를 가져오는 메서드
fs.rename(기존 경로, 새 경로, 콜백) : 파일의 이름을 바꾸는 메서드
fs.readdir(경로, 콜백) : 폴더 안의 내용물 확인
fs.unlink(경로, 콜백) : 파일 삭제
fs.rmdir(경로, 콜백) : 폴더 삭제