첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
본 블로그는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 블로그 콘텐츠 향상을 위해 쓰여집니다.

728x90
반응형
728x170

TestProject.zip
0.01MB

▶ 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
반응형
그리드형
Posted by 사용자 icodebroker
TAG , ,

댓글을 달아 주세요