1. 파일 입출력 이란? ( File Input/Output )


C#을 콘솔로 공부하면서 우리는 콘솔창에 많은 메시지를 출력 해왔습니다.

이와 마찬가지로 우리가 필요한 정보를 파일로 하드 디스크에 입출력(읽기, 쓰기)하는 것입니다.

프로그램을 제작하다 보면 코드가 많아진 상황에서 디버그나 필요한 정보의 로그파일(logfile) 즉, 로그를 남겨 정상적인

작동을 하고있는지 체크를 하기도하고 특정 파일형태로 게임 데이터를 저장(SaveFile)하여 불러오기도 합니다.

우선 가장 기본적인 txt 파일을 사용해보도록 하겠습니다. 윈도우에서 메모장을 열어 글을 쓰고 저장을 하면 기본이되는

파일형식인데요. 프로그램에서도 System.IO 라는 네임스페이스로 파일 입출력의 기능을 지원해주고 있습니다.

System.IO 의 구성을 나열하기에는 글이 너무 길어져 MSDN에 기술되어 있는 문서를 참고해주세요.


MSDNhttps://docs.microsoft.com/ko-kr/dotnet/api/system.io?view=netframework-4.7.2


2. 디렉토리 ( Directory )


파일을 읽고 쓰기전 우리는 우선 파일경로에 대해 알아야 합니다. 파일을 읽고 쓰는데 가장 기본적인 오류가 발생하는

곳으로 우리가 저장하고 불러올 파일이 정확히 어디있는지 혹은 어디에 저장할지 정할줄 알아야 한다는 것이지요.


소스코드

using System;
using System.IO;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //현재 경로
            string currentPath = Directory.GetCurrentDirectory();
            currentPath += "\\Save";

            //특정 경로를 지정하고 싶다면 
            //string currentPath = @"c:\test";
            //string currentPath = "c:\\test";
            //@ 심벌은 문자열 앞에 사용하면 해당 문자열 안의 Escape 문자를
            //무시하고 문자 그대로 인식하기때문에 \\ 보다 자연스럽게 \로 패스를 지정할수 있습니다.


            //현재 경로에 Save 폴더가 존재하는지 확인
            if (Directory.Exists(currentPath))
            {
                Console.WriteLine("경로 존재");
            }
            else
            {
                //디렉토리 생성
                Directory.CreateDirectory(currentPath);
                Console.WriteLine(currentPath + " 에 폴더 생성!");
            }
        }
    }
}

첫 실행 결과

...\...\bin\Debug\Save 에 폴더 생성!

두번째 실행 결과

경로 존재


Directory

 - GetCurrentDirectory() : 프로그램 실행파일이 있는 경로를 가져옵니다.

 - Exists( [파일경로] ) : 파일경로 존재 유무에 따라 bool 타입으로 반환됩니다.

 - CreateDirectory ( [파일경로] ) : 해당 파일경로에 폴더를 생성 합니다. 


주석의 내용과 같이 @ 심벌의 사용 유무는 문자열 안의 Escape 문자 인식 여부에 따라 경로를 지정할수 있습니다.



3. 파일 입출력


파일을 입출력 하기 위해서는 두번째 네임스페이스( System.Text )가 필요합니다.

이는 인코딩(Encoding)을 쓰기 위함인데 인코딩은 정보의 형태나 형식을 변환하는 처리를 말합니다.

쉽게말해 문자를 구성하는 표준 코드를  만들어 사람간 또는 사람과 컴퓨터간 정보를 교환하기 위해 사용합니다.


소스코드

using System;
using System.IO;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //현재 경로
            string currentPath = Directory.GetCurrentDirectory();
            currentPath += "\\Save";

            //특정 경로를 지정하고 싶다면 
            //string currentPath = @"c:\test";
            //string currentPath = "c:\\test";
            //@ 심벌은 문자열 앞에 사용하면 해당 문자열 안의 Escape 문자를
            //무시하고 문자 그대로 인식하기때문에 \\ 보다 자연스럽게 \로 패스를 지정할수 있습니다.


            //현재 경로에 Save 폴더가 존재하는지 확인
            if (Directory.Exists(currentPath))
            {
                Console.WriteLine("경로 존재");
            }
            else
            {
                //디렉토리 생성
                Directory.CreateDirectory(currentPath);
                Console.WriteLine(currentPath + " 에 폴더 생성!");
            }

            //생성 파일의 이름과 형식(txt)을 적용합니다.
            string filePath = currentPath + "\\test.txt";


            //파일 스트림을 만든다. 
            FileStream fileStream = new FileStream(
                filePath,              //저장경로
                FileMode.Create,       //파일스트림 모드
                FileAccess.Write       //접근 권한
                );

            //파일스트림 모드
            //FileMode.Create           //파일을 만든다 있으면 덮어 씀.
            //FileMode.CreateNew        //파일을 만든다 있으면 IOException 예외가 발생.
            //FileMode.Append           //파일을 만든다 있으면 뒤에서 부터 추가로 씀.

            //FileMode.Open             //파일을 연다 없다면 FileNotFoundException 예외 발생
            //FileMode.OpenOrCreate     //파일을 연다 없으면 만듬. ( 문제가 있으니 쓰지말자 )
            //FileMode.Truncate         //파일을 연다 없으면 만듬. ( 열든 만들든 무조건 빈파일로 )

            string strData = "오늘도 강좌를 읽어 주셔서 감사합니다.\r\n블로그에 자주 찾아와 주세요.\r\n";

            
            //
            // 방법 1 ( StreamWriter 를 이용한방법 )
            //

            //파일스트림에 무언가를 써주는 StreamWriter 만들기
            StreamWriter streamWriter = new StreamWriter(
				fileStream,            //쓸 파일스트림을 여기에다가.....
				Encoding.UTF8          //파일에다 쓸때 인코딩 객체 전달.
				);


			streamWriter.Write(strData);
            
            //스트림 Writer 닫아 주세요.
            streamWriter.Close();

            /*
            //
            // 방법 2 ( Encoding 을 통해 byte 배열로 쓰는법 )
            //

            byte[] byteArr = Encoding.UTF8.GetBytes(strData);

            //파일 스트림에다가 byte 배열을 직접쓰기.
            fileStream.Write(
                byteArr,
                0,
                byteArr.Length);
            */

            //파일스트림을 다 썼다면 반드시 닫아 주세요.
            fileStream.Close();

            Console.WriteLine("파일생성 또는 덮어 쓰기 완료!");
        }
    }
}

결과

경로 존재
파일생성 또는 덮어 쓰기 완료!


실제 파일이 생성되고 문서내용이 정확이 입력되었는지 해당폴더로 가서 직접 파일을 열어보세요.

56줄에 /* 와 72줄 */ 을 추가하고 73줄 /* 85줄 */ 을 삭제하여 방법 2번도 사용해보세요.

다쓴 FileStream, StreamWriter 인스턴스는 반드시 닫아주세요.


위의 코드와 같이 파일을 저장했다면 불러와서 직접 코드내에서 문서의 내용을 확인해 봐야 겠지요?


소스코드

using System;
using System.IO;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //현재 경로
            string currentPath = Directory.GetCurrentDirectory();
            currentPath += "\\Save";

            //현재 경로에 Save 폴더가 존재하는지 확인
            if (!Directory.Exists(currentPath))
            {
                //디렉토리 생성
                Directory.CreateDirectory(currentPath);
                Console.WriteLine(currentPath + " 에 폴더 생성!");
            }

            //생성 파일의 이름과 형식(txt)을 적용합니다.
            string filePath = currentPath + "\\test.txt";

            //string sFileName = "\\test.txt";
            string strData = "오늘도 강좌를 읽어 주셔서 감사합니다.\r\n블로그에 자주 찾아와 주세요.\r\n";

            //파일 쓰기 메서드
            FileWrite(filePath, strData);

            //파일 읽기 메서드
            FileRead(filePath);
        }
        
        static void FileWrite(string filePath, string strData)
        {
            //파일 스트림을 만든다. 
            FileStream fileStream = new FileStream(
                filePath,              //저장경로
                FileMode.Create,       //파일스트림 모드
                FileAccess.Write       //접근 권한
                );

            StreamWriter streamWriter = new StreamWriter(fileStream, Encoding.UTF8);
            streamWriter.Write(strData);

            //다쓴 StreamWriter 와 FileStream 닫기
            streamWriter.Close();
            fileStream.Close();
        }
        static void FileRead(string filePath)
        {
            //폴더 경로는 확인하였고 실제 파일이 있는지 유무를 확인
            FileInfo fi = new FileInfo(filePath);
            if(fi.Exists)
            {
                //파일 존재
                //파일 읽어올 스트림 준비
                FileStream fileStream = new FileStream(
                    filePath,
                    FileMode.Open,
                    FileAccess.Read);

                //파일스트림을 읽는 Reader 생성
                StreamReader streamReader = new StreamReader(
                    fileStream,
                    Encoding.UTF8);         //쓰기와 동일하게 UTF8로 읽음.

                //읽어올 파일을 저정할 스트링 빌더 생성
                StringBuilder strBulider = new StringBuilder(1000);  //빌더크기(Capacity) 1000

                //Peek : 파일 끝에 도달하거나 문제가 발생하면 -1을 반환 합니다.
                while (streamReader.Peek() > -1)    //파일의 끝이 아닐동안계속
                {
                    string strLine = streamReader.ReadLine();      //파일 한줄 읽어온다.

                    //읽어온내용 문자열 추가
                    //strBulider.Append(strLine);

                    //읽어온내용 문자열로 추가하고 줄바뀜
                    strBulider.AppendLine(strLine);

                }

                //스트링 빌더에 있는 내용 출력
                Console.WriteLine(strBulider.ToString());

                //다쓴 StreamReader 와 FileStream 닫기
                streamReader.Close();
                fileStream.Close();
            }
            else
            {
                //파일 없음
                Console.WriteLine("읽을 파일이 없습니다.");
            }
        }
    }
}

결과

오늘도 강좌를 읽어 주셔서 감사합니다.
블로그에 자주 찾아와 주세요.


코드가 길어져서 읽기와 쓰기를 분할하여 메서드에 옴겨 놓았습니다.

파일 일기코드는 52줄에서 98줄을 참고해주세요.


파일쓰기와 마찬가지로 파일의 존재 유무에 대해 알 필요성이 있어 FileInfo 클래스를 이용하여 확인하고

파일을 받아들이는 FileStream은 동일하게 사용 하였습니다. 차이점은 쓰기는 StreamWriter를 이용하고 읽기는

StreamReader를 이용합니다. 또한 문서의 끝을 알기위해 Peek() 메서드를 활용하여 문서의 끝에 도달하면 반복문을

빠져나오는 구조 입니다. 읽어 들이는 방법은 여러가지 형태가 있으며 상황에 맞게 사용합니다.

소스코드에서는 한줄씩 읽어 들이는 방법으로 사용하였고 처리 또한 긴 문서가 아니여서 간단하게 처리되었습니다.


이상으로 파일 입출력 강좌를 마치도록 하겠습니다.



추가 내용

소스코드

using System;
using System.IO;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //현재 경로
            string currentPath = Directory.GetCurrentDirectory();
            currentPath += "\\Save";

            //현재 경로에 Save 폴더가 존재하는지 확인
            if (!Directory.Exists(currentPath))
            {
                //디렉토리 생성
                Directory.CreateDirectory(currentPath);
                Console.WriteLine(currentPath + " 에 폴더 생성!");
            }

            //생성 파일의 이름과 형식(txt)을 적용합니다.
            string filePath = currentPath + "\\test.txt";

            // 우리는 다음과 같은 규칙을 가지고 데이터를 저장합니다.
            // [데이터 이름]=[값]
            string strData = "hp=10\nmp=20\n";

            //파일 쓰기 메서드
            FileWrite(filePath, strData);

            //파일 읽기 메서드
            FileRead(filePath);
        }
        
        static void FileWrite(string filePath, string strData)
        {
            //파일 스트림을 만든다. 
            FileStream fileStream = new FileStream(
                filePath,              //저장경로
                FileMode.Create,       //파일스트림 모드
                FileAccess.Write       //접근 권한
                );

            StreamWriter streamWriter = new StreamWriter(fileStream, Encoding.UTF8);
            streamWriter.Write(strData);

            //다쓴 StreamWriter 와 FileStream 닫기
            streamWriter.Close();
            fileStream.Close();
        }
        static void FileRead(string filePath)
        {
            //폴더 경로는 확인하였고 실제 파일이 있는지 유무를 확인
            FileInfo fi = new FileInfo(filePath);
            if(fi.Exists)
            {
                //파일 존재
                //파일 읽어올 스트림 준비
                FileStream fileStream = new FileStream(
                    filePath,
                    FileMode.Open,
                    FileAccess.Read);

                //파일스트림을 읽는 Reader 생성
                StreamReader streamReader = new StreamReader(
                    fileStream,
                    Encoding.UTF8);         //쓰기와 동일하게 UTF8로 읽음.

                //읽어올 파일을 저정할 스트링 빌더 생성
                StringBuilder strBulider = new StringBuilder(1000);  //빌더크기(Capacity) 1000

                //Peek : 파일 끝에 도달하거나 문제가 발생하면 -1을 반환 합니다.
                while (streamReader.Peek() > -1)    //파일의 끝이 아닐동안계속
                {
                    string strLine = streamReader.ReadLine();      //파일 한줄 읽어온다.

                    //읽어온내용 문자열 추가
                    //strBulider.Append(strLine);

                    //읽어온내용 문자열로 추가하고 줄바뀜
                    strBulider.AppendLine(strLine);

                }

                //스트링 빌더에 있는 내용 출력
                Console.WriteLine(strBulider.ToString());

                //다쓴 StreamReader 와 FileStream 닫기
                streamReader.Close();
                fileStream.Close();

                //추가) 정한 규칙으로 저장된 데이터를 파싱 합니다.
                int hp = 0;
                int mp = 0;

                string strTemp = strBulider.ToString();

                //string의 split메서드를 활용하여 개행문자 '\n' 을 기준으로 기존의 문자열을
                //나누어 3개의 배열로 반환됩니다.
                string[] data = strTemp.Split('\n');        
                
                for (int i = 0; i < data.Length; i++)
                {
                    //data[0]는 "hp=10" data[1]는 "mp=20" data[2] = "" 형태의 문자열로 저장되어 있습니다.

                    if(data[i].Length > 0)  //반환된 배열중에 데이터가 없는 배열은 제외(data[2])
                    {
                        string TempData = data[i];              

                        // 우리는 "데이터 이름=값" 형식으로 저장하였기에 다시한번 split로 문자'=' 를 기준으로 나누면
                        // 0번 배열은 데이터 이름, 1번 배열은 값을 가진다는
                        // 규칙을 이용하여 데이를 파싱 할수 있습니다.
                        string[] result = TempData.Split('='); 

                        if (result[0] == "hp")
                            hp = Convert.ToInt32(result[1]);  // convert 클래스는 문자열을 원하는 형태로 변환합니다.
                        if(result[0] == "mp")
                            mp = Convert.ToInt32(result[1]);
                    }
                }
                Console.WriteLine("hp = " + hp.ToString());
                Console.WriteLine("mp = " + mp.ToString());
            }
            else
            {
                //파일 없음
                Console.WriteLine("읽을 파일이 없습니다.");
            }
        }
    }
}

추가 코드는 

[데이터 이름] = [값] 이라는 규칙을 가지고 데이터를 저장하고 파싱하여 원하는 데이터로 활용할수 있게 다시 파싱하는 코드를 수정 및 추가 하였습니다.

+ Recent posts