내가만든/라인들2016. 2. 28. 17:19

C#

로그 파일이 일정크기(2GB)가 넘어가면 일반적인 텍스트 툴로 파일으 오픈할 수 없다..ㅠ

해서 텍스트 파일을 크기/라인 수로 분할하는 프로그램을 제작.

큰 파일은 시간이 오래 걸리기 떄문에 멀티스레드를 이용한 병렬처리를 적용해 보았다(Parallel.For)

프로세스 사용량이 전 코어 100% 사용하게 되어 일반적인 싱글 스레드 로직보다 초대 3배가량 나은 성능을 보여준다 :)

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;

namespace DistributeTextFile.FileManager
{
    // Declare ENUM Values
    //*---------------------------------*
    enum E_VALUE
    {
        KBYTE = 1024,
        MBYTE = KBYTE * KBYTE,
        READ_MAX = 100000,
    };

    enum E_TYPE
    {
        LINE = 0,
        SIZE ,
        _MAX_,
        DEFAULT = LINE,
    };
    //*---------------------------------*

    struct StreamData
    {
        public StreamReader SReader;
        public string szFilPath;
        public int nLines;
    };

    class FileManager : Util.Sigleton.Singleton<FileManager>
    {           
        public FileManager()
        {           
            this.Init();
        }
        ~FileManager() { }

        private void Init()
        {
            this.eDistributeType = E_TYPE.DEFAULT;
            this.FileListContainer = new List<string>();

            this.szDistributedTypeName = new string[(int)E_TYPE._MAX_];
            this.szDistributedTypeName[(int)E_TYPE.LINE] = "라인 수";
            this.szDistributedTypeName[(int)E_TYPE.SIZE] = "파일 크기(MB)";

            this.nDistributedTypeValue = new int[(int)E_TYPE._MAX_];
            this.nDistributedTypeValue[(int)E_TYPE.LINE] = 1000000;
            this.nDistributedTypeValue[(int)E_TYPE.SIZE] = 100;
        }

        private bool ReadFile( string szFilePath )
        {
            if (!File.Exists( szFilePath ))
            {
                return false;
            }          

            return true;
        }
       
        public void DistributeFiles()
        {
            int szMainProccessID = Thread.CurrentThread.ManagedThreadId;
            string szOutFolder = "\\Result";
            string szReadFailList = "";

            UInt64 nTotalBuffSize = 0;
            UInt64 nCurReadSize = 0;

            StreamReader []_SR = new StreamReader[this.FileListContainer.Count];
           
            List< StreamData > FileStreamContainer = new List<StreamData>();

            object objMutex = new object();
            Parallel.For(0, this.FileListContainer.Count, (nCnt) =>
            {
                if (false == this.ReadFile(this.FileListContainer[nCnt]))
                {
                    szReadFailList += this.FileListContainer[nCnt] + "; ";
                    return;
                }

                _SR[nCnt] = new StreamReader(this.FileListContainer[nCnt], Encoding.Default);
                nTotalBuffSize += (UInt64)_SR[nCnt].BaseStream.Length;

                int nLineNum = 0;

                /* 단순 라인 수 읽기는 싱글 스레드 로직이 2~3배 가량 더 빠름 */
                nLineNum = File.ReadLines(this.FileListContainer[nCnt]).Count();
                _SR[nCnt].BaseStream.Position = 0;

                StreamData stStreamData = new StreamData();
                stStreamData.nLines = nLineNum;
                stStreamData.SReader = _SR[nCnt];
                stStreamData.szFilPath = this.FileListContainer[nCnt];

                lock (objMutex)
                {
                    FileStreamContainer.Add(stStreamData);
                }
            });

            object objProgressBarMutex = new object();
            Parallel.For(0, FileStreamContainer.Count, (idx) =>
            {
                /// ---폴더 생성-------------------------------------------------
                string szPath = FileStreamContainer[idx].szFilPath;   // 파일 경로
                string szFileName = Path.GetFileName(szPath);             // 파일 이름
                string szExtName = Path.GetExtension(szFileName);        // 확장자
                szFileName = szFileName.Split('.')[0];

                string szOutPutPath = Directory.GetCurrentDirectory() + szOutFolder + "\\" + szFileName + "\\";
                Directory.CreateDirectory(szOutPutPath);
                /// ------------------------------------------------------------

                /// ---스레드 당 생성 되는 지역 변수----------------------------
                Dictionary<int, string> ReadFileContainer = new Dictionary<int, string>(); // 스레드 별로 사용할 파일(컨테이너)

                int nRemainReadLine = FileStreamContainer[idx].nLines;  // 이 스레드에서 처리해야할 총 라인 수
                int nTotalWriteLine = 0; // 현재 총 쓰기 완료 라인 수
                int nCurWriteLine = 0; // 현재 사용중인 파일에서 쓰기 완료한 라인 수
                int nFileCounts = getFileCounts(FileStreamContainer[idx].nLines); // 분할될 파일 갯수
                bool bReadStream = true; // 원본 파일(스트림) 읽기 여부
                int nReadLineNum = 0;
                /// ------------------------------------------------------------
                for (int jdx = 0; jdx < nFileCounts; jdx++)
                {
                    /// ---파일 생성-------------------------------------------------
                    FileStream FS = File.Create(szOutPutPath + szFileName + "[" + jdx + "]" + szExtName);
                    /// -------------------------------------------------------------

                    object objReadMutex = new object();
                    bool bIsWriteLineRemained = true; // 써야할 내용이 남아 있을때
                    int nCurFileTotalLines = 0; // 현재 파일에 쓰여진 라인 수

                    do
                    {
                        if (true == bReadStream)
                        {
                            /// 원본 파일에서 읽어들이기 ( 최대 100000 라인 )
                            ReadFileContainer.Clear();
                            int nLoopCnt = ((int)E_VALUE.READ_MAX < nRemainReadLine) ? (int)E_VALUE.READ_MAX : nRemainReadLine;
                            nCurWriteLine = 0; // 현재 사용중인 파일에서 쓰기 완료한 라인 수 초기화
                            Parallel.For(0, nLoopCnt, (nCurLine) =>
                            {
                                lock (objReadMutex)
                                {
                                    try
                                    {
                                        string szTmp = FileStreamContainer[idx].SReader.ReadLine();
                                        ReadFileContainer.Add(nReadLineNum++, szTmp + "\n");
                                    }
                                    catch (Exception e) { }

                                }
                            });
                        }

                        int nWriteLine = 0; // 이 반복문 내에서 쓰기 작업 완료한 횟수
                        for (int kdx = nCurWriteLine; kdx < ReadFileContainer.Count; kdx++)
                        {
                            try
                            {
                                byte[] byByteBuff = Encoding.Default.GetBytes(ReadFileContainer[kdx + nTotalWriteLine]);
                                FS.Write(byByteBuff, 0, ReadFileContainer[kdx + nTotalWriteLine].Length);

                                lock (objProgressBarMutex)
                                {
                                    // ---- Progress Bar -----------------------
                                    nCurReadSize += (UInt64)byByteBuff.Length;
                                    int nProgress = (int)(((float)nCurReadSize / (float)nTotalBuffSize) * 100);
                                    if ( Thread.CurrentThread.ManagedThreadId == szMainProccessID)
                                    {
                                        MainForm.ProgressBar_Set(nProgress);
                                    }                                   
                                    // ------------------------------------------
                                }

                            }
                            catch (Exception e)
                            {
                                nWriteLine++;
                                nCurFileTotalLines++;
                                break;
                            }
                            nWriteLine++;
                            nCurFileTotalLines++;
                            //if (nWriteLine >= this.nDistributedTypeValue[(int)this.eDistributeType])
                            //if (nCurWriteLine >= this.nDistributedTypeValue[(int)this.eDistributeType])
                            if (nCurFileTotalLines >= this.nDistributedTypeValue[(int)this.eDistributeType])
                            {
                                break;
                            }
                        }
                        /* 1. 파일 분할 조건에 따라 꽉 찼을 경우(읽어 들이는 작업은 No, 새로 파일 여는 작업 Yes)
                         * 2. 시스템 제한(10만줄)에 해당 되어 읽어 들이는 작업 재수행(읽어들이는 작업 Yes, 새로 파일 여는 작업 No) */
                        nCurWriteLine += nWriteLine;
                        nTotalWriteLine += nWriteLine;
                        nRemainReadLine -= nWriteLine; // 이 스레드(파일)의 남은 작업량을 갱신 해준다
                        if (0 >= nRemainReadLine) // 더 이상 읽어야 할 라인이 존재하지 않는다면 종료
                        {
                            bIsWriteLineRemained = false;
                            FS.Close();
                        }
                        else // 아직 읽어야 할 라인이 존재한다
                        {
                            // 1. 파일 분할 조건에 해당하지 않으면( 컨테이너 내용은 다썼는데, 파일 제한에 해당 되지 않음) 컨테이너를 다시 채우고 쓰기작업
                            // do~while문 탈출 하지 않고 재수행
                            if (nCurWriteLine == ReadFileContainer.Count && nTotalWriteLine < this.nDistributedTypeValue[(int)this.eDistributeType])
                            {
                                bReadStream = true;
                                continue;
                            }
                            // 2. 파일 분할 조건에 해당하고, 컨테이너 내용도 다썼을 경우
                            // do~while문 탈출
                            else if (nCurWriteLine == ReadFileContainer.Count && nTotalWriteLine >= this.nDistributedTypeValue[(int)this.eDistributeType])
                            {
                                bReadStream = true;
                                bIsWriteLineRemained = false;
                                FS.Close();
                            }
                            // 3. 파일 분할 조건에 해당하면( 컨테이너 내용 다 못썼는데, 파일 제한에 해당됨) 파일을 새로 생성(컨테이너 비우기는 X)
                            // do~while문 탈출
                            else
                            {
                                bReadStream = false;
                                bIsWriteLineRemained = false;
                                FS.Close();
                            }
                        }

                    } while (bIsWriteLineRemained);
                }
            }
            );

            OpenFolder(Directory.GetCurrentDirectory() + szOutFolder);
            MainForm.ProgressBar_Init();

            // =======================================================================
            // =======================================================================
        }

        private int getFileCounts(int nLineNums)
        {
            int nFileNum = 0;

            nFileNum = (nLineNums / this.nDistributedTypeValue[(int)this.eDistributeType]);
            if (0 < nLineNums % this.nDistributedTypeValue[(int)this.eDistributeType]) { nFileNum++; }

            return nFileNum;
        }
        private bool checkConditions( UInt64 dwReadValue )
        {
            int nType = (int)this.eDistributeType;
            UInt64 dwCondition = (UInt64) this.nDistributedTypeValue[nType];
            bool retValue = true;
            switch (this.eDistributeType)
            {
                case E_TYPE.LINE:
                default:
                    {
                        if (dwCondition <= dwReadValue)
                        {
                            retValue = false;
                        }
                    }
                    break;
                case E_TYPE.SIZE:
                    {
                        if (dwCondition * (UInt64)E_VALUE.MBYTE <= dwReadValue)
                        {
                            retValue = false;
                        }
                    }
                    break;
            }
            return retValue;
        }
        public void OpenFolder( string szFolderPath )
        {
            System.Diagnostics.Process.Start("explorer.exe", szFolderPath);
        }

        public void SetFilePath(string szFilePath)
        {
            this.szFilePath = szFilePath;
        }
        public void SetFilePath(string []szFilePath)
        {
            this.FileListContainer.Clear();
            this.FileListContainer.AddRange(szFilePath);
        }
        public String GetFilePath()
        {
            return this.szFilePath;
        }

        public void SetType(int nType)
        {
            this.eDistributeType = (E_TYPE)nType;
        }
        public new int GetType()
        {
            E_TYPE eIdx = this.eDistributeType;
            return (int)eIdx;
        }
        public String[] GetTypes()
        {
            return this.szDistributedTypeName;
        }

        public void SetTypeValue(int nValue)
        {
            this.nDistributedTypeValue[(int)this.eDistributeType] = nValue;
        }
        public int GetTypeValue()
        {
            return this.nDistributedTypeValue[(int)this.eDistributeType];
        }

        public void SetMainForm(DistributeTextFile.Form1 stMainForm) { this.MainForm = stMainForm; }

        private string szFilePath;
        private List<string> FileListContainer;
        private E_TYPE eDistributeType;
        private String []szDistributedTypeName;
        private int []nDistributedTypeValue;
               
        ///  Main Form
        private DistributeTextFile.Form1 MainForm;
    }
}

※ 성능을 더 개선한 버전(컨테이너 사용하지 않음)도 있는데, 모든 소스는 SVN에 있다 :)

※ 사용 예시

 

 

 

 

'내가만든 > 라인들' 카테고리의 다른 글

파일분할_파일  (0) 2016.03.02
스케쥴링_파일  (0) 2016.03.02
[이벤트] 카카오톡 욕설 집계 스크립트  (0) 2016.02.28
[이벤트] 크리스마스 트리  (0) 2016.02.28
[메모리풀] 헤더 파일  (0) 2016.02.28
Posted by 비엔나햄