
지난번에 데이터가 흐르는 통로, 스트림(Stream)이라는 개념을 알아보았다면
이제 그 통로를 실제 '파일(File)'이라는 목적지에 연결해 볼 시간입니다.
이번 글에서는 C# 파일 입출력의 기본이자 핵심인 FileStream클래스를 다뤄보겠습니다.
C#에서 FileStream은 파일 시스템 내의 파일과 상호작용을 하기 위한 핵심 클래스입니다.
FileStream은 파일에 직접 접근하여 바이트(byte) 단위의 입출력(I/O) 작업을 수행하며,
다른 파일 관련 클래스들의 기반이 되는 저수준(low-level) 도구입니다.
FileStream은 System.IO.Stream추상 클래스를 상속받으며 다음과 같은 특징을 가집니다.
모든 데이터를 바이트의 연속된 흐름으로 처리합니다.
따라서 텍스트를 다룰 때는 문자를 직접 바이트 배열로 변환하거나,
변환된 바이트 배열을 다시 문자로 해석하는 과정이 필요합니다.
FileStream에게 텍스트, 이미지, 음악 파일은 '바이트'의 나열일 뿐입니다.FileStream은 파일을 열 때 어떻게 사용할지(FileAccess) 지정하여
읽기 전용, 쓰기 전용, 또는 읽기/쓰기 모두 가능한 상태로 만들 수 있습니다.
FileAccess.Read로 설정하면 '읽기만 가능한 일방통행'이 되고, FileAccess.Write로 하면 '쓰기만 가능한 일방통행',FileAccess.ReadWrite로 하면 '읽고 쓰기가 가능한 양방향 도로'가 되는 셈이죠.FileStream은 '순차 접근'과 '임의 접근'을 모두 지원하는 강력한 도구입니다.
Read()나 Write()를 호출하면Seek()메소드를 사용하면 원하는 위치로 임의 접근(목적지 검색)도 가능합니다.Position속성으로 알 수 있습니다.버퍼링(Buffering)은 데이터의 처리 과정에서 발생하는 속도 차이를 완화하기 위해
'버퍼(Buffer)'라는 공간에 데이터를 일시적으로 저장하는 과정을 의미합니다.
FileStream에서 제공되는 버퍼(Buffer)의 기본 크기는 4096바이트(4KB)입니다.
FileStream은 사용이 끝나면 반드시 파일을 해제해야(Close()) 합니다.
using문을 사용해서 using블록 안에서 FileStream객체를 생성하면
코드 블록이 끝날 때 자동으로 Dispose()메서드가 호출되어 안전하게 정리해 줍니다.
FileStream을 사용하기 위해서는 몇 가지 정보가 필요합니다.
"어떤 파일을?", "무슨 목적으로 열 건지?", "읽기용?" "쓰기용?" 같은 것들이죠.
FileStream객체를 생성하는 기본적인 방법은 다음과 같습니다.
using System.IO;
// using 키워드는 스트림 사용이 끝나면 자동으로 닫아주는(Dispose) 역할을 합니다.
// 파일 작업 시에는 반드시 사용해야 하는 필수 구문입니다!
using (FileStream fs = new FileStream("경로", FMode.Args, FAccess.Args, FShare.Args))
{
// 파일 작업 수행...
}
여기서 FileMode, FileAccess, Fileshare를 하나씩 살펴볼까요?
파일을 열 때의 시나리오를 정의합니다.
FileMode | 설명 |
|---|---|
CreateNew | 새 파일을 만듭니다. 파일이 이미 있으면 예외 발생! |
Create | 새 파일을 만듭니다. 파일이 이미 있으면 덮어씁니다. |
Open | 기존 파일을 엽니다. 파일이 없으면 예외 발생! |
OpenOrCreate | 기존 파일을 엽니다. 파일이 없으면 새로 만듭니다. |
Append | 기존 파일을 열고, 내용을 맨 끝에 추가합니다. |
Truncate | 기존 파일을 열고, 모든 내용을 지웁니다. |
파일에 대한 읽기/쓰기 권한을 지정합니다. (생략 시 기본값: ReadWrite 또는 Write)
FileAccess | 설명 |
|---|---|
Read | 읽기 전용 |
Write | 쓰기 전용 |
ReadWrite | 읽기 및 쓰기 |
다른 프로세스에서 파일에 액세스하는 방법을 지정합니다. (생략 시 기본값은 Read)
FileShare에 지정하는 값들은 OR 연산자(|)를 사용하여 조합할 수도 있습니다.
여기서 가장 자주 사용되는 값은 다음과 같습니다.
FileShare | 설명 |
|---|---|
None | FileStream이 열려 있는 동안에는 다른 프로세스가 이 파일에 액세스할 수 없음 |
Read | 다른 프로세스가 이 파일을 읽는 것을 허용합니다. |
Write | 다른 프로세스가 이 파일에 쓰는 것을 허용합니다. |
ReadWrite | 다른 프로세스가 이 파일을 읽고 쓰는 것을 모두 허용합니다. |
Delete | 다른 프로세스가 이 파일을 삭제하는 것을 허용합니다. |
FileStream은 데이터를 byte배열, 즉 바이트 덩어리로 다룹니다.
우리가 흔히 쓰는 문자열("Hello World")을 파일에 쓰려면,
먼저 이 문자열을 바이트 배열로 변환하는 과정이 필요합니다.
[코드]
using System;
using System.IO;
using System.Text; // 문자 인코딩을 위해 필요합니다.
string filePath = @"C:\Temp\MyTest.txt";
string content = "Hello, FileStream!";
Directory.CreateDirectory(@"C:\Temp");
try
{
// 1. 파일을 생성(또는 덮어쓰기)하고 쓰기 권한으로 FileStream 열기
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
// 2. 문자열을 UTF-8 인코딩의 byte 배열로 변환 (UTF-8은 전 세계적인 표준)
byte[] data = Encoding.UTF8.GetBytes(content);
// 3. byte 배열을 파일에 쓰기
fs.Write(data, 0, data.Length);
}
Console.WriteLine("파일 쓰기 완료!");
}
catch (Exception ex)
{
Console.WriteLine($"오류 발생: {ex.Message}");
}
Encoding.UTF8.GetBytes(문자열): 문자열을 byte배열로 변환해 줍니다.fs.Write(배열, 시작 위치, 길이): 변환된 byte배열을 파일에 기록합니다.파일에서 byte배열을 읽고 우리가 읽을 수 있는 문자열로 변환해 주면 됩니다.
[코드]
// 위에서 만든 파일을 읽어봅시다.
if (File.Exists(filePath))
{
try
{
// 1. 파일을 열고 읽기 권한으로 FileStream 열기
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
// 2. 파일 크기만큼의 byte 배열(버퍼) 생성
byte[] buffer = new byte[fs.Length];
// 3. 파일에서 데이터를 읽어와서 buffer에 채우기
fs.Read(buffer, 0, buffer.Length);
// 4. byte 배열을 UTF-8 인코딩으로 다시 문자열로 변환
string readContent = Encoding.UTF8.GetString(buffer);
Console.WriteLine("파일에서 읽은 내용:");
Console.WriteLine(readContent);
}
}
catch (Exception ex)
{
Console.WriteLine($"오류 발생: {ex.Message}");
}
}
new byte[fs.Length]: 파일의 전체 크기(Length)만큼 데이터를 담을 버퍼를 준비합니다.fs.Read(버퍼, 시작 위치, 길이): 파일에서 데이터를 읽고 buffer배열을 채웁니다.Encoding.UTF8.GetString(배열): byte배열을 사람이 읽기 좋게 문자열로 변환합니다.[실행 결과]
파일 쓰기 완료!
파일에서 읽은 내용:
Hello, FileStream!
파일 입출력의 기초라고 할 수 있는 FileStream에 대해 알아봤습니다.
| 구분 | 핵심 개념 | 설명 |
|---|---|---|
| 생성 | new FileStream(path, mode, access) | 파일 경로, 모드, 접근 권한을 지정하여 스트림 통로를 엽니다. |
| 필수 | using 구문 | FileStream에서 사용이 끝나면 자동으로 리소스를 해제합니다. |
| 쓰기 | Encoding.GetBytes() ⮕ fs.Write() | 문자열을 byte 배열로 변환한 뒤 파일에 씁니다. |
| 읽기 | fs.Read() ⮕ Encoding.GetString() | 파일에서 byte 배열을 읽어온 뒤 문자열로 변환합니다. |