go언어의 io패키지의 Reader

Jane Yeonju Kim·2022년 5월 17일
0

Golang

목록 보기
4/4
post-thumbnail

GO언어의 io패키지는 바이트 스트림을 가지고 작업을 하기 위한 인터페이스와 헬퍼(함수)의 모음을 제공한다.



Reader 인터페이스

type Reader interface {
    Read(p []byte) (n int, err error)    
}

Reader는 버퍼(p)를 Read() 메서드에 전달함으로써 동작한다.
다시 말하면, 내용이 담길 버퍼는 Read() 메서드는 바이트 슬라이스(p) 형태로 하나의 인자로 전달된다.
반환되는 값으로는 버퍼에 담긴 내용의 길이(n)와 에러 여부(err)가 있다.


package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	// string을 Reader 인터페이스 타입으로 담을 변수 r 선언
	r := strings.NewReader("Hello, Reader!")

	// 내용을 읽어서 8바이트 크기만큼 담을 버퍼 b 선언
	b := make([]byte, 8)
    
	for {
    	// 읽을 내용.Read(버퍼) => 다른 언어도 같은 구조로 동작합니다!
		n, err := r.Read(b)
        // format %v 는 슬라이스, 구조체, 인터페이스 출력
        // format %q 는 문자열을 escape(특수문자 앞에 역슬래시)하여 출력
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}
}

/*
1. 첫번째 for문 출력 (길이 n, 에러여부 err, 버퍼에 있는 내용 b)
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"

2. 두번째 for문 출력 (버퍼길이보다 적게 읽어도 에러가 나지 않습니다!)
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"

3. 세번째 for문 출력
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""
*/

보장성 개선하기

만약에 항상 버퍼의 길이만큼 읽어오게 하려면 (버퍼 길이 <= 내용 길이)
== Read() 메서드의 보장성을 개선하려면

일부만 읽었을 때 io.ErrUnexpectedEOF 에러를 반환하는 io.ReadFull() 메서드를 사용하는게 더 적합하다.

// ReadFull method
func ReadFull(r Reader, buf[] byte) (n int, err error)



buf := make([]byte, 8)
if _, err := io.ReadFull(r, buf); err == io.EOF {
    return io.ErrUnexpectedEOF
} else if err != nil {
    return err
}

스트림 연결

MultiReader를 사용하면 이들을 하나의 Reader로 합칠 수 있다.

func MultiReader(readers ...Reader) Reader

예시: 디스크에 있는 데이터와 인메모리 헤더가 결합된 HTTP 요청을 보낼 수도 있을 것이다. 많은 사람들이 헤더와 파일을 인메모리 버퍼로 복사하려고 하지만 이는 느리고 많은 메모리를 사용할 수 있다. 다음과 같은 간단한 방법이 있다.

r := io.MultiReader(
    bytes.NewReader([]byte("...my header...")),
    myFile,
)

http.Post("http://example.com", "application/octet-stream", r)

MultiReader는 http.Post()가 두 개의 Reader를 하나의 연결된 Reader로 간주하도록 한다.

스트림 복제

func TeeReader(r Reader, w Writer) Reader

Reader는 Reader가 한 번 읽히면, 데이터를 다시 읽을 수 없다는 단점이 있다. 예를 들어, 애플리케이션이 HTTP 요청 파싱을 실패하면 파서는 이미 데이터를 다 사용했기 때문에 디버깅을 할 수 없을 것이다.

TeeReader는 Reader에서 데이터를 다 읽어버리는것에 방해받지 않으면서 Reader의 데이터를 저장하는 기능이 있다. 이 함수는 Reader(r을 말함)를 래핑하는 새로운 Reader를 생성한다.

새로운 Reader에서 읽는것들은 또한 w에 저장될 것이다. 이 Writer는 in-memory buffer부터 로그 파일, STDERR까지 그 어떤것도 가능하다.


스트림 길이 제한

func LimitReader(r Reader, n int64) Reader

스트림은 제한이 없기 때문에 몇몇 상황에선 메모리나 디스크 이슈를 일으킬 수 있다. 가장 일반적인 예시는 파일 업로드 엔드포인트이다. 엔드포인트는 일반적으로 디스크가 꽉 차는걸 방지하기위해 크기 제한을 가지고 있지만, 이를 직접 구현할 필요가 없이 LimitReader를 사용하면 된다!

LimitReader는 Reader가 전체 바이트 수를 제한하도록 래핑함으로써 이 기능을 제공한다.

LimitReader의 한가지 단점은 Reader로 읽는 데이터의 크기가 n을 초과하는지에 대한 여부를 알려주지 않는다는 것이다. 이는 단순히 r에서 n 바이트를 읽게되면 io.EOF를 반환할 것이다. 그래서 제한값을 n+1로 설정한 후 마지막 바이트을 보고 n 바이트보다 많은 바이트를 읽었는지 아닌지를 판별할 수 있다.




참고한 글들
mingrammer님의 io패키지 번역
go-walkthrough medium 원글

profile
안녕하세요! 김개발자입니다 👩‍💻 🐈

0개의 댓글