C#에서 파일 읽고 쓰기 (StreamReader, StreamWriter 활용)

제공

안녕하세요! 오늘은 C#에서 파일을 다루는 기본적인 방법에 대해 함께 알아보려고 해요. 프로그래밍을 하다 보면 파일을 읽고 쓰는 작업은 정말 흔하게 만날 수 있죠. 마치 숨 쉬는 것처럼요! C#에서는 `StreamReader``StreamWriter`를 사용해서 아주 쉽고 효율적으로 파일을 다룰 수 있답니다. 이 두 가지 기능을 제대로 활용하면 파일 복사도 간단하게 할 수 있어요. 오늘 우리는 `StreamReader` 기본 사용법부터 `StreamWriter`를 이용한 파일 쓰기, 그리고 파일 복사까지 차근차근 살펴볼 거예요. 혹시 파일 처리 중 발생할 수 있는 예외 처리와 파일 닫는 방법까지 꼼꼼하게 알려드릴 테니 걱정 마세요! 자, 그럼 이제 신나는 파일 탐험을 시작해 볼까요?

 

 

StreamReader 기본 사용법

자, 이제 C#에서 파일을 읽어들이는 StreamReader에 대해 본격적으로 파헤쳐 볼까요? 마치 보물 상자를 여는 열쇠처럼, StreamReader는 텍스트 파일의 내용을 우리에게 보여주는 강력한 도구랍니다! 파일을 한 줄씩, 또는 문자 단위로 읽어 들일 수 있어서 정말 편리해요. 마치 숙련된 낚시꾼처럼 원하는 정보만 쏙쏙 낚아챌 수 있죠!

StreamReader의 네임스페이스

StreamReader는 System.IO 네임스페이스에 속해 있어요. 이 네임스페이스는 파일 입출력에 관련된 다양한 클래스를 제공하는데, 그중에서도 StreamReader는 텍스트 파일 읽기에 특화되어 있답니다. 마치 전문 요리사가 특정 요리를 위해 특별한 칼을 사용하는 것처럼 말이죠!

StreamReader 객체 생성

StreamReader 객체를 생성하는 방법은 여러 가지가 있어요. 파일 경로를 직접 지정하는 방법도 있고, 이미 열려 있는 FileStream 객체를 사용하는 방법도 있죠. 각각의 상황에 맞는 방법을 선택하는 것이 중요해요. 마치 등산할 때 지형에 따라 다른 등산화를 신는 것과 같답니다!

가장 기본적인 생성 방법은 파일 경로를 직접 사용하는 거예요. 예를 들어 “myFile.txt”라는 파일을 읽고 싶다면, StreamReader reader = new StreamReader("myFile.txt"); 와 같이 간단하게 객체를 생성할 수 있어요. 참 쉽죠?

파일 경로에는 절대 경로와 상대 경로 모두 사용 가능해요. 절대 경로는 “C:\Documents\myFile.txt”처럼 파일의 전체 경로를 명시하는 것이고, 상대 경로는 현재 프로그램이 실행되는 위치를 기준으로 경로를 지정하는 거예요. 예를 들어 현재 디렉토리에 있는 파일을 읽으려면 파일 이름만 적어주면 된답니다. 상황에 맞게 적절한 경로를 사용하는 것이 중요해요!

ReadLine() 메서드를 사용한 파일 읽기

자, 이제 StreamReader 객체를 생성했으니, 파일 내용을 읽어 볼까요? ReadLine() 메서드를 사용하면 파일을 한 줄씩 읽어올 수 있어요. 마치 책을 한 페이지씩 넘기듯이 말이죠! 파일의 끝에 도달하면 ReadLine() 메서드는 null을 반환해요. 이를 이용해서 파일의 모든 내용을 읽어 들일 수 있답니다.

StreamReader reader = new StreamReader("myFile.txt");
string line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine(line); // 읽어 들인 한 줄을 출력합니다.
}

위 코드는 “myFile.txt” 파일의 내용을 한 줄씩 읽어서 콘솔에 출력하는 예제예요. while 루프를 사용해서 reader.ReadLine()null을 반환할 때까지 반복해서 파일 내용을 읽어 들이고 있죠.

ReadToEnd() 메서드를 사용한 파일 읽기

ReadToEnd() 메서드를 사용하면 파일의 전체 내용을 한 번에 문자열로 읽어올 수도 있어요. 마치 낚싯대를 던져 한 번에 물고기를 낚는 것처럼 말이죠! 하지만 파일 크기가 매우 큰 경우에는 메모리 부족 문제가 발생할 수 있으니 주의해야 해요!

StreamReader reader = new StreamReader("myFile.txt");
string content = reader.ReadToEnd();
Console.WriteLine(content); // 파일 전체 내용을 출력합니다.

이처럼 ReadToEnd() 메서드는 간편하게 파일 전체 내용을 읽어올 수 있지만, 큰 파일을 다룰 때는 신중하게 사용해야 해요.

StreamReader 닫기

StreamReader를 사용할 때는 항상 파일을 닫아주는 것을 잊지 마세요! 파일을 열어둔 채로 프로그램을 종료하면 파일이 손상될 수도 있답니다. Close() 메서드를 사용해서 파일을 닫아주면 안전하게 파일을 사용할 수 있어요. 마치 사용한 칼을 깨끗하게 닦아서 보관하는 것처럼 말이죠!

reader.Close();

또는 using 문을 사용하면 파일을 자동으로 닫아주기 때문에 더욱 편리하고 안전하게 파일을 관리할 수 있어요. using 문을 사용하면 Close() 메서드를 명시적으로 호출하지 않아도 블록이 끝날 때 자동으로 파일이 닫힌답니다. 정말 편리하죠?

using (StreamReader reader = new StreamReader("myFile.txt"))
{
    string line;
    while ((line = reader.ReadLine()) != null)
    {
        Console.WriteLine(line); // 읽어 들인 한 줄을 출력합니다.
    }
} // using 블록이 끝나면 reader가 자동으로 닫힙니다.

이처럼 StreamReader는 다양한 기능을 제공해서 텍스트 파일을 효율적으로 읽을 수 있게 도와준답니다!

 

StreamWriter 기본 사용법

자, 이제 C#에서 파일을 쓰는 방법을 알아볼 시간이에요! StreamReader로 파일을 읽는 재미를 봤다면, 이번에는 StreamWriter로 내 마음대로 파일 내용을 휘리릭~ 작성해 보는 거죠! 마치 디지털 세상에 나만의 깃발을 꽂는 것처럼 말이에요! 😄

StreamWriter는 말 그대로 스트림에 데이터를 쓰는 역할을 해요. 텍스트 파일, CSV 파일 등 다양한 파일 형식에 적용할 수 있답니다. StreamWriter는 System.IO 네임스페이스에 포함되어 있고, 이 네임스페이스는 .NET Framework의 핵심 구성 요소 중 하나죠. 닷넷 개발을 한다면, System.IO는 거의 매일같이 마주치게 될 거예요. 마치 아침에 눈 뜨면 마시는 커피처럼요! ☕

StreamWriter 객체 생성

StreamWriter 객체를 생성하는 방법은 여러 가지가 있어요. 파일 경로를 직접 지정하는 방식이 가장 일반적이죠. 예를 들어 “my_file.txt”라는 파일에 쓰고 싶다면, new StreamWriter("my_file.txt")처럼 간단하게 객체를 만들 수 있어요. 참 쉽죠?

하지만! 여기서 중요한 포인트! 만약 “my_file.txt” 파일이 이미 존재한다면? 기존 내용은 싹~ 사라지고 새 내용으로 덮어씌워진답니다. 마치 포맷하는 것과 같아요! 😱 조심, 또 조심! 기존 내용을 유지하면서 추가하고 싶다면 new StreamWriter("my_file.txt", true)처럼 두 번째 인자로 true를 넣어주면 돼요. 이렇게 하면 파일의 끝에서부터 이어서 쓸 수 있답니다. 이 작은 true 하나가 얼마나 큰 차이를 만드는지! 꼭 기억해 두세요~! 😉

텍스트 쓰기

자, 그럼 이제 실제로 StreamWriter를 사용해서 파일에 텍스트를 써 볼까요? StreamWriter 객체의 WriteLine() 메서드를 사용하면 한 줄씩 텍스트를 쓸 수 있어요. 예를 들어 “Hello, World!”를 파일에 쓰고 싶다면, myStreamWriter.WriteLine("Hello, World!");처럼 하면 되죠. 마치 마법의 주문처럼요! ✨

WriteLine() 외에도 Write() 메서드도 있어요. Write() 메서드는 줄 바꿈 없이 텍스트를 씁니다. WriteLine()은 개행 문자(\r\n)를 자동으로 추가해주지만, Write()는 그렇지 않아요. 이 차이점, 은근히 중요하니까 꼭 알아두세요! 🤔

다른 데이터 타입 쓰기

StreamWriter는 텍스트뿐만 아니라 다른 데이터 타입도 쓸 수 있다는 사실, 알고 계셨나요? 정수, 실수, 불리언 값 등 다양한 데이터 타입을 문자열로 변환해서 쓸 수 있어요. 예를 들어 myStreamWriter.WriteLine(123);처럼 정수 123을 파일에 쓸 수 있죠. 정말 다재다능한 친구죠? 👍

스트림 닫기

StreamWriter를 사용할 때 꼭! 잊지 말아야 할 것이 있어요. 바로 Close() 메서드를 호출해서 스트림을 닫아주는 거예요. Close() 메서드를 호출하지 않으면, 파일이 제대로 저장되지 않거나 시스템 리소스가 낭비될 수 있답니다. 마치 수도꼭지를 잠그지 않는 것과 같아요! 💧 그러니 꼭 Close() 메서드를 호출해서 깔끔하게 마무리해 주는 센스! ✨ 습관처럼 using 문을 사용하는 것도 좋은 방법이에요. using 문을 사용하면 블록이 끝날 때 자동으로 Close() 메서드가 호출되거든요. 편리하고 안전하게 StreamWriter를 사용할 수 있답니다!

파일 수정

StreamWriter를 사용하면 파일을 만드는 것뿐만 아니라 기존 파일을 수정할 수도 있어요. 파일의 특정 위치에 내용을 삽입하거나 삭제하는 등 다양한 작업이 가능하죠. 마치 텍스트 편집기처럼 말이에요! ✏️ 다음에는 StreamReader와 StreamWriter를 함께 사용해서 파일을 복사하는 방법을 알아볼 거예요! 기대해 주세요! 😉

StreamWriter 성능 최적화

더 나아가 StreamWriter 성능을 최적화하는 꿀팁! 대용량 파일을 다룰 때는 StreamWriter의 버퍼 크기를 조절하여 성능 향상을 꾀할 수 있어요. new StreamWriter("file.txt", false, Encoding.UTF8, 65536) 과 같이 생성자에 버퍼 크기를 명시적으로 지정할 수 있답니다. 일반적으로 4KB(4096)에서 64KB(65536) 사이의 값이 적절하며, 파일 크기와 시스템 환경에 따라 최적의 값은 달라질 수 있으니 테스트를 통해 직접 확인해 보는 것이 가장 좋아요! 버퍼 크기를 조절하면 I/O 작업 횟수를 줄여 처리 속도를 높일 수 있답니다. 🚀

또한, 파일 쓰기 작업이 끝나면 Flush() 메서드를 호출하여 버퍼에 남아있는 데이터를 강제로 파일에 쓰도록 할 수 있어요. 이는 데이터 손실을 방지하는 데 도움이 되죠. 특히 예외 발생 시 데이터가 유실되는 것을 막아주는 안전장치 역할을 한답니다. 🛡️

StreamWriter는 정말 강력하고 유용한 도구예요! 다양한 옵션과 메서드를 활용하여 파일을 자유자재로 다뤄보세요! 💪

 

StreamReader와 StreamWriter를 활용한 파일 복사

자, 이제 본격적으로 StreamReaderStreamWriter 쌍을 이뤄 파일 복사를 해볼까요? 마치 찰떡궁합처럼 말이죠! 이 둘의 조합은 정말 강력해요. 마치 슈퍼히어로 듀오 같달까요? ^^ 파일 복사 기능은 프로그래밍에서 정말 빈번하게 사용되는 기능 중 하나인데, C#에서는 StreamReader와 StreamWriter를 사용해서 아주 효율적으로 구현할 수 있답니다. 복잡한 파일 시스템 API를 직접 다루지 않고도 말이죠!

기본적인 원리는 간단해요. StreamReader를 사용해서 원본 파일에서 데이터를 읽어오고, StreamWriter를 사용해서 읽어온 데이터를 새로운 파일(대상 파일)에 써주는 거예요. 마치 물을 한 양동이에서 다른 양동이로 옮겨 담는 것과 비슷하다고 생각하면 돼요. 단, 여기서 양동이는 파일이고, 물은 데이터겠죠? ㅎㅎ

코드 예시

코드로 한번 살펴볼까요? 아래 예시는 source.txt 파일을 읽어서 destination.txt 파일로 복사하는 코드예요.

using System.IO;

// ... (이전 코드 생략)

try
{
    // StreamReader와 StreamWriter 객체 생성
    using (StreamReader reader = new StreamReader("source.txt"))
    using (StreamWriter writer = new StreamWriter("destination.txt"))
    {
        // 파일의 끝까지 반복해서 읽고 씀
        string line;
        while ((line = reader.ReadLine()) != null) 
        {
            writer.WriteLine(line); // 읽어온 라인을 새로운 파일에 씀
        }
        // 모든 데이터가 복사되었어요!
    }
}
catch (Exception ex)
{
    // 예외 처리 (자세한 내용은 다음 섹션에서 다룰 거예요!)
    Console.WriteLine($"파일 복사 중 오류 발생: {ex.Message}");
}

여기서 using 문은 아주 중요한 역할을 해요! using 블록 안에서 생성된 StreamReader와 StreamWriter 객체는 블록이 끝나면 자동으로 닫히도록 보장해 준답니다. 마치 자동문처럼 말이죠! 이렇게 하면 파일을 명시적으로 닫지 않아도 되고, 리소스 누수(resource leak)를 방지할 수 있어요. 파일 핸들을 제대로 닫지 않으면 시스템 자원을 낭비하게 되고, 심각한 경우에는 시스템 불안정으로 이어질 수 있으니 꼭 기억해 두세요!

자, 위 코드에서 reader.ReadLine() 메서드는 한 줄씩 읽어오는 역할을 하고, writer.WriteLine() 메서드는 한 줄씩 쓰는 역할을 해요. 이 부분이 파일 복사의 핵심이라고 할 수 있죠! while 루프는 파일의 끝(null)에 도달할 때까지 계속 반복되면서 파일 내용을 한 줄씩 복사한답니다. 마치 성실한 일꾼처럼 말이죠!

버퍼를 사용한 파일 복사

만약 파일이 아주 크다면, 한 줄씩 읽고 쓰는 것보다 버퍼를 사용하는 것이 성능 향상에 도움이 될 수 있어요. 마치 택배를 한 번에 여러 개씩 옮기는 것과 같은 원리예요. 버퍼를 사용하려면 reader.Read()writer.Write() 메서드를 사용하면 돼요. 아래는 버퍼를 사용한 예시 코드입니다.

using (StreamReader reader = new StreamReader("source.txt"))
using (StreamWriter writer = new StreamWriter("destination.txt"))
{
    char[] buffer = new char[4096]; // 4KB 버퍼
    int charsRead;
    while ((charsRead = reader.Read(buffer, 0, buffer.Length)) > 0)
    {
        writer.Write(buffer, 0, charsRead);
    }
}

이 코드에서는 4KB 크기의 char 배열을 버퍼로 사용하고 있어요. 버퍼 크기를 조절하면 성능에 영향을 줄 수 있으니, 적절한 크기를 찾는 것이 중요해요! 일반적으로 4KB~8KB 정도가 적당하다고 알려져 있지만, 상황에 따라 다를 수 있으니 테스트를 통해 최적의 값을 찾아보는 것도 좋을 것 같아요! 😊

이처럼 StreamReaderStreamWriter를 사용하면 파일 복사를 효율적이고 안전하게 처리할 수 있답니다! 다음 섹션에서는 예외 처리와 파일 닫기에 대해 자세히 알아볼 거예요. 기대해 주세요~! 😉

 

예외 처리 및 파일 닫기

파일을 다루는 작업은 컴퓨터의 자원을 사용하는 것이기 때문에, 항상 예기치 못한 상황에 대비해야 해요. 마치 낯선 길을 떠날 때 예비 타이어와 구급상자를 챙기는 것처럼 말이죠! 파일을 읽고 쓰는 과정에서 발생할 수 있는 다양한 오류들을 처리하지 않으면 프로그램이 갑자기 종료되거나, 심지어 데이터 손실까지 발생할 수 있답니다. 생각만 해도 아찔하죠?! 그래서 `try-catch-finally` 블록을 사용하여 예외 처리를 하는 것이 정말 중요해요. 마치 안전벨트를 매는 것과 같다고 할 수 있죠!

try-catch-finally 블록

`try` 블록 안에는 파일을 읽고 쓰는 핵심 코드를 넣어요. `StreamReader`와 `StreamWriter`를 사용하는 부분이죠. 이 블록 안에서 예외가 발생하면, 프로그램은 즉시 `catch` 블록으로 이동해요. `catch` 블록에서는 예외의 종류에 따라 적절한 처리를 해줘야 한답니다. 예를 들어, 파일을 찾을 수 없는 `FileNotFoundException`이 발생하면 사용자에게 “죄송해요, 파일을 찾을 수 없어요ㅠㅠ”라는 메시지를 표시할 수 있겠죠? 아니면 다른 파일을 선택하도록 안내할 수도 있고요! `IOException`처럼 파일 입출력과 관련된 더 일반적인 예외를 처리할 수도 있어요. 마치 만능 열쇠처럼 다양한 상황에 대처할 수 있는 거죠!

finally 블록

자, 이제 `finally` 블록에 대해 알아볼까요? `finally` 블록은 예외 발생 여부와 상관없이 항상 실행되는 특별한 영역이에요. `try` 블록에서 열었던 파일을 닫는 작업은 바로 이 `finally` 블록 안에서 처리해야 합니다! 왜냐하면, 파일을 열어둔 채로 프로그램이 종료되면 시스템 자원이 낭비되고, 심지어 파일이 손상될 수도 있기 때문이죠. 마치 사용한 컵은 깨끗이 씻어서 제자리에 두는 것과 같아요. `StreamReader`와 `StreamWriter` 객체는 `Close()` 메서드를 사용해서 닫아줄 수 있어요. 아주 간단하죠?!


try
{
    // StreamReader 및 StreamWriter를 사용하여 파일 작업 수행
    using (StreamReader reader = new StreamReader("input.txt"))
    using (StreamWriter writer = new StreamWriter("output.txt")) 
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            writer.WriteLine(line);
        }
    } // using 블록이 끝나면 reader와 writer가 자동으로 닫힙니다.
}
catch (FileNotFoundException ex)
{
    Console.WriteLine("파일을 찾을 수 없습니다: " + ex.Message);
}
catch (IOException ex)
{
    Console.WriteLine("파일 입출력 오류: " + ex.Message);
}
// finally 블록은 using 구문으로 대체되었습니다.

using 구문

위의 코드에서는 using 구문을 사용했는데요, 이 using 구문은 try-finally 블록을 간결하게 만들어주는 마법과도 같아요! using 블록 안에서 생성된 객체는 블록이 끝나는 순간 자동으로 Dispose()` 메서드가 호출되어 안전하게 정리된답니다. StreamReaderStreamWriterDispose()` 메서드 내부에서 Close()` 메서드를 호출하기 때문에, using 구문을 사용하면 파일 닫기를 잊어버릴 염려가 없어요! 정말 편리하죠? ^^

파일을 다루는 작업은 컴퓨터와 소통하는 중요한 통로와 같아요. 예외 처리와 파일 닫기를 통해 안전하고 효율적으로 파일을 다룰 수 있도록 항상 신경 써야 한답니다! 마치 소중한 친구를 대하듯이 말이죠! 이러한 작은 습관들이 프로그램의 안정성과 성능을 크게 향상시켜줄 거예요! 자, 이제 여러분도 안전하게 파일을 다룰 준비가 되었나요?

다양한 예외 상황

더 나아가서, 파일을 읽고 쓸 때 발생할 수 있는 다양한 예외 상황에 대해 좀 더 자세히 알아볼까요? 예를 들어, 파일을 열려고 시도했는데 다른 프로그램이 이미 사용 중인 경우에는 IOException의 한 유형인 SharingViolationException이 발생할 수 있어요. 이런 경우에는 사용자에게 "죄송해요, 파일이 다른 프로그램에서 사용 중이에요 ㅠㅠ" 라는 메시지를 표시하고, 잠시 후에 다시 시도하도록 안내할 수 있겠죠?

또는, 디스크 공간이 부족해서 파일을 쓸 수 없는 경우에는 IOException의 또 다른 유형인 DiskFullException이 발생할 수도 있어요. 이럴 때는 "앗, 디스크 공간이 부족해요! 불필요한 파일을 정리해주세요~" 라는 메시지와 함께 디스크 정리 유틸리티를 실행하는 링크를 제공하는 것도 좋은 방법이겠네요!

UnauthorizedAccessException은 파일 접근 권한이 없을 때 발생하는 예외입니다. 이 경우에는 "죄송하지만, 이 파일에 접근할 권한이 없어요 ㅠㅠ 관리자에게 문의해주세요~" 라는 메시지를 표시하고, 관리자에게 연락할 수 있는 정보를 제공하는 것이 좋겠죠?

이처럼 다양한 예외 상황에 대한 구체적인 처리 방안을 마련해두면 사용자에게 더욱 친절하고 유용한 프로그램을 제공할 수 있답니다! 마치 꼼꼼한 여행 가이드처럼 말이죠! 예외 처리, 어렵게 생각하지 말고 하나씩 차근차근 배워나가면 돼요! 화이팅!

파일 잠금(File Locking)

파일 잠금(File Locking)에 대해서도 알아두면 좋을 것 같아요! 여러 프로그램이 동시에 같은 파일에 접근하려고 할 때, 데이터가 손상되는 것을 방지하기 위해 파일 잠금 기능을 사용할 수 있어요. .NET에서는 FileStream 객체를 생성할 때 FileShare 열거형을 사용하여 파일 잠금 모드를 설정할 수 있답니다. 예를 들어, FileShare.None으로 설정하면 다른 프로그램이 해당 파일에 접근하는 것을 완전히 차단할 수 있어요. 마치 비밀번호를 걸어놓은 금고처럼 말이죠! 반대로, FileShare.Read로 설정하면 다른 프로그램이 파일을 읽을 수는 있지만, 쓰기는 할 수 없도록 제한할 수 있답니다. 파일 잠금을 적절히 활용하면 데이터의 무결성을 보장하고, 안전하게 파일을 공유할 수 있어요!

 

자, 이제 C#에서 StreamReaderStreamWriter를 이용해서 파일을 읽고 쓰는 방법, 어느 정도 감이 잡히셨나요? 처음엔 조금 낯설게 느껴질 수도 있지만, 몇 번 연습하다 보면 금방 손에 익을 거예요. 파일 다루기는 프로그래밍에서 정말 기본적이면서도 중요한 부분이거든요. 복잡한 프로그램을 만들더라도, 결국 파일을 읽고 쓰는 작업은 꼭 필요하게 돼요. 오늘 배운 내용을 토대로 여러분만의 프로그램을 만들어보고, 다양한 시도를 해보면서 실력을 키워나가면 좋겠어요. 혹시라도 궁금한 점이나 어려운 부분이 있다면 언제든 질문해주세요! 함께 고민하고 해결해나가면 프로그래밍이 더 재밌어질 거예요. 😊 다음에 또 유용한 정보로 찾아올게요!

 


코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다