CodeGym /행동 /C# SELF /FileStream 클래스

FileStream 클래스

C# SELF
레벨 35, 레슨 3
사용 가능

1. FileStream 클래스: 조각조각 작업하기

FileStream은 마치 네 파일에 직접 연결된 수도관 같은 거야. 이 관을 통해서 데이터를 컨트롤하면서 보낼 수 있어: 파일에 바이트를 보내거나(쓰기), 파일에서 바이트를 받아올 수 있지(읽기). 더 고수준 메서드들은 그냥 "물 한 컵"을 주는 거라면, FileStream은 네가 직접 "수도꼭지"를 조절해서 물(바이트) 흐름을 원하는 대로 조절할 수 있게 해줘.

FileStream은 바이트 단위로 동작해. 이 말은, 그게 텍스트든 이미지든 신경 안 써; 그냥 모든 게 바이트 시퀀스일 뿐이야. 만약 FileStream으로 텍스트 파일을 다루고 있다면, 직접 문자열을 바이트로 변환(예를 들어 인코딩, UTF-8 같은 거)해서 써야 하고, 읽을 때도 다시 바이트를 문자열로 바꿔야 해.

언제 FileStream이 꼭 필요할까?

  • 엄청 큰 파일 다룰 때: 파일이 너무 커서 한 번에 메모리에 올릴 수 없거나(예: 기가바이트 로그, 동영상 파일), 그럴 필요가 없을 때. FileStream은 데이터를 조각(청크) 단위로 읽고 쓸 수 있어서 메모리 효율이 좋아.
  • 바이너리 데이터: 그냥 텍스트가 아닌 파일(이미지, 오디오, 비디오, 직렬화된 객체, DB 파일 등) 다룰 때 FileStream이 필수야. 바이트에 직접 접근할 수 있으니까.
  • 접근 모드 세밀하게 컨트롤할 때: 파일을 읽기*랑*쓰기 동시에 열고 싶다거나, 다른 프로그램이 접근 못 하게 하고 싶다거나, 반대로 여러 프로세스가 동시에 읽게 하고 싶을 때. FileStream이 이런 모드 전부 컨트롤해줘.
  • 비동기 작업: 요즘 고성능 앱(예: 웹서버)에서는 파일 작업이 메인 스레드를 막으면 안 돼. FileStream은 비동기 메서드(ReadAsync, WriteAsync)도 지원해서 프로그램이 계속 반응할 수 있어.
  • 부분 읽기/쓰기 또는 랜덤 액세스: 파일의 특정 위치(예: 500번째 바이트)에서 읽거나, 파일 중간에 데이터를 써야 할 때, FileStream으로 파일 포인터 위치를 직접 조절할 수 있어.

초보자들이 자주 하는 실수: FileStream을 그냥 간단히 한 줄 텍스트를 작은 파일에 쓸 때 쓰는 거. 이런 경우엔 오버킬이야. FileStream은 좀 더 복잡하고 특별한 상황에서 쓰는 도구야.

2. FileStream 만들기와 열기

파일을 FileStream으로 다루려면, 먼저 인스턴스를 만들어야 해. 이건 생성자를 쓰는데, 파일을 어떻게 다룰지 정하는 중요한 파라미터들을 넣어줘야 해.

using System;
using System.IO;   // FileStream 쓸 때 꼭 필요
using System.Text; // 인코딩(문자열 <-> 바이트 변환) 쓸 때 필요

FileStream으로 텍스트 파일 기본 읽기 예제:

먼저 읽을 파일을 하나 만들어보자.

// 데모용으로 간단한 텍스트 파일 하나 만들기
string path = "example_filestream.txt";

// 읽기용 스트림 열기
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

// 읽은 데이터를 임시로 저장할 버퍼(바이트 배열) 만들기
byte[] buffer = new byte[fs.Length];

// 스트림에서 버퍼로 바이트 읽기
int bytesRead = fs.Read(buffer, 0, buffer.Length);

// 읽은 바이트를 다시 문자열로 변환(예: UTF-8 인코딩 사용)
string content = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("FileStream으로 파일에서 읽은 내용: " + content);

fs.Close(); //스트림 닫기

주요 파라미터

  • FileMode: 파일을 어떻게 열지 (Open, Create, Append OpenOrCreate 등)
  • FileAccess: 파일로 뭘 할지 (Read, Write, ReadWrite)
  • FileShare: 다른 프로세스가 동시에 이 파일을 쓸 수 있는지(보통 간단한 작업엔 필요 없음)

자주 하는 실수: FileStream을 닫는 걸 까먹으면, 파일이 "잠겨서" 나중에 삭제나 재오픈이 안 될 수 있어!

3. FileStream 생성자 파라미터

FileStream 생성자는 파일을 어떻게 열지 아주 세밀하게 설정할 수 있어. 주요 파라미터는 이래:

new FileStream(
    string path,           // 파일 경로(절대/상대)
    FileMode mode,         // 파일을 어떻게 열지(생성, 열기, 덮어쓰기 등)
    FileAccess access,     // 어떤 접근 권한(읽기만, 쓰기만, 읽기+쓰기)
    FileShare share        // 파일이 열려있는 동안 다른 프로세스가 어떻게 접근할 수 있는지(옵션)
);

FileMode랑 FileAccess 열거형 값들을 살펴보자:

FileMode (파일 열기 모드):

파일을 열 때 OS가 어떻게 처리할지 정해.

  • FileMode.Open: 이미 있는 파일을 연다. 경로에 파일이 없으면 FileNotFoundException이 나와.
  • FileMode.Create: 새 파일을 만든다. 같은 이름의 파일이 이미 있으면 완전히 덮어써서 내용이 다 사라져.

FileAccess (파일 접근 권한):

열린 파일로 어떤 작업이 가능한지 정해.

  • FileAccess.Read: 파일을 읽기만 가능. 쓰기는 못 해.
  • FileAccess.Write: 파일을 쓰기만 가능. 읽기는 못 해.
  • FileAccess.ReadWrite: 읽기랑 쓰기 둘 다 가능. 이 모드는 유연하지만, 스트림 위치 관리도 네가 직접 해야 해서 좀 더 신경 써야 해.

FileShare (공유 모드):

네 프로그램이 파일을 열고 있는 동안, 다른 프로세스가 그 파일을 어떻게 열 수 있는지 정해. 락 문제 방지에 중요해.

  • FileShare.Read: 다른 프로세스가 읽기만 가능, FileStream이 열려있는 동안 쓰기는 못 해.
  • FileShare.Write: 다른 프로세스가 쓰기만 가능, 읽기는 못 해.
  • FileShare.ReadWrite: 다른 프로세스가 읽기랑 쓰기 둘 다 가능. 이건 제일 자유로운 모드지만, 여러 프로그램이 동시에 파일을 바꾸면 충돌 날 수 있어.

모드별 자세한 내용은 다음 강의에서 다룰게.

4. FileStream으로 데이터 읽고 쓰기

FileStream을 쓸 때는 바이트 단위로 조작해. 그래서 텍스트 데이터를 다룰 땐 문자열과 바이트 배열 사이 변환을 직접 해줘야 해. (인코딩 클래스, 예: System.Text.Encoding.UTF8 사용)

FileStream으로 파일에 데이터 쓰기

쓰기할 때는 데이터를(예: 문자열) 바이트 배열로 변환해서 스트림에 써.

string outputPath = "user_data.txt";
string userName = "이반 페트로프";

// 문자열을 UTF-8 인코딩으로 바이트 배열로 변환
byte[] userNameBytes = Encoding.UTF8.GetBytes(userName);

// FileStream을 쓰기용으로 열기
FileStream fsWrite = new FileStream(outputPath, FileMode.Create, FileAccess.Write);

// 바이트 배열을 스트림에 쓰기
fsWrite.Write(userNameBytes, 0, userNameBytes.Length);
Console.WriteLine($"이름 '{userName}'이(가) 파일 '{outputPath}'에 성공적으로 저장됨.");

fsWrite.Close(); //스트림 닫기

FileStream으로 파일에서 데이터 읽기

읽기할 때는 스트림에서 바이트를 버퍼로 읽고, 그걸 다시 원하는 형식(예: 문자열)로 변환해.

string inputPath = "user_data.txt";

// 읽기용 FileStream 열기
FileStream fsRead = new FileStream(inputPath, FileMode.Open, FileAccess.Read);

// 파일 크기만큼 버퍼 만들기
byte[] buffer = new byte[fsRead.Length];

// 스트림에서 버퍼로 바이트 읽기
int bytesRead = fsRead.Read(buffer, 0, buffer.Length);

// 읽은 바이트를 문자열로 변환
string loadedUserName = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"FileStream으로 파일에서 읽은 이름: {loadedUserName}");

fsRead.Close(); //스트림 닫기

5. 큰 파일 다루기

FileStream의 가장 큰 장점은 File.ReadAll... 메서드랑 달리, 조각조각 읽고 쓸 수 있다는 거야. 이건 파일이 너무 커서 한 번에 메모리에 못 올릴 때 필수야.

큰 파일을 조각조각 읽기

파일을 조각조각 읽으려면, 고정 크기 버퍼(바이트 배열)를 만들어서, 파일 끝까지 계속 읽어오면 돼.

string bigFilePath = "bigfile.bin";     //큰 파일
int bufferSize = 4096;                  // 버퍼 크기, 예: 4KB
byte[] buffer = new byte[bufferSize];   // 버퍼 만들기
int bytesRead;                          // 실제로 읽은 바이트 수
long totalBytesRead = 0;                // 전체 읽은 바이트 수

// 스트림 열기
FileStream fs = new FileStream(bigFilePath, FileMode.Open, FileAccess.Read);
int chunkNumber = 1;

// fs.Read()가 양수(읽은 바이트 수) 반환하는 동안 반복
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
    totalBytesRead += bytesRead;
    Console.WriteLine($"조각 {chunkNumber++}: {bytesRead} 바이트 읽음. 전체: {totalBytesRead} 바이트.");

    // !!! 여기서 읽은 "조각" 데이터를 처리하면 됨 !!!
   
}

fs.Close(); //스트림 닫기

중요한 팁: fs.Read(buffer, offset, count)는 항상 buffer를 다 채운다는 보장이 없어. 실제로 읽은 바이트 수를 반환하는데, 이게 count보다 작을 수도 있어(특히 파일 끝에서). 항상 bytesRead 값을 써서 데이터 처리해야 해.

큰 데이터 조각조각 쓰기

읽기랑 마찬가지로, 큰 데이터를 파일에 조각조각 쓸 수도 있어.

string bigOutputPath = "big_output.txt";
string longText = new string('A', 1_000_000); // 'A' 백만 개로 된 문자열
byte[] longTextBytes = Encoding.UTF8.GetBytes(longText);
int writeBufferSize = 1024; // 한 번에 1KB씩 쓰기

// FileMode.Create: 파일 생성
FileStream fsWrite = new FileStream(bigOutputPath, FileMode.Create, FileAccess.Write);

for (int i = 0; i < longTextBytes.Length; i += writeBufferSize)
{
    // 이번에 쓸 바이트 수 계산
    int bytesToWrite = Math.Min(writeBufferSize, longTextBytes.Length - i);

    // longTextBytes에서 'i' 오프셋부터 bytesToWrite만큼 쓰기
    fsWrite.Write(longTextBytes, i, bytesToWrite);
    Console.WriteLine($"{bytesToWrite} 바이트 저장됨");
}

fsWrite.Close(); //스트림 닫기

6. 잘 안 보이는 함정과 흔한 실수들

FileStream처럼 강력한 도구도, 몇 가지 주의할 점이 있어:

버퍼링과 Flush(): 이미 말했듯이, 데이터가 메모리 버퍼에 남아있고 디스크에 안 써질 수도 있어. using을 안 쓰거나 Close()/Dispose()를 안 부르고 그냥 프로그램 끝내면, 데이터가 날아갈 수 있음. 항상 Flush()를 부르거나(using/Close()에 맡기거나) 해서 데이터가 확실히 저장되게 해.

예외 처리: 입출력 에러(예: IOException - 디스크 꽉 찼을 때, UnauthorizedAccessException - 권한 없을 때, FileNotFoundException - Open 모드로 없는 파일 열 때 등)는 흔해. FileStream 작업은 항상 try-catch로 감싸자.

인코딩: FileStream으로 텍스트 다룰 때(바이트 단위로 동작하니까), 인코딩을 네가 직접 잘 선택해야 해 (Encoding.UTF8, Encoding.ASCII, Encoding.Unicode 등). 잘못 고르면 "깨진 글자"가 나올 수 있어.

포지션 관리: Seek() 쓸 때는 offset이랑 origin 파라미터를 잘못 주면 파일 범위 밖이나 엉뚱한 위치로 갈 수 있으니 조심!

파일 락(FileShare): FileShare.None으로 파일을 열면, 다른 프로세스가 접근 못 해. 만약 다른 프로그램(혹은 네 프로그램의 다른 스레드)이 같은 파일을 쓰려고 하면 문제 생길 수 있어. 항상 상황에 맞는 FileShare 모드를 골라야 해.

2
과제
C# SELF, 레벨 35, 레슨 3
잠금
FileStream을 사용한 텍스트 파일 읽기
FileStream을 사용한 텍스트 파일 읽기
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION