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에 있다 :)
※ 사용 예시