원본 URL: http://www.blogwind.com/Wuvist/42999.shtml
.Net 프레임워크에서는 StreamReader가 사용하는 인코딩을 생성자에서 지정해야 하며 중간에 전혀 변경할 수 없습니다.
일반적인 상황에서는 아무런 문제가 발생하지 않습니다. 일반적으로 하드 디스크에서 파일을 읽는 경우 단일 파일 내의 인코딩은 일반적으로 균일합니다. 읽기 오류가 발견되더라도 StreamReader를 닫고 새 인코딩을 사용하여 읽기를 다시 시작할 수 있습니다.
최근에 인코딩을 수정해야 할 필요성을 느꼈는데 프로그램이 다시 읽을 수 있는 기회를 끄지 않았습니다. 제가 사용하고 있는 StreamReader의 BaseStream은 네트워크 스트림이기 때문에 닫을 수 없습니다. 하지만 네트워크 스트림이 전달한 것들이 GB2312, Big5, UTF8, ISO-8859-1 등 다른 인코딩을 포함하고 있을 수 있습니다. .. 인코딩 정보를 먼저 얻은 다음 특정 내용을 읽습니다. 그러나 처음에 사용된 Stream Reader 인코딩이 잘못되면 읽은 내용을 결코 복구할 수 없습니다....단어가 손실됩니다
. 다시 얻을 수 없습니다... 정보를 인코딩한 후 특정 콘텐츠가 원래 스트림 리더에 의해 버퍼링되었기 때문에 새 스트림 리더를 다시 만듭니다...
유일한 해결책은 CurrentEncoding 속성...
처음부터 모두 작성 처음에는 모노의 소스 코드를 얻고 모노의 스트림 리더 구현 코드를 수정하는 것이 매우 비실용적이었습니다.
Stream Reader는 실제로 매우 간단합니다. 내부에는 두 개의 버퍼가 있습니다. 하나는 입력 버퍼이고 다른 하나는 기본 스트림에서 읽은 원본 데이터를 캐시하는 데 사용되고 후자는 항목을 캐시하는 데 사용됩니다. 원본 데이터에 따라 디코딩됩니다... ...모노 구현에서 ReadBuffer 메서드를 이해한다면 CurrentEncoding을 동적으로 수정하는 것은 그리 어렵지 않습니다...
제가 처리해야 할 네트워크 프로토콜은 라인 프로토콜입니다. ... 프로그램에서 StreamReader의 Readline 메서드만 호출했지만 완전히 Read의 두 가지 메서드를 사용하지 않아서 인코딩을 동적으로 수정하는 것이 훨씬 쉬워졌습니다...
내가 하는 일은 매번 Readline을 호출하면 디코딩된 버퍼의 커서(pos)를 이동할 뿐만 아니라 입력 버퍼(pos_input)에서도 새 커서를 이동할 수 있습니다. 방법은 매우 간단합니다. Readline 메서드는 커서를 이동하기 위해 FindNextEOL을 호출해야 합니다. 개행 기호를 찾습니다... FindNextEOL 메서드에 한 줄을 더 추가합니다.
int FindNextEOL()
{
FindNextInputEOL();
....
새로운 함수 FindNextInputEOL은 전자가 입력 버퍼를 처리하는 반면 후자는 디코딩된 버퍼를 처리한다는 점을 제외하면 FindNextEOL의 완전한 복제본입니다...
이런 식으로 각 Readline 이후에 입력 버퍼가 상위 계층에서 디코딩되지 않았습니다. 읽은 원본 데이터는 무엇입니까?
그런 다음 CurrentEncoding 특성에 Set 메서드를 추가합니다.
세트
{
인코딩=값;
디코더 = 인코딩.GetDecoder();
decoded_count = pos + decoder.GetChars (input_buffer, pos_input, cbEncoded, pos_input, decoded_buffer, pos);
}
새로운 인코딩을 설정할 때 프로그램은 입력 버퍼(pos_input)의 커서에 따라 읽혀지지 않은 원본 데이터를 다시 디코딩하고, 디코딩된 버퍼의 내용을 대체합니다.
그러면 작업이 완료됩니다... Readline 메서드를 수정할 필요조차 없습니다... cbEncoded 변수를 전역 변수에 넣는 것을 제외하면...
그러나 이 수정으로 인해 Read의 두 메서드를 완전히 사용할 수 없게 됩니다. ... 일단 호출되면... 입력 버퍼의 두 커서와 디코딩된 버퍼가 동기화되지 않게 됩니다... 전체 코드가 아래에 첨부되어 있습니다. 누군가가 Read의 두 가지 방법을 알아내는 데 도움을 줄 수 있기를 바랍니다. ... 미리 감사드립니다 ...
/
// System.IO.StreamReader.cs
//
// 작가:
// 디트마르 마우러( [email protected] )
// 미겔 데 이카사 ( [email protected] )
//
// (다) (주)자이미안 http://www.ximian.com
// 저작권 (C) 2004 Novell ( http://www.novell.com )
//
//
// 저작권 (C) 2004 Novell, Inc ( http://www.novell.com )
//
// 이에 따라 다음을 획득하는 모든 사람에게 무료로 허가가 부여됩니다.
// 이 소프트웨어의 사본 및 관련 문서 파일(
// "소프트웨어"), 다음을 포함하여 제한 없이 소프트웨어를 취급합니다.
// 제한 없이 사용, 복사, 수정, 병합, 게시,
// 소프트웨어 사본을 배포, 재라이센스 부여 및/또는 판매합니다.
// 소프트웨어를 제공받은 사람이 그렇게 하도록 허용합니다.
// 다음 조건:
//
// 위의 저작권 고지와 본 허가 고지는 다음과 같습니다.
// 소프트웨어의 모든 사본 또는 상당 부분에 포함되어 있습니다.
//
// 소프트웨어는 어떠한 종류의 보증도 없이 "있는 그대로" 제공됩니다.
// 다음의 보증을 포함하되 이에 국한되지 않는 명시적 또는 묵시적
// 상품성, 특정 목적에의 적합성 및
// 어떠한 경우에도 저작권 침해가 발생하지 않습니다.
// 소송 여부에 관계없이 모든 청구, 손해 또는 기타 책임에 대해 책임을 집니다.
// 계약, 불법 행위 또는 기타 다른 방식으로 인해 발생하거나 연결로 인해 발생하는 경우
// 소프트웨어나 소프트웨어의 사용 또는 기타 거래와 관련하여.
//
시스템 사용;
System.Text 사용;
System.Runtime.InteropServices
네임스페이스 System.IO
사용
{
[직렬화 가능]
공개 클래스 DynamicStreamReader : TextReader
{
const int DefaultBufferSize = 1024;
const int DefaultFileBufferSize = 4096;
128
;
//입력 버퍼
//
바이트 [] 입력_버퍼;
//
// 위 입력 버퍼에서 디코딩된 버퍼
//
char [] decoded_buffer;
//
// decoded_buffer의 디코딩된 바이트입니다.
//
int decoded_count
;
// decoded_buffer의 현재 위치
//
정수 위치
//
// input_buffer의 현재 위치
//
int pos_input
;
// 우리가 사용하는 버퍼 크기
//
int buffer_size;
int do_checks;
인코딩 인코딩;
디코더
스트림 base_stream;
bool mayBlock;
StringBuilder line_builder;
비공개 클래스 NullStreamReader : DynamicStreamReader
{
공개 재정의 int Peek()
{
-1을 반환합니다.
}
공개 재정의 int 읽기()
{
-1을 반환합니다.
}
공개 재정의 int 읽기([In, Out] char[] 버퍼, int 인덱스, int 개수)
{
0을 반환합니다.
}
공개 재정의 문자열 ReadLine()
{
null을 반환;
}
공개 재정의 문자열 ReadToEnd()
{
String.Empty를 반환합니다.
}
공개 재정의 스트림 BaseStream
{
get { 반환 Stream.Null }
}
공개 재정의 인코딩 CurrentEncoding
{
get { return Encoding.Unicode };
}
}
공개 새 정적 읽기 전용 DynamicStreamReader Null = (DynamicStreamReader)(new NullStreamReader());
내부 DynamicStreamReader() {}
공개 DynamicStreamReader(스트림 스트림)
: this (스트림, Encoding.UTF8, true, DefaultBufferSize) { }
public DynamicStreamReader(스트림 스트림, bool discover_encoding_from_bytemarks)
: this (스트림, Encoding.UTF8, discover_encoding_from_bytemarks, DefaultBufferSize) { }
public DynamicStreamReader(스트림 스트림, 인코딩 인코딩)
: this (스트림, 인코딩, true, DefaultBufferSize) { }
public DynamicStreamReader(스트림 스트림, 인코딩 인코딩, bool discover_encoding_from_bytemarks)
: 이것(스트림, 인코딩, discover_encoding_from_bytemarks, DefaultBufferSize) { }
공개 DynamicStreamReader(스트림 스트림, 인코딩 인코딩, bool discover_encoding_from_bytemarks, int buffer_size)
{
초기화(스트림, 인코딩, discover_encoding_from_bytemarks, buffer_size);
}
공개 DynamicStreamReader(문자열 경로)
: this (경로, Encoding.UTF8, true, DefaultFileBufferSize) { }
public DynamicStreamReader(문자열 경로, bool discover_encoding_from_bytemarks)
: this (경로, Encoding.UTF8, discover_encoding_from_bytemarks, DefaultFileBufferSize) { }
public DynamicStreamReader(문자열 경로, 인코딩 인코딩)
: this (경로, 인코딩, true, DefaultFileBufferSize) { }
public DynamicStreamReader(문자열 경로, 인코딩 인코딩, bool discover_encoding_from_bytemarks)
: 이 (경로, 인코딩, discover_encoding_from_bytemarks, DefaultFileBufferSize) { }
공개 DynamicStreamReader(문자열 경로, 인코딩 인코딩, bool discover_encoding_from_bytemarks, int buffer_size)
{
if (null == 경로)
throw new ArgumentNullException("경로");
if(String.Empty == 경로)
throw new ArgumentException("빈 경로는 허용되지 않습니다.");
if (path.IndexOfAny (Path.InvalidPathChars) != -1)
throw new ArgumentException("경로에 잘못된 문자가 포함되어 있습니다.");
if (null == 인코딩)
새로운 ArgumentNullException("인코딩")을 발생시킵니다.
if (buffer_size <= 0)
throw new ArgumentOutOfRangeException("buffer_size", "버퍼의 최소 크기는 양수여야 합니다.")
string DirName = Path.GetDirectoryName(path);
if (DirName != String.Empty && !Directory.Exists(DirName))
새로운 DirectoryNotFoundException 발생("Directory '" + DirName + "'를 찾을 수 없습니다.");
if (!File.Exists(경로))
throw new FileNotFoundException("파일을 찾을 수 없습니다.", path)
Stream stream = (Stream) File.OpenRead (path);
초기화(스트림, 인코딩, discover_encoding_from_bytemarks, buffer_size);
}
내부 무효 초기화(스트림 스트림, 인코딩 인코딩, bool discover_encoding_from_bytemarks, int buffer_size)
{
if (널 == 스트림)
새로운 ArgumentNullException("스트림")을 발생시킵니다.
if (null == 인코딩)
새로운 ArgumentNullException("인코딩")을 발생시킵니다.
if (!stream.CanRead)
throw new ArgumentException("스트림을 읽을 수 없습니다.");
if (buffer_size <= 0)
throw new ArgumentOutOfRangeException("buffer_size", "버퍼의 최소 크기는 양수여야 합니다.")
if(buffer_size <MinimumBufferSize)
buffer_size = 최소버퍼사이즈;
base_stream = 스트림;
input_buffer = 새 바이트 [buffer_size];
this.buffer_size = 버퍼_크기;
this.encoding = 인코딩;
디코더 = 인코딩.GetDecoder();
바이트[] 프리앰블 = 인코딩.GetPreamble();
do_checks = discover_encoding_from_bytemarks 1 : 0;
do_checks += (preamble.Length == 0) 0 : 2;
decoded_buffer = 새 문자 [encoding.GetMaxCharCount (buffer_size)];
decoded_count = 0;
위치 = 0;
pos_input =0;
}
공개 가상 스트림 BaseStream
{
얻다
{
base_stream을 반환합니다.
}
}
공개 가상 인코딩 CurrentEncoding
{
얻다
{
if (인코딩 == null)
새로운 예외 발생();
인코딩 반환;
}
세트
{
인코딩=값;
디코더 = 인코딩.GetDecoder();
decoded_count = pos + decoder.GetChars (input_buffer, pos_input, cbEncoded - pos_input, decoded_buffer, pos);
//DiscardBufferedData();
}
}
공개 재정의 무효 닫기()
{
폐기(true);
}
protected override void Dispose(부울 처리)
{
if (&& base_stream != null 처리)
base_stream.Close();
input_buffer = null;
decoded_buffer = null;
인코딩 = null;
디코더 = null;
base_stream = null;
base.Dispose(폐기);
}
//
// 인코딩 자동 감지 및 건너뛰기 기능 제공
// 스트림 시작 부분에 바이트 표시가 있습니다.
//
int DoChecks(정수 개수)
{
if ((do_checks & 2) == 2)
{
바이트 [] 프리앰블 = 인코딩.GetPreamble ();
int c = 프리앰블.길이;
if (개수 >= c)
{
나는 int;
for (i = 0; i < c; i++)
if (input_buffer [i] != 프리앰블 [i])
휴식;
if (i == c)
내가 반환;
}
}
if ((do_checks & 1) == 1)
{
if (개수 < 2)
0을 반환합니다.
(input_buffer [0] == 0xfe && input_buffer [1] == 0xff)
{
this.encoding = Encoding.BigEndianUnicode;
2를 반환합니다.
}
if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe)
{
this.encoding = 인코딩.유니코드;
2를 반환합니다.
}
if (개수 < 3)
0을 반환합니다.
if (input_buffer [0] == 0xef && input_buffer [1] == 0xbb && input_buffer [2] == 0xbf)
{
this.encoding = 인코딩.UTF8;
3을 반환합니다.
}
}
0을 반환합니다.
}
공개 무효 DiscardBufferedData()
{
pos = decoded_count = 0;
메이블록 = 거짓;
// 디코더의 내부 상태도 폐기합니다.
디코더 = 인코딩.GetDecoder();
}
int cbEncoded;
int pars_start;
// 버퍼가 비어 있으면 다시 채웁니다.
개인용 int ReadBuffer()
{
위치 = 0;
pos_input = 0;
cbEncoded = 0;
// 디코더가 문자를 제공할 때까지 계속 반복합니다.
decoded_count = 0;
구문 분석 시작 = 0;
하다
{
cbEncoded = base_stream.Read(input_buffer, 0, buffer_size);
if (cbEncoded == 0)
0을 반환합니다.
mayBlock = (cbEncoded < buffer_size);
if (do_checks > 0)
{
이전 인코딩 = 인코딩;
parse_start = DoChecks(cbEncoded);
if (이전 != 인코딩)
{
디코더 = 인코딩.GetDecoder();
}
do_checks = 0;
cbEncoded -= 구문 분석 시작;
}
decoded_count += decoder.GetChars (input_buffer, parse_start, cbEncoded, decoded_buffer, 0);
구문 분석 시작 = 0;
} while (decoded_count == 0);
return decoded_count;
}
공개 재정의 int Peek()
{
if (base_stream == null)
throw new ObjectDisposedException("StreamReader", "닫힌 StreamReader에서 읽을 수 없습니다.");
if (pos >= decoded_count && (mayBlock || ReadBuffer () == 0))
return -1;
return decoded_buffer [pos];
}
공개 재정의 int 읽기()
{
throw new Exception("동적 리더가 읽을 수 없습니다!");
}
공개 재정의 int 읽기([In, Out] char[] dest_buffer, int index, int count)
{
throw new Exception("동적 리더가 읽을 수 없습니다!");
}
bool 발견CR_input;
int FindNextInputEOL()
{
char c = '