1. 개념


퀵 정렬 ( Quick Sort )은 "찰스 앤터니 리처드 호어"가 개발한 정렬 알고리즘으로 현재 정렬 알고리즘 중에 최고의

속도를 자랑하며 많이 쓰이는 정렬방식이다.

최악의 경우는 n번의 비교를 수행하고 평균적으로 n log n 번의 비교를 수행하여 정렬을 하고 실질적으로는

메모리 참조가 지역화 되어 있어 CPU 캐시의 히트율이 높아 정렬시 제곱 시간만큼 걸릴 확률이 거의 없도록

알고리즘을 설계하는 것이 가능함


기본 알고리즘

    - 배결 가운데 하나의 원소를 고르고 그 원소를 Pivot이라 부른다.

    - Pivot 보다 작은 값은 앞쪽(Left)에 값이 오고 큰 값은 뒷쪽(Right)에 값이 오도록 한다.

    - 이렇게 Pivot의 위치가 정해지면 분할된 양쪽의 배열을 재귀적으로 이 과정을 반복함.


위의 그림과 같이 배열의 0번째 인덱스 ( 재귀 호출시에는 가장 앞쪽의 배열이 0번째 인덱스가 됨 )가 Pivot으로 정하여 해당

값 보다 작은 값은 왼쪽 큰 값은 오른쪽으로 이동하게 조건을 이용하여 분할 하였으며 이렇게 호출된 메서드는 완료시

하나의 pivot index가 정해지고 더이상 분할되지 않을때까지 재귀적으로 호출하여 값을 정렬 함

이 알고리즘의 이상적인 상태는 pivot이 되는 값이 양쪽을 균일하게 분할되게 하는 값일때 가장 빠른 속도를 낼수 있음



2. C# 코드


using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nArr = new int[]{ 1, 4, 3,  5, 9, 6, 2, 7, 8, 10 };

            quick_sort( nArr, 0, nArr.Length-1);

            for (int i = 0; i < nArr.Length; i++)
                Console.Write(nArr[i] + "\t");
        }
        private static int ArryDivide(int[] Arry, int left, int right)
        {
            int PivotValue, temp;
            int index_L, index_R;

            index_L = left;
            index_R = right;

            //Pivot 값은 0번 인덱스의 값을 가짐
            PivotValue = Arry[left];

            while(index_L < index_R)
            {
                //Pivot 값 보다 작은경우 index_L 증가(이동)
                while ( (index_L <= right) && (Arry[index_L] < PivotValue) )
                    index_L++;

                //Pivot 값 보다 큰경우 index_R 감소(이동)
                while ( (index_R >= left)  && (Arry[index_R] > PivotValue) )
                    index_R--;

                //index_L 과 index_R 이 교차되지 않음
                if(index_L < index_R)
                {
                    temp = Arry[index_L];
                    Arry[index_L] = Arry[index_R];
                    Arry[index_R] = temp;

                    //같은 값이 존재 할 경우 
                    if (Arry[index_L] == Arry[index_R])
                        index_R--;
                }
            }

            //index_L 과 index_R 이 교차된 경우 반복문을 나와 해당 인덱스값을 리턴
            return index_R;
        }
        private static void quick_sort( int[] arry, int left, int right)
        {
            if(left < right)
            {
                int PivotIndex = ArryDivide(arry, left, right);

                quick_sort( arry, left, PivotIndex - 1);
                quick_sort( arry, PivotIndex + 1, right);
            }
        }
    }
}

결과

1    2    3    4    5    6    7    8    9    10




3. 장단점


장점

    - 현재 모든 정렬 알고리즘보다 빠른 속도를 자랑한다

    - 균적으로 n log n 번의 비교를 수행 하지만 메모리 참조가 지역화 되어 제곱 시간만큼 시간 복잡도가 걸리지 않음

    - 고정적으로 log n 만큼의 메모리를 필요로 함

단점

    - 정렬시 불균형하게 분할되 수행시간이 차이가 날수 있다.



정렬 알고리즘 테스트

종류

 최소

 최대

정렬시간 (10만개 테스트)

퀵 정렬

n log n

 n2

 32 ms

 삽입 정렬

n

n2

8142 ms

 선택 정렬

n2

n2

13499 ms

 버블 정렬

n2

n2

53488 ms




4. 관련글



1. 개념


정렬하고자 하는 배열중 최소의 값을 찾아 차례 차례 순서에 따라 값을 교체 해주는 방식으로 최소값을 찾는 시간은

처음 찾을때 보다 정렬이 진행 될수록 찾는 시간이 단축되는 구조 입니다.


  - 검색 시작지점은 배열의 인덱스 0부터 시작하며 진행순서에 따라 시작지점 인덱스는 1씩 증가

  - 최소값 또는 최대값을 가진 인덱스를 찾고 시작 인덱스와 교환




위의 사진처럼 오름차순으로 정렬을 진행할때 회차에 따라 시작인덱스는 1씩증가되며 시작인덱스 외의 나머지 인덱스들을

검색하여 가장 작은 값을 찾아 교환해주는 방식으로 진행됩니다.



2. C# 코드


using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nArr = new int[]{ 1, 4, 3,  5, 9, 6, 2, 7, 8, 10 };
            int TargetIndex, temp;

            //배열의 마지막 값은 자연스럽게 정렬이 되므로 배열크기 -1 만큼 반복
            for(int i = 0; i < nArr.Length-1; i++)
            {
                //정렬방식에 따라 교환이 이루어질 인덱스
                TargetIndex = i;

                //비교대상 인덱스는 시작인덱스 +1 에서 배열의 마지막까지 반복
                for(int j = i+1; j < nArr.Length; j++)
                {
                    //정렬방식 < : 오름차순, > : 내림차순
                    if (nArr[j] < nArr[TargetIndex])
                        TargetIndex = j;
                }
                
                if( i != TargetIndex)
                {
                    //검색을 시작한 가장 앞쪽 인덱스와 교환
                    temp = nArr[i];
                    nArr[i] = nArr[TargetIndex];
                    nArr[TargetIndex] = temp;
                }
            }

            for (int i = 0; i < nArr.Length; i++)
                Console.Write(nArr[i] + "\t");
        }
    }
}

결과

1    2    3    4    5    6    7    8    9    10



3. 장단점


장점

    - 비교적 쉬운 코드로 구현 가능

    - 정렬이 진행됨에 따라 속도는 빨라짐

    - 버블 정렬보다 값의 복사와 이동이 적어 비교적 빠름

단점

    - 데이터의 크기가 커질수록 효율이 떨어짐

    - 데이터 정렬 속도가 고정적으로 n의 제곱만큼 걸려 더이상의 정렬 속도를 기대할수 없음


정렬 알고리즘 테스트

종류

 최소

 최대

정렬시간 (10만개 테스트)

퀵 정렬

n log n

 n2

 32 ms

 삽입 정렬

n

n2

8142 ms

 선택 정렬

n2

n2

13499 ms

 버블 정렬

n2

n2

53488 ms


시간적인 측면에서는 고정적으로 정렬시간이 안정적이지만 더이상의 속도를 낼수 없고 구현이 간단한 정렬방식의

구조적 한계 때문에 비효율적인 방법 입니다.


4. 관련글


1. 개념


버블 정렬과 같이 단순 비교문을 이용하여 정렬하고자 하는 값를 옴겨 놓는 방식은 비슷하나

키( Key )에 해당하는 값을 정하고 앞쪽의 하나씩 비교 대상 값과 비교하여 위치를 옴겨 놓는 방식


- key 값은 배열의 두번째 인덱스 값부터 시작하며 1회당 인덱스를 1씩 증가시켜 배열의 끝까지 도달합니다.

- 비교 대상 ( compare ) 값은 key 인덱스보다 -1 낮은 인덱스부터 시작하며 1회당 -1씩 감소 시켜 배열의 처음까지 도달합니다.

- 비교 조건(오름차순, 내림차순)에 해당한다면 값을 교환 변경 합니다.


key의 시작 인덱스 : 1

comare의 시작 인덱스 : 0


위의 사진과 조건에 해당할때만 값의 복사가 일어나서 버블 정렬과 다르게 시간을 단축시키는 요소가 생깁니다.



2. C# 코드


using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nArr = new int[]{ 1, 4, 3,  5, 9, 6, 2, 7, 8, 10 };

            // nKey : 키 값
            // nCompareIndex : 비교 값이 되는 인덱스
            // nCount : 값의 교환 횟수
            int nKey, nCompareIndex, nCount; 
            nCount = 0;

            // 코드의 이해를 돕기위해 반복문의 인자 값을 이용목적으로 변수이름을 사용
            // 시작 키 인덱스 : 1
            for (int KeyIndex = 1; KeyIndex < nArr.Length; KeyIndex++)
            {
                nKey = nArr[KeyIndex];

                // 시작 비교 인덱스 : 0
                // nArr[nCompareIndex] > nKey : 오름차순
                // nArr[nCompareIndex] < nKey : 내림차순
                for (nCompareIndex = KeyIndex - 1; nCompareIndex >= 0 && nArr[nCompareIndex] > nKey; nCompareIndex--)
                {
                    nArr[nCompareIndex + 1] = nArr[nCompareIndex];
                    nCount++;
                }

                nArr[nCompareIndex + 1] = nKey;
            }

            Console.WriteLine("Count : {0}", nCount);

            for (int i = 0; i < nArr.Length; i++)
                Console.Write(nArr[i] + "\t");

        }
    }
}

결과

Count : 9
1    2    3    4    5    6    7    8    9    10



3. 장단점


장점

    - 비교적 쉬운 코드로 구현 가능

    - 데이터의 크기가 작을 수록 복잡한 코드로 구현된 정렬보다 빠를수 있음

    - 같은 구현 난이도의 버블 정렬보다 정렬 속도의 이점을 가지고 있음

단점

    - 데이터의 크기가 커질수록 효율이 떨어짐

    - 최악의 경우 비교 횟수와 교환 횟수가 버블정렬과 같이 n의 2승만큼 커질수 있음


정렬 알고리즘 테스트

종류

 최소

 최대

정렬시간 (10만개 테스트)

퀵 정렬

n log n

 n2

 32 ms

 삽입 정렬

n

n2

8142 ms

 선택 정렬

n2

n2

13499 ms

 버블 정렬

n2

n2

53488 ms

단적인 측면에서는 버블 정렬보다는 빠르지만 근본적인 효율에서는 비효율적인 방법 입니다.


4. 관련글


1. 개념


인접한 두 원소를 비교 하여 정렬하는 방법입니다.

단순히 A > B  라는 조건식을 이용하여 1회전 마다 큰 값이든 작은 값이든 하나씩 위치를 맞춰가는 방식으로

정렬하고자 하는 자료의 n의 2승만큼 반복하여 정렬합니다.


- 오름 차순 : 작은 값을 먼저 오게 만드는 정렬 방식 ( 1, 2, 3 ... )으로 A>B 라면 교환하여 정렬합니다.

- 내림 차순 :  큰 값을 먼저 오게 만드는 정렬 방식 ( ... 3, 2, 1 ) 으로 A<B 라면 교환하여 정렬합니다.


2. c# 코드


내림차순

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nArr = new int[]{ 1, 4, 3,  5, 9, 6, 2, 7, 8, 10 };

            int nTemp;
            int nCount = 0;
            for(int i = 0; i < nArr.Length-1; i++)
            {
                for (int j = 0; j < nArr.Length-1; j++)
                {
                    if(nArr[j] < nArr[j+1])  
                    {
                        nTemp = nArr[j+1];
                        nArr[j+1] = nArr[j];
                        nArr[j] = nTemp;
                    }
                    nCount++;
                }
            }

            Console.WriteLine("Count : {0}", nCount);

            for(int i = 0; i < nArr.Length; i++)
                Console.Write(nArr[i] + "\t");
        }
    }
}

결과

Count : 81
10  9  8  7  6  5  4  3  2  1


조건식만 바꿔준다면 오름차순으로 정렬됩니다. ( nArr[j] > nArr[j+1] )

결과에서 알수있듯 총 비교횟수는 81회입니다. 이는 데이터 총갯수 - 1의 2승 만큼 계산되어 진것으로 연산순서는

아래와 같습니다.


//------- 시작 --------
// 1 4 3 5 9 6 2 7 8 10
//------- 1 회 --------
// 4 1 3 5 9 6 2 7 8 10
// 4 3 1 5 9 6 2 7 8 10
// 4 3 5 1 9 6 2 7 8 10
// 4 3 5 9 1 6 2 7 8 10
// 4 3 5 9 6 1 2 7 8 10
// 4 3 5 9 6 2 1 7 8 10
// 4 3 5 9 6 2 7 1 8 10
// 4 3 5 9 6 2 7 8 1 10
// 4 3 5 9 6 2 7 8 10 1

//------- 2 회 --------
// 4 3 5 9 6 2 7 8 10 1
// 4 5 3 9 6 2 7 8 10 1
// 4 5 9 3 6 2 7 8 10 1
// 4 5 9 6 3 2 7 8 10 1
// 4 5 9 6 3 2 7 8 10 1
// 4 5 9 6 3 7 2 8 10 1
// 4 5 9 6 3 7 8 2 10 1
// 4 5 9 6 3 7 8 10 2 1
// 4 5 9 6 3 7 8 10 2 1
// ...


3. 장단점


장점

- 구현이 간단하다

단점

- 구조상 모든 요소들을 비교하고 이동시키기 때문에 효율적이지 못하다


일반적으로 자료의 이동 보다 교환 작업이 연산 작업이 복잡하기 때문에 구현의 단순성에도 불구하고

거의 쓰이지 않습니다.


정렬 알고리즘 테스트

종류

 최소

 최대

정렬시간 (10만개 테스트)

퀵 정렬

n log n

 n2

 32 ms

 삽입 정렬

n

n2

8142 ms

 선택 정렬

n2

n2

13499 ms

 버블 정렬

n2

n2

53488 ms



4. 관련글


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