...
버퍼 (Buffer)
기본적으로 자바스크립트는 이진 데이터(binary data)를 다룰 수 없다.
그런데 서버로 활용하는 노드에서는 TCP streams 이나 파일을 읽고 쓸 수 있어야 한다. 그래서 등장한 것이 buffer이다.
쉽게 말해 파일을 읽고 쓰는데 전송되는 이진 데이터를 buffer로 변환해서 활용하는 것이라고 이해하면 된다.
아래 코드는 node 내장 모듈인 fs의 readFile을 통해 파일을 읽고, 콜백으로 반환된 결과를 출력해 보면 파일 내용이 buffer로 변환한 것을 볼수 있다.
const fs = require("fs");
// 파일 읽기
fs.readFile("./memo.txt", (err, data) => {
if (err) {
console.warn(err);
}
console.log(data); // <Buffer 72 65 61 64 20 6d 65 20 62 72 6f 21>
});
버퍼는 데이터를 조각(청크, chunk)내어 buffer에 채운 후 다 차면 buffer를 통째로 옮기고 새 buffer에 아직 옮기지 못한 데이터 조각을 다시 채운다.
이러한 데이터 조각을 buffer에 채우는 일을 한번쯤 들어본 버퍼링(buffering)이라고 부른다.
흔히 일상생활에서 영상이 버퍼링 중이라며 재생되지 않는 경우를 종종 경험했을텐데, buffer에 데이터를 채울 때까지 기다리는 버퍼링 작업을 말하는 것이다.
정리하자면 버퍼(buffer)란 일정한 크기로 모아두는 데이터 공간이며, 일정한 크기가 차면 한 번에 처리된다.
따라서 보통 파일보다 메모리가 커야된다.
Buffer 메소드
- Buffer.from(문자열): 문자열을 버퍼로 변경
- Buffer.toString(버퍼): 버퍼를 다시 문자열로 변경, hex나 base64를 인자로 넣으면 인코딩으로도 변환 가능
- Buffer.concat(배열): 배열 안에 든 버퍼들을 하나로 합침
- Buffer.alloc(바이트): 빈 버퍼를 생성, 바이트를 인자로 지정하면 해당 크기의 버퍼가 생성
const buffer = Buffer.from("저를 버퍼로 바꿔보세요"); // 스트링을 버퍼로 변환
console.log('from() : ', buffer); // 버퍼 2진수를 16진수로 표현
console.log('length : ', buffer.length); // 32바이트
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); // 5바이트
console.log('alloc() : ', buffer3);
스트림 (Stream)
스트림(stream)이란 데이터의 흐름을 말한다.
일상생활에서 한번쯤 들어본 스트리밍(streaming)이 일정한 크기의 데이터를 지속적으로 전달하는 작업을 일컫는다.
파일 다운 받을때 11%.. 35%.. 85% 로 다운 로딩이 되는데 이게 바로 스트림인 것이다.
이처럼 일정한 크기로 나눠서 여러 번에 걸쳐서 처리하는데, 버퍼(또는 청크)의 크기를 작게 만들어서 주기적으로 데이터를 전달하는 방식이다.
어렵게 이해할 필요없이, 메모리가 부족해 버퍼를 여러개 보내야 한다면 그게 스트림 이다.
스트림을 쓰는 이유는,
만일 100 byte 짜리 파일을 읽는다고 한다면, 버퍼로 읽을때 100 byte 만큼의 메모리 크기가 필요하다.
하지만 스트림으로 읽을때는 10byte만 있으면 충분하다. 왜냐하면 여러번 끊어서 읽기 때문에 메모리를 아낄수 있기 때문이다.
그래서 대용량 파일 서버를 구축할때는 스트림은 필수라고 봐도 무방하다.
파일 읽기 Stream
읽기 스트림을 만든다. highwatermark라는 옵션으로 버퍼의 크기를 정할 수 있다.
※ highWatermark는 버퍼의 크기를 나타내며 객체 모드의 경우는 객체의 수를 나타낸다.
- 이벤트 리스너를 붙여서 사용
- data, end, error이벤트를 사용
// text.txt
저는 조금씩 조금씩 나눠서 전달 됩니다. 나눠진 조각을 chunk라고 부릅니다.
안녕하세요. 반갑습니다. Hello World !!
const fs = require('fs')
// createReadStream은 한번에 64kbye를 읽는다. 이를 반복 스트림하는 동작
// highWaterMark 옵션을 바꿔준다. 기본은 64000바이트 이다. 16바이트로 바꿔준다.
const readStream = fs.createReadStream('./text.txt', { highWaterMark: 16}) // 전체 파일을 지정한 크기의 chunk로 읽는다.
const data = [] ;
// chunck크기 만큼 읽을 때 마다 이벤트를 발생시킨다. 파일이 크면 여러번 발생됨
readStream.on('data', (chunck) => {
data.push(chunck)
console.log('data : ', chunck, chunck.length)
})
// chunck읽기가 다 끝마친 경우
readStream.on('end', () => {
console.log('end :', Buffer.concat(data).toString()); // chunck로 나뉘어진 버퍼 데이터들을 합친다.
})
// stream도 비동기라, 비동기들은 항상 에러 처리를 직접 정의해 줘야 한다.
readStream.on('error', (err) => {
console.log('error : ', err);
})
파일 쓰기 Stream
- write()메서드로 넣을 데이터를 쓴다. 여러 번 호출가능
- end()메서드로 종료를 알려야 한다.
- finish이벤트 : end()메서드가 호출될 때 발생
const writeStream = fs.createWriteStream('./text3.txt')
writeStream.on("finish", () => {
console.log('파일 쓰기 완료');
})
// 지정한 경로에 파일 내용 쓰기
writeStream.write("이 글을 씁니다.\n") // 대용량 데이터일 경우, 보낼때도 chunck로 짤라서 보낸다.
writeStream.write("한 번 더 씁니다.\n")
writeStream.end(); // 파일 쓰기 완료
// text3.txt
이 글을 씁니다.
한 번 더 씁니다.
버퍼와 스트림 메모리 비교하기
1) 용량이 매우 큰 파일 생성
createWriteStream으로 만들어야 메모리 문제가 생기지 않음.
2) 버퍼 형식으로 그대로 복사해보기
- 18메가 -> 1기가
3) 스트림 파이프 형식으로 복사해보기
- 18메가 -> 62메가
파이프 (Pipe)
파이프란 말그대로 버퍼들을 파이프처럼 연결하는 것을 말한다.
파일 읽기 스트림과 쓰기 스트림을 서로 연결하여 버퍼 데이터를 다룰때 자주 쓰인다.
const fs = require('fs');
const readStream = fs.createReadStream('text2.txt'); // 스트림으로 읽을 파일
const writeStream = fs.createWriteStream('text3.txt'); // 스트림으로 쓸 파일
// text2 파일에서 text3 파일로 내용을 조금씩 chunck로 복사해서 씀
// 어떻게 보면 그냥 파일 복사
readStream.pipe(writeStream);
파일 압축 / 파이프 체이닝
const fs = require('fs');
const zlib = require('zlib')
const readStream = fs.createReadStream('text2.txt'); // 스트림으로 읽을 파일
const zlibStream = zlib.createGzip(); // .gz 압축파일 생성
const writeStream = fs.createWriteStream('text3.txt'); // 스트림으로 쓸 파일
// text2에서 읽은 chunck내용을 압축하고, 압축된 내용을 text3으로 씀
// 파이프끼리 체이닝으로 여러번 연결될 수 있다.
readStream
.pipe(zlibStream)
.pipe(writeStream);
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.