File.ReadAllText / WriteAllText 같은 간단 방법FileStream, StreamReader, StreamWriter 같은 스트림 기반 방식BinaryReader, BinaryWriter를 사용하는 이진 파일Seek을 이용한 임의 접근(Random Access)ReadAsync, WriteAsync를 이용한 비동기 파일 I/OPath, Directory 등 경로/디렉터리 관리File 클래스 (한 번에 읽고/쓰기)
작은 텍스트 파일을 빠르게 읽고 쓸 때는 System.IO.File 클래스의
ReadAllXXX, WriteAllXXX 메서드를 쓰는 게 가장 편하다.
using System;
using System.IO;
class Program
{
static void Main()
{
string path = "test.txt";
// 쓰기
File.WriteAllText(path, "첫 줄\n두 번째 줄");
// 읽기
string text = File.ReadAllText(path);
Console.WriteLine(text);
}
}
WriteAllText : 문자열 전체를 파일에 한 번에 기록ReadAllText : 파일 전체를 한 번에 읽어 문자열로 반환using System;
using System.IO;
class Program
{
static void Main()
{
string[] lines = { "Line1", "Line2", "Line3" };
File.WriteAllLines("lines.txt", lines);
string[] readLines = File.ReadAllLines("lines.txt");
foreach (var line in readLines)
{
Console.WriteLine(line);
}
}
}
이런 방식은 파일 크기가 크지 않을 때 “간단하게 읽고/쓰기” 좋다.
FileStream + StreamReader/StreamWriter조금 더 본격적으로 파일을 다루려면 스트림(Stream) 기반으로 접근한다.
FileStream : 파일을 바이트 단위로 읽고/쓰는 기본 스트림StreamReader : 텍스트를 편하게 읽기StreamWriter : 텍스트를 편하게 쓰기StreamReaderusing System;
using System.IO;
class Program
{
static void Main()
{
using (FileStream fs = new FileStream("test.txt", FileMode.Open, FileAccess.Read))
using (StreamReader reader = new StreamReader(fs)) // 기본 인코딩
{
string all = reader.ReadToEnd(); // 파일 전체 읽기
Console.WriteLine(all);
} // using 끝나면 자동으로 파일 닫힘
}
}
StreamWriterusing System;
using System.IO;
class Program
{
static void Main()
{
using (FileStream fs = new FileStream("test.txt", FileMode.Create, FileAccess.Write))
using (StreamWriter writer = new StreamWriter(fs))
{
writer.WriteLine("첫 줄");
writer.WriteLine("둘째 줄");
} // Flush + Close 자동
}
}
using을 써야 할까?
FileStream은 운영체제의 파일 핸들을 잡고 있다.
파일을 열어만 놓고 닫지 않으면:
그래서 using 블록을 사용해서 Dispose()를 자동으로 호출하는 것이 기본 패턴이다.
BinaryReader / BinaryWriter
텍스트가 아니라 숫자, 바이트, 구조체 같은 데이터를 파일에 저장하고 싶다면
BinaryReader / BinaryWriter를 사용한다.
using System;
using System.IO;
class Program
{
static void Main()
{
using (FileStream fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write))
using (BinaryWriter bw = new BinaryWriter(fs))
{
bw.Write(123); // int
bw.Write(3.14f); // float
bw.Write(true); // bool
bw.Write("Hello"); // string
}
}
}
using System;
using System.IO;
class Program
{
static void Main()
{
using (FileStream fs = new FileStream("data.bin", FileMode.Open, FileAccess.Read))
using (BinaryReader br = new BinaryReader(fs))
{
int i = br.ReadInt32();
float f = br.ReadSingle();
bool b = br.ReadBoolean();
string s = br.ReadString();
Console.WriteLine($"{i}, {f}, {b}, {s}");
}
}
}
중요한 포인트는 읽는 순서가 쓰는 순서와 반드시 같아야 한다는 것. 이건 일종의 “파일 포맷 / 프로토콜”을 설계하는 느낌으로 이해하면 된다.
FileStream.Seek
파일을 처음부터 쭉 읽는 게 아니라, 파일의 중간으로 점프해서 읽거나 쓰고 싶을 때가 있다.
이때 FileStream.Seek를 사용한다.
using System;
using System.IO;
class Program
{
static void Main()
{
using (FileStream fs = new FileStream("data.bin", FileMode.Open, FileAccess.Read))
using (BinaryReader br = new BinaryReader(fs))
{
// 파일의 8바이트 지점으로 이동 (0 기반 오프셋)
fs.Seek(8, SeekOrigin.Begin);
int value = br.ReadInt32(); // 8바이트 이후의 int 값
Console.WriteLine(value);
}
}
}
Seek(offset, origin)origin에는 SeekOrigin.Begin, SeekOrigin.Current, SeekOrigin.End 등이 들어간다.고정 길이 레코드로 저장된 바이너리 파일을 다루거나, 대용량 파일에서 특정 위치만 빠르게 읽고 싶을 때 유용한 방식이다.
ReadAsync / WriteAsync / File.*Async
UI가 멈추지 않게 하거나, 서버에서 많은 요청을 처리할 때는
비동기 I/O를 사용하는 게 좋다.
C#에서는 async/await와 함께 File.*Async, FileStream.ReadAsync, WriteAsync 등을 사용한다.
File.ReadAllTextAsync, WriteAllTextAsyncusing System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string path = "test.txt";
await File.WriteAllTextAsync(path, "Hello Async File!");
string text = await File.ReadAllTextAsync(path);
Console.WriteLine(text);
}
}
FileStream의 ReadAsync / WriteAsyncusing System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
byte[] buffer = Encoding.UTF8.GetBytes("Async Stream Example");
// 쓰기
using (FileStream fs = new FileStream(
"async.bin",
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096,
useAsync: true)) // 비동기 사용 설정
{
await fs.WriteAsync(buffer, 0, buffer.Length);
}
// 읽기
using (FileStream fs = new FileStream(
"async.bin",
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 4096,
useAsync: true))
{
byte[] readBuffer = new byte[buffer.Length];
int bytesRead = await fs.ReadAsync(readBuffer, 0, readBuffer.Length);
string text = Encoding.UTF8.GetString(readBuffer, 0, bytesRead);
Console.WriteLine(text);
}
}
}
비동기 파일 I/O를 사용하면, I/O를 기다리는 동안 스레드를 블로킹하지 않아서 UI 응답성과 서버 처리량이 좋아지는 효과가 있다.
Path, Directory, FileInfo파일 작업을 할 때는 파일만이 아니라 경로, 폴더 관리도 함께 필요하다.
Path – 문자열 경로 다루기using System;
using System.IO;
class Program
{
static void Main()
{
string folder = @"C:\Temp";
string fileName = "test.txt";
string fullPath = Path.Combine(folder, fileName); // C:\Temp\test.txt
string dir = Path.GetDirectoryName(fullPath); // C:\Temp
string name = Path.GetFileName(fullPath); // test.txt
string ext = Path.GetExtension(fullPath); // .txt
Console.WriteLine(fullPath);
Console.WriteLine(dir);
Console.WriteLine(name);
Console.WriteLine(ext);
}
}
Directory – 폴더 생성 / 파일 목록using System;
using System.IO;
class Program
{
static void Main()
{
// 폴더 생성
Directory.CreateDirectory(@"C:\Temp\Logs");
// 특정 폴더의 .txt 파일 목록
string[] files = Directory.GetFiles(@"C:\Temp", "*.txt");
foreach (var f in files)
{
Console.WriteLine(f);
}
}
}
FileInfo / DirectoryInfo – 객체 지향 버전using System;
using System.IO;
class Program
{
static void Main()
{
var info = new FileInfo("test.txt");
Console.WriteLine(info.FullName);
Console.WriteLine(info.Length);
Console.WriteLine(info.LastWriteTime);
}
}
| 상황 | 추천 API |
|---|---|
| 작은 텍스트 파일을 한 번에 읽고/쓰기 | File.ReadAllText, File.WriteAllText |
| 줄 단위로 간단하게 읽고/쓰기 | File.ReadAllLines, File.WriteAllLines |
| 큰 파일을 조금씩 읽거나, 반복해서 처리 | FileStream + StreamReader/StreamWriter |
| 숫자/바이너리 구조 저장/복원 | FileStream + BinaryReader/BinaryWriter |
| 파일 중간으로 점프해서 읽기/쓰기 | FileStream.Seek |
| UI 멈추지 않게 비동기로 파일 처리 | File.*Async, FileStream.ReadAsync/WriteAsync |
| 경로 조합, 확장자, 폴더 관리 | Path, Directory, FileInfo, DirectoryInfo |