패스트캠퍼스 챌린지 6일차 - 스트림과 노드
0.공부 기록
패캠 시작 12 : 20
점심시간 13:00 ~ 14 :00
패캠 끝~! 15 :30
인증샷
공부 노트 작성 = 코드블럭입니다.
컴퓨터 공부 특성 상 노트를 따록 작성하는 것보는 블로그의 코드블럭에 저장하는 것이 더 효과적인 것 같습니다
1. Stream 이란
스트림은 스트림 가능한 소스로부터 데이터를 작은 청크로 쪼개 처리할 수 있음!
큰 데이터(영상 등)를 처리해야 하거나 비동기적(네트워크 등)으로만 얻을 수 있는 데이터를 처리해야 할 때 유용함.
-> 큰 데이터를 쪼개어서 연산 실행
스트림의 일반적인 구현 형태
const fs = require('fs')
const rs = fs.createReadStream('file.txt')
rs.on('data', data => {})
rs.on('error', error => {})
rs.on('end', () => {})
// 여러 이벤트 핸들러를 달아서 처리함.
// 특별히 지정하지 않으면 data는 Buffer가 됨.
const rs = fs.createReadStream('file.txt', { encoding : 'utf-8' })
// 인코딩을 지정하면 data를 string으로 받아올 수 있음
2. 스트림의 종류와 스탠다드 라이브러리 구현체들
Readable 스트림으로부터 읽을 수 있음.
-> fs.createReadStream
-> process.stdin
-> 서버 입장의 HTTP 요청
-> 클라이언트 입장의 HTTP 응답
Writable 스트림을 출력할 수 있음.
-> fs.createWriteStream
-> process.stdout
-> 클라이언트 입장의 HTTP 요청
-> 서버 입장의 HTTP 응답
Duplex 입 출력이 가능한 스트림
-> TCP Socket
-> zlib stream :: 압축 관련
-> crypto stream :: 암호화 관련
Transform 입력받은 스트림을 새로운 스트림으로~
-> zlib stream :: 압축이라니깐~
-> crypto stream :: 암호화를 거치고 나오니깐~
3.스트림으로 사용해서 큰 데이터 처리해보기
500mb 파일을 생성하기
const fs = require('fs')
const ws = fs.createWriteStream('local/big-file')
const NUM_MB = 500 //500MB 파일을 만들것임.
for (let i = 0; i < NUM_MB; i += 1 ){
ws.write('a'.repeat(1024 * 1024)) //1바이트짜리가 1024 = 1mb,
}
* time node index.js :: 프로그램이 작동하는데 걸린 시간을 측정할 수 있음
500MB 파일을 읽어보기
const fs = require('fs')
const rs = fs.createReadStream('local/big-flie')
rs.on('data', data => {
log("EVE", data[0])
// 97 -> 버퍼의 0번째 자리를 읽은 것
// 97 은 소문자a의 아스키코드
})
rs.on('end', () => {
log("end")
// 모든 파일을 다 읽게 되면 출력됨
})
인코딩 값을 설정하고 문자열의 형태로 받아오기
const fs = require('fs')
const rs = fs.createReadStream('local/big-flie', {
encoding : 'utf-8'
})
let chunkCount = 0
rs.on('data', data => {
chunkCount += 1
log("EVE", data[0])
// a :: 스트링으로 출력이 되니깐~
})
rs.on('end', () => {
log(chunkCount)
// 8000 :: 8000번을 나눠서 데이터를 읽어온 것임
log("end")
})
스트링 파일에서 a의 연속 구간의 개수와 b의 연속 구간의 개수를 세는 프로그램
-> 버퍼로 처리를 해야한다면, 500mb를 램에 넣어야 했음.
-> 스트림 덕에 그러지 않아도 됨.
a 블럭과 b 블럭을 생성하는 코드
// @ts-check
const fs = require('fs')
const ws = fs.createWriteStream('local/big-file')
const NUM_MB = 500 //500MB 파일을 만들것임.
/** @type {Object.<string, number>} */
const numBlockPerChar = {
a : 0,
b : 0
}
for (let i = 0; i < NUM_MB; i += 1 ){
const blockLength = Math.floor(800 + Math.random() * 200)
const char = i % 2 === 0 ? 'a' : 'b'
ws.write(char.repeat(1024 * blockLength)) //1바이트짜리가 1024 = 1mb,
numBlockPerChar[char] += 1
}
console.log(numBlockPerChar)
a 블럭과 b 블럭의 개수를 세어보는 코드
스트림을 통해서 읽어오는 버퍼의 값을 키울 수도 있음(옵션값으로 highWaterMark 를 사용하면 됨 (기본 값 : 65536))
// @ts-check
const fs = require('fs')
const rs = fs.createReadStream('local/big-flie', {
encoding : 'utf-8',
highWaterMark : 65536 * 2 // 스크림의 청크의 개수가 절반으로 줄어듬
})
/** @type {Object.<string, number>} */
const numBlockChar = {
a : 0,
b : 0
}
/** @type {string | undifined} */
let prevChar
rs.on('data', data => {
if(typeof data !== 'string') return
for(let i = 0; i < data.length; i+=1){
if(data[i] !== prevChar){
const newChar = data[i]
if(!newChar) countinue
prevChar = newChar
numBlockChar[newChar] += 1
}
}
})
rs.on('end', () => {
log(chunkCount)
log(numBlockChar)
})
4. 퍼포먼스의 차이가 있을까? (스트림 VS 버퍼)
버퍼를 통해서 한번에 파일을 읽어오기
const data = fs.readFileSync('file', 'utf-8')
-> 버퍼의 경우 모든 내용을 램에 올려두고 작업이 끝날때까지 내리지 못하지만,
스트림의 경우에는 청크단위로 램에 올리기에 훨씬 메모리 쪽 부하가 덜함.
5. 스트림 활용의 주의점들
정상 JSON들에 한하여 data값을 모두 더하기
문제점 1 : 어디서 chunk 가 짤릴지 모름 !
예시 : (highWaterMark 의 기본 값이 충분히커서 에러X : 에러는 하기 예시 참고)
const { log } = console
const fs = require('fs')
const rs = fs.createReadStream('json', {encoding : "utf-8"})
let totalSum = 0
rs.on('data', data => {
if(typeof data !== 'string') return
// 이러면 에러가 발생함!
totalSum =data.split('/n')
.map(jsonLine => {
try{
retrun JSON.parse(jsonLine)
}catch{
return undifined
}
})
// 어라...이게 뭐지 filter는 뭐야!
.filter(json => json)
.map(json => json.data)
// reduce 는 그 전값과 지금 값을 가져올 수 있음.
// 그래서 덧셈이나 이런 작업을 하기에 좋음
.reduce( (sum, cur) => sum + cur, 0)
})
rs.ons('end', () => {
log("finished")
log(totalSum)
})
highWaterMark 의 값을 조절하고, 에러가 발생하는 부분을 보강하기
const { log } = console
const fs = require('fs')
const rs = fs.createReadStream('json', {
encoding : "utf-8",
highWaterMark : 6 // 한번에 읽어오는 문자가 6글자임.
// 이렇게 설정하면 정상적인 계산이 불가능 함.
//
})
let totalSum = 0
let accumJsonStr = ""
rs.on('data', chunk => {
if(typeof chunk !== 'string') return
accumJsonStr += chunk
const lastNewLineIdx = accumJsonStr.lstIndexOf('\n')
const jsonLineStr = accumJsonStr.substring(0, lastNewLineIdx) // JSON 이 여러개 있을 수 있는 블럭
accumJsonStr = accumJsonStr.substring(lastNewLineIdx) // 아직 JSON 이 완성되지 않은 블럭
totalSum =jsonLineStr.split('/n')
.map(jsonLine => {
try{
retrun JSON.parse(jsonLine)
}catch{
return undifined
}
})
// 어라...이게 뭐지 filter는 뭐야!
.filter(json => json)
.map(json => json.data)
// reduce 는 그 전값과 지금 값을 가져올 수 있음.
// 그래서 덧셈이나 이런 작업을 하기에 좋음
.reduce( (sum, cur) => sum + cur, 0)
})
rs.ons('end', () => {
log("finished")
log(totalSum)
})
5-2 : 파이프라인과 프로미스
파이프 라인을 통해서 압축 다루기 (복잡한 콜백을 프로미스로 전환하여 사용하기)
const { log, error } = console
const fs = require('fs')
const stream = require('stream')
const zlib = require('zlib')
const util = require('util')
async function gzip(){
util.promisity(stream.pipeline)(
fs.createReadStream('fileName')
zlib.createGzip(),
fs.createWriteStream('fileName.gz'),
)
}
gzip()
환급 챌린지 (6/30)
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
패스트캠퍼스 [직장인 실무교육]
프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.
fastcampus.co.kr
#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #한번에끝내는Node.js웹프로그래밍초격차패키지