728x90
반응형
728x170
▶ StreamHelper.cs
using System.IO;
namespace TestProject
{
/// <summary>
/// 스트림 헬퍼
/// </summary>
public static class StreamHelper
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 읽기 - Read(stream, bufferByteArray, byteCountToRead)
/// <summary>
/// 읽기
/// </summary>
/// <param name="stream">스트림</param>
/// <param name="bufferByteArray">버퍼 바이트 배열</param>
/// <param name="byteCountToRead">읽을 바이트 카운트</param>
public static void Read(Stream stream, byte[] bufferByteArray, int byteCountToRead)
{
int totalByteCountRead = 0;
while(totalByteCountRead < byteCountToRead)
{
int byteCountRead = stream.Read(bufferByteArray, totalByteCountRead, byteCountToRead - totalByteCountRead);
if(byteCountRead == 0)
{
throw new EndOfStreamException
(
string.Format
(
"End of stream reached with {0} byte{1} left to read.",
byteCountToRead - totalByteCountRead,
byteCountToRead - totalByteCountRead == 1 ? "s" : string.Empty
)
);
}
totalByteCountRead += byteCountRead;
}
}
#endregion
}
}
728x90
▶ ReverseLineReader.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace TestProject
{
/// <summary>
/// 리버스 라인 리더
/// </summary>
public sealed class ReverseLineReader : IEnumerable<string>
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 디폴트 버퍼 크기
/// </summary>
private const int DefaultBufferSize = 4096;
/// <summary>
/// 소스 스트림 함수
/// </summary>
private readonly Func<Stream> sourceStreamFunction;
/// <summary>
/// 인코딩
/// </summary>
private readonly Encoding encoding;
/// <summary>
/// 버퍼 크기
/// </summary>
private readonly int bufferSize;
/// <summary>
/// 문자 시작 탐지 함수
/// </summary>
private Func<long, byte, bool> detectCharacterStartFunction;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - ReverseLineReader(sourceStreamFunction, encoding, bufferSize)
/// <summary>
/// 생성자
/// </summary>
/// <param name="sourceStreamFunction">소스 스트림 함수</param>
/// <param name="encoding">인코딩</param>
/// <param name="bufferSize">버퍼 크기</param>
public ReverseLineReader(Func<Stream> sourceStreamFunction, Encoding encoding, int bufferSize)
{
this.sourceStreamFunction = sourceStreamFunction;
this.encoding = encoding;
this.bufferSize = bufferSize;
if(encoding.IsSingleByte)
{
this.detectCharacterStartFunction = (position, data) => true;
}
else if(encoding is UnicodeEncoding)
{
this.detectCharacterStartFunction = (position, data) => (position & 1) == 0;
}
else if(encoding is UTF8Encoding)
{
this.detectCharacterStartFunction = (position, data) => (data & 0x80) == 0 || (data & 0x40) != 0;
}
else
{
throw new ArgumentException("Only single byte, UTF-8 and Unicode encodings are permitted");
}
}
#endregion
#region 생성자 - ReverseLineReader(sourceStreamFunction, encoding)
/// <summary>
/// 생성자
/// </summary>
/// <param name="sourceStreamFunction">소스 스트림 함수</param>
/// <param name="encoding">인코딩</param>
public ReverseLineReader(Func<Stream> sourceStreamFunction, Encoding encoding) : this(sourceStreamFunction, encoding, DefaultBufferSize)
{
}
#endregion
#region 생성자 - ReverseLineReader(sourceStreamFunction)
/// <summary>
/// 생성자
/// </summary>
/// <param name="sourceStreamFunction">소스 스트림 함수</param>
public ReverseLineReader(Func<Stream> sourceStreamFunction) : this(sourceStreamFunction, Encoding.UTF8)
{
}
#endregion
#region 생성자 - ReverseLineReader(filePath, encoding)
/// <summary>
/// 생성자
/// </summary>
/// <param name="filePath">파일 경로</param>
/// <param name="encoding">인코딩</param>
public ReverseLineReader(string filePath, Encoding encoding) : this(() => File.OpenRead(filePath), encoding)
{
}
#endregion
#region 생성자 - ReverseLineReader(filePath)
/// <summary>
/// 생성자
/// </summary>
/// <param name="filePath">파일 경로</param>
public ReverseLineReader(string filePath) : this(filePath, Encoding.UTF8)
{
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 열거자 구하기 - GetEnumerator()
/// <summary>
/// 열거자 구하기
/// </summary>
/// <returns>열거자 인터페이스</returns>
public IEnumerator<string> GetEnumerator()
{
Stream stream = this.sourceStreamFunction();
if(!stream.CanSeek)
{
stream.Dispose();
throw new NotSupportedException("Unable to seek within stream");
}
if(!stream.CanRead)
{
stream.Dispose();
throw new NotSupportedException("Unable to read within stream");
}
return GetEnumeratorCore(stream);
}
#endregion
#region 열거자 구하기 - IEnumerable.GetEnumerator()
/// <summary>
/// 열거자 구하기
/// </summary>
/// <returns></returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
#region 열거자 구하기 (코어) - GetEnumeratorCore(stream)
/// <summary>
/// 열거자 구하기 (코어)
/// </summary>
/// <param name="stream">스트림</param>
/// <returns>열거자 인터페이스</returns>
private IEnumerator<string> GetEnumeratorCore(Stream stream)
{
try
{
long position = stream.Length;
if(this.encoding is UnicodeEncoding && (position & 1) != 0)
{
throw new InvalidDataException("UTF-16 encoding provided, but stream has odd length.");
}
byte[] bufferByteArray = new byte[this.bufferSize + 2];
char[] characterByteArray = new char[this.encoding.GetMaxCharCount(bufferByteArray.Length)];
int leftOverData = 0;
string previousEnd = null;
bool firstYield = true;
bool swallowCarriageReturn = false;
while(position > 0)
{
int byteCountToRead = Math.Min(position > int.MaxValue ? this.bufferSize : (int)position, this.bufferSize);
position -= byteCountToRead;
stream.Position = position;
StreamHelper.Read(stream, bufferByteArray, byteCountToRead);
if(leftOverData > 0 && byteCountToRead != this.bufferSize)
{
Array.Copy(bufferByteArray, this.bufferSize, bufferByteArray, byteCountToRead, leftOverData);
}
byteCountToRead += leftOverData;
int firstCharPosition = 0;
while(!this.detectCharacterStartFunction(position + firstCharPosition, bufferByteArray[firstCharPosition]))
{
firstCharPosition++;
if(firstCharPosition == 3 || firstCharPosition == byteCountToRead)
{
throw new InvalidDataException("Invalid UTF-8 data");
}
}
leftOverData = firstCharPosition;
int characterCountRead = this.encoding.GetChars
(
bufferByteArray,
firstCharPosition,
byteCountToRead - firstCharPosition,
characterByteArray,
0
);
int endExclusive = characterCountRead;
for(int i = characterCountRead - 1; i >= 0; i--)
{
char lookingAtCharacter = characterByteArray[i];
if(swallowCarriageReturn)
{
swallowCarriageReturn = false;
if(lookingAtCharacter == '\r')
{
endExclusive--;
continue;
}
}
if(lookingAtCharacter != '\n' && lookingAtCharacter != '\r')
{
continue;
}
if(lookingAtCharacter == '\n')
{
swallowCarriageReturn = true;
}
int start = i + 1;
string bufferContents = new string(characterByteArray, start, endExclusive - start);
endExclusive = i;
string stringToYield = previousEnd == null ? bufferContents : bufferContents + previousEnd;
if(!firstYield || stringToYield.Length != 0)
{
yield return stringToYield;
}
firstYield = false;
previousEnd = null;
}
previousEnd = endExclusive == 0 ? null : (new string(characterByteArray, 0, endExclusive) + previousEnd);
if(leftOverData != 0)
{
Buffer.BlockCopy(bufferByteArray, 0, bufferByteArray, this.bufferSize, leftOverData);
}
}
if(leftOverData != 0)
{
throw new InvalidDataException("Invalid UTF-8 data at start of stream");
}
if(firstYield && string.IsNullOrEmpty(previousEnd))
{
yield break;
}
yield return previousEnd ?? "";
}
finally
{
stream.Dispose();
}
}
#endregion
}
}
300x250
▶ Program.cs
using System;
using System.IO;
namespace TestProject
{
/// <summary>
/// 프로그램
/// </summary>
class Program
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Private
#region 프로그램 시작하기 - Main()
/// <summary>
/// 프로그램 시작하기
/// </summary>
private static void Main()
{
string data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string filePath = "d:\\sample.dat";
using(StreamWriter writer = File.CreateText(filePath))
{
for(int i = 0; i < 100000; i++)
{
writer.WriteLine($"{i + 1}, {data}");
}
}
ReverseLineReader reader = new ReverseLineReader(filePath);
foreach(string line in reader)
{
Console.WriteLine(line);
break;
}
}
#endregion
}
}
728x90
반응형
그리드형(광고전용)
'C# > Common' 카테고리의 다른 글
[C#/COMMON] Process 클래스 : PowerShell 스크립트 파일 실행하기 (0) | 2021.02.22 |
---|---|
[C#/COMMON] Process 클래스 : PowerShell 스크립트 실행하기 (0) | 2021.02.22 |
[C#/COMMON] Runspace 클래스 : PowerShell 스크립트 실행하기 (0) | 2021.02.22 |
[C#/COMMON] 극 좌표(Polar Coordinates) 사용하기 (0) | 2021.02.14 |
[C#/COMMON] 3자 컴포넌트 어셈블리 서명하기 (0) | 2021.02.08 |
[C#/COMMON] 모니터 수 구하기 (0) | 2021.02.08 |
[C#/COMMON] Enum 클래스 : GetValues 정적 메소드를 사용해 열거형 값 배열 구하기 (0) | 2021.02.07 |
[C#/COMMON] WeakReference 클래스 : 약한 참조 사용하기 (0) | 2021.02.07 |
[C#/COMMON] 모니터 정보 구하기 (0) | 2021.02.06 |
[C#/COMMON] 모니터 정보 구하기 (0) | 2021.02.06 |
댓글을 달아 주세요