CodeGym /행동 /C# SELF /바이트 스트림 다루기

바이트 스트림 다루기

C# SELF
레벨 36 , 레슨 4
사용 가능

1. 소개

우리는 이미 스트림 (Stream)이 뭔지 알고 있지. 이건 데이터를 순차적으로 읽거나 쓸 수 있게 해주는 추상화야. FileStream으로 파일을 바이트 단위로 다뤄봤고, StreamReaderStreamWriter로 텍스트 데이터도 편하게 다뤄봤지. 사실 얘네도 내부적으로 FileStream을 쓰고, 인코딩도 신경 써줘.

근데 만약 우리가 파일에 그냥 텍스트가 아니라, 타입이 딱 정해진 데이터(예를 들어 int 정수, double, float 실수, bool 불린, DateTime 날짜, 혹은 커스텀 구조체 등)를 저장해야 한다면? 물론 이걸 다 문자열로 바꿔서 StreamWriter로 쓸 수도 있고, 읽을 때 다시 파싱할 수도 있어. 근데 이 방식엔 단점이 많아:

  • 저장 비효율: 12345라는 숫자를 텍스트로 저장하면 5바이트(문자)야. 근데 바이너리 int는 4바이트만 써. 데이터가 많아질수록 이 차이가 커져.
  • 성능: 숫자를 계속 문자열로 바꾸고 다시 숫자로 바꾸는 건 CPU에 부담이야.
  • 정확도: 실수를 텍스트로 바꿨다 다시 읽으면 반올림 때문에 값이 달라질 수도 있어.
  • 파싱 복잡함: 여러 타입이 섞인 텍스트(예: "123,45 TRUE 2024-06-21")를 직접 분해해서 읽으려면 코드가 복잡해지고, 에러도 잘 나.

이런 문제를 해결하려고 BinaryReaderBinaryWriter 같은 특화된 클래스가 있어. 얘네는 어떤 Stream (보통 FileStream) 위에서 동작하고, C#의 프리미티브 타입을 바이너리로 읽고 쓸 수 있는 편한 메서드를 제공해. 바이트를 타입으로 바꾸거나 반대로 바꾸는 걸 알아서 해주니까, 구조화된 바이너리 파일을 다루는 게 훨씬 쉬워져.

핵심 아이디어: BinaryReaderBinaryWriter는 독립적인 스트림이 아니야. 얘네는 기존 Stream의 기능을 강화해서, 바이트 대신 C# 타입을 다루는 메서드를 추가해주는 거지.

2. BinaryWriter로 데이터 쓰기

BinaryWriter는 C#의 각 프리미티브 타입마다 오버로드된 Write() 메서드들을 제공해. 이 메서드를 호출하면, BinaryWriter가 값을 바이너리(바이트 시퀀스)로 바꿔서, 그 바이트들을 베이스 Stream에 써줘.

예시: 게임 설정 저장하기

예를 들어, 게임 설정을 저장한다고 해보자: 볼륨(실수), 현재 레벨(정수), 음악 켜짐 여부(불린), 선택한 난이도(문자열).


string filePath = "settings.bin";

// 1. 파일 쓰기용 FileStream 만들기
using FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write);
// 2. FileStream 위에 BinaryWriter 만들고, 문자열 인코딩 지정(필요하면)
using BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
// 3. 여러 타입 데이터 쓰기
writer.Write(0.75f);       // float (4바이트)
writer.Write(15);          // int (4바이트)
writer.Write(true);        // bool (1바이트)
writer.Write("Easy");      // string (길이 프리픽스 + 바이트)
                
Console.WriteLine($"설정이 '{filePath}'에 저장됐어.");

예시 설명:

  • FileStreamFileMode.Create로 만들어서, 새 파일을 만들거나 기존 파일을 덮어써.
  • 그 다음 BinaryWriter를 만들고, fs를 넘겨줘. 참고로 BinaryWriterDispose()가 호출되면(즉, using 블록 끝나면) 베이스 스트림(fs)도 같이 닫아줘.
  • writer.Write() 메서드는 직관적이야: Write(float), Write(int), Write(bool), Write(string). 각 타입에 맞게 바이트로 변환해서 써줘.
  • 문자열의 경우, BinaryWriter가 자동으로 길이 프리픽스를 문자열 바이트 앞에 붙여. 그래서 BinaryReader가 문자열을 읽을 때, 몇 바이트를 읽어야 하는지 정확히 알아.
  • settings.bin을 텍스트 에디터로 열면 "이상한 문자"만 보여. 이건 바이너리 파일이라서 그래. 내용을 보려면 HEX 에디터가 필요해.

3. BinaryReader로 데이터 읽기

BinaryReaderReadXxx() (예: ReadInt32(), ReadBoolean(), ReadString()) 같은 메서드를 제공해. 이 메서드들은 베이스 Stream에서 필요한 만큼 바이트를 읽어서, C#의 해당 타입으로 변환해줘.

예시: 게임 설정 불러오기

이제 아까 만든 settings.bin 파일에서 설정을 읽어보자.


string filePath = "settings.bin";

// 1. 파일 읽기용 FileStream 만들기
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
// 2. FileStream 위에 BinaryReader 만들고, 같은 인코딩 사용
using BinaryReader reader = new BinaryReader(fs, Encoding.UTF8);

// 3. 데이터를 "같은 순서"로 읽기 (쓴 순서와 똑같이!)
float volume = reader.ReadSingle();     // float
int level = reader.ReadInt32();         // int
bool isMusicOn = reader.ReadBoolean();  // bool
string difficulty = reader.ReadString(); // string
                
Console.WriteLine($"설정이 '{filePath}'에서 불러와졌어:");
Console.WriteLine($"- 볼륨: {volume:P0}"); // 퍼센트 포맷팅
Console.WriteLine($"- 플레이어 레벨: {level}");
Console.WriteLine($"- 음악 켜짐: {isMusicOn}");
Console.WriteLine($"- 난이도: {difficulty}");

예시 설명:

  • FileStreamFileMode.Open으로 열어서 읽기 모드로 사용해.
  • BinaryReaderfs 위에 만들고, 쓸 때랑 같은 인코딩을 지정해.
  • 진짜 중요: reader.ReadXxx() 메서드 호출 순서는 반드시 BinaryWriter로 쓴 순서랑 같아야 해. 만약 int를 쓴 자리에 string을 읽으려고 하면, EndOfStreamException이 나거나, 이상한 값이 나올 수 있어.
  • ReadXxx() 메서드는 알아서 필요한 만큼 바이트를 읽고, 타입으로 변환해줘. ReadString()BinaryWriter가 쓴 길이 프리픽스를 이용해서, 문자열 전체를 정확히 읽어와.

4. 중요한 팁과 베스트 프랙티스

순서 엄수:

이게 제일 중요해. BinaryReaderBinaryWriter는 타입에 대한 메타데이터를 저장하지 않아. 그냥 각 프리미티브 타입이 몇 바이트인지만 알아. 순서를 꼭 맞춰야 해.

리소스 관리 (using):

.NET에서 시스템 리소스(파일, 네트워크 등)를 다루는 대부분의 클래스처럼, BinaryReaderBinaryWriterIDisposable을 구현해. 그래서 항상 using 블록으로 감싸줘야 해. 그래야 에러가 나도 Dispose()가 자동으로 호출돼서, 리소스 누수 없이 파일이 잘 닫혀.

참고로 BinaryWriterBinaryReader는 기본적으로 넘겨준 베이스 스트림(FileStream 등)도 Dispose()로 같이 닫아줘.


using FileStream fs = new FileStream("data.bin", FileMode.OpenOrCreate);
using BinaryWriter writer = new BinaryWriter(fs);
// ... 작업

문자열 인코딩:

BinaryWriter.Write(string)으로 쓴 문자열을 BinaryReader.ReadString()으로 제대로 읽으려면, 항상 같은 인코딩을 생성자에 지정해야 해(예: Encoding.UTF8). 안 그러면 ASCII에 없는 문자가 깨질 수 있어.

예외 처리:

파일 입출력은 언제든 외부 요인(파일 없음, 권한 없음, 디스크 꽉 참 등)으로 실패할 수 있어. FileStream이나 BinaryReader/BinaryWriter를 쓸 땐 항상 try-catch로 감싸서, 에러에 대비하자.

BaseStream과 위치:

BaseStream 속성을 통해 베이스 스트림에 접근할 수 있어(예: reader.BaseStream이나 writer.BaseStream). 이걸로 현재 위치(BaseStream.Position)를 알거나, 파일 내에서 이동(BaseStream.Seek())할 수 있어.


// BaseStream.Position 사용 예시
using FileStream fs = new FileStream("data.bin", FileMode.OpenOrCreate);
using BinaryWriter writer = new BinaryWriter(fs);

writer.Write(123);
Console.WriteLine($"스트림의 현재 위치: {writer.BaseStream.Position}"); // 4 (int 크기)

writer.Write("Hello");
Console.WriteLine($"스트림의 현재 위치: {writer.BaseStream.Position}"); // 4 + (1+5) = 10

⚠️ Write(string) 메서드는 먼저 문자열 길이를 7비트 정수로 쓰고, 그 다음에 문자열 바이트를 써. 그래서 실제 크기는 1 + 문자열 길이랑 항상 같진 않아.

2
과제
C# SELF, 레벨 36, 레슨 4
잠금
BinaryWriter와 BinaryReader를 사용한 정수 기록 및 읽기
BinaryWriter와 BinaryReader를 사용한 정수 기록 및 읽기
1
설문조사/퀴즈
입출력 스트림, 레벨 36, 레슨 4
사용 불가능
입출력 스트림
파일 읽기와 쓰기
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION