C#에서 디스크 파일 읽고/쓰기

황현중·2025년 12월 1일

C#

목록 보기
18/24
  • File.ReadAllText / WriteAllText 같은 간단 방법
  • FileStream, StreamReader, StreamWriter 같은 스트림 기반 방식
  • BinaryReader, BinaryWriter를 사용하는 이진 파일
  • Seek을 이용한 임의 접근(Random Access)
  • ReadAsync, WriteAsync를 이용한 비동기 파일 I/O
  • Path, Directory 등 경로/디렉터리 관리

1. 가장 쉬운 파일 처리 – File 클래스 (한 번에 읽고/쓰기)

작은 텍스트 파일을 빠르게 읽고 쓸 때는 System.IO.File 클래스의 ReadAllXXX, WriteAllXXX 메서드를 쓰는 게 가장 편하다.

1-1. 텍스트 전체를 한 번에 쓰기 / 읽기

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 : 파일 전체를 한 번에 읽어 문자열로 반환

1-2. 줄 단위로 한 번에 쓰기 / 읽기

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);
        }
    }
}

이런 방식은 파일 크기가 크지 않을 때 “간단하게 읽고/쓰기” 좋다.


2. 스트림 기반 파일 처리 – FileStream + StreamReader/StreamWriter

조금 더 본격적으로 파일을 다루려면 스트림(Stream) 기반으로 접근한다.

  • FileStream : 파일을 바이트 단위로 읽고/쓰는 기본 스트림
  • StreamReader : 텍스트를 편하게 읽기
  • StreamWriter : 텍스트를 편하게 쓰기

2-1. 텍스트 파일 읽기 – StreamReader

using 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 끝나면 자동으로 파일 닫힘
    }
}

2-2. 텍스트 파일 쓰기 – StreamWriter

using 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 자동
    }
}

2-3. 왜 using을 써야 할까?

FileStream은 운영체제의 파일 핸들을 잡고 있다. 파일을 열어만 놓고 닫지 않으면:

  • 다른 프로그램이나 코드에서 파일을 사용하지 못할 수 있고
  • OS 자원이 낭비된다

그래서 using 블록을 사용해서 Dispose()를 자동으로 호출하는 것이 기본 패턴이다.


3. 이진 파일 처리 – BinaryReader / BinaryWriter

텍스트가 아니라 숫자, 바이트, 구조체 같은 데이터를 파일에 저장하고 싶다면 BinaryReader / BinaryWriter를 사용한다.

3-1. 바이너리 쓰기

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
        }
    }
}

3-2. 바이너리 읽기

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}");
        }
    }
}

중요한 포인트는 읽는 순서가 쓰는 순서와 반드시 같아야 한다는 것. 이건 일종의 “파일 포맷 / 프로토콜”을 설계하는 느낌으로 이해하면 된다.


4. 임의 접근(Random Access) – 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 등이 들어간다.

고정 길이 레코드로 저장된 바이너리 파일을 다루거나, 대용량 파일에서 특정 위치만 빠르게 읽고 싶을 때 유용한 방식이다.


5. 비동기 파일 I/O – ReadAsync / WriteAsync / File.*Async

UI가 멈추지 않게 하거나, 서버에서 많은 요청을 처리할 때는 비동기 I/O를 사용하는 게 좋다. C#에서는 async/await와 함께 File.*Async, FileStream.ReadAsync, WriteAsync 등을 사용한다.

5-1. 고수준 비동기 – File.ReadAllTextAsync, WriteAllTextAsync

using 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);
    }
}

5-2. 스트림 기반 비동기 – FileStreamReadAsync / WriteAsync

using 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 응답성서버 처리량이 좋아지는 효과가 있다.


6. 경로/디렉터리 다루기 – Path, Directory, FileInfo

파일 작업을 할 때는 파일만이 아니라 경로, 폴더 관리도 함께 필요하다.

6-1. 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);
    }
}

6-2. 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);
        }
    }
}

6-3. 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);
    }
}

7. 언제 어떤 방식을 쓰면 좋을까?

상황 추천 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

0개의 댓글