C# StreamReader和EBCDIC的奇怪行为:为什么?

C# StreamReader和EBCDIC的奇怪行为:为什么?,c#,.net,character-encoding,streamreader,ebcdic,C#,.net,Character Encoding,Streamreader,Ebcdic,背景:我必须编写一个应用程序,它采用一个设计糟糕的EBCDIC文件,其中包含二进制数据,该文件使用ASCII行终止符,有时二进制数据碰巧包含ASCII CRLF,这会导致行分割错误。我需要采用这种旧的文件格式,并将CRLFs放在每条记录的末尾 似乎将StreamReader与IBM037编码一起使用会导致ReadLine()方法只读取\r作为行尾,而不是像我预期的那样读取\r\n,因此从ReadLine返回的每个字符串(在第一个之后)都以LF开头(ASCII中的0A) 再现问题的示例程序: us

背景:我必须编写一个应用程序,它采用一个设计糟糕的EBCDIC文件,其中包含二进制数据,该文件使用ASCII行终止符,有时二进制数据碰巧包含ASCII CRLF,这会导致行分割错误。我需要采用这种旧的文件格式,并将CRLFs放在每条记录的末尾

似乎将
StreamReader
IBM037
编码一起使用会导致
ReadLine()
方法只读取
\r
作为行尾,而不是像我预期的那样读取
\r\n
,因此从
ReadLine
返回的每个字符串(在第一个之后)都以LF开头(ASCII中的
0A

再现问题的示例程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

class Program
{
  static void Main(string[] args)
  {
    //generate example EBCDIC data
    List<byte> bytes = new List<byte>();
    Encoding EBCDIC = Encoding.GetEncoding("IBM037");
    bytes.AddRange(Encoding.Convert(Encoding.ASCII, EBCDIC, Encoding.ASCII.GetBytes("Some nice ascii text")));
    bytes.AddRange(new byte[] { (byte)'\r', (byte)'\n' });
    bytes.AddRange(Encoding.Convert(Encoding.ASCII, EBCDIC, Encoding.ASCII.GetBytes("Some more nice ascii text")));

    //read it using StreamReader
    using(MemoryStream ms = new MemoryStream(bytes.ToArray()))
    using (StreamReader reader = new StreamReader(ms, EBCDIC))
    {
      string line = string.Empty;
      while ((line = reader.ReadLine()) != null)
      {
        EBCDIC.GetBytes(line).ToList().ForEach(c => Console.Write(c));
        Console.WriteLine();
      }
    }
    Console.ReadLine();
  }
}
第二行开头的10不应该在那里,因为这是CRLF序列中的LF

我对
ReadLine
方法的理解是:

行号定义为后跟换行符(“\n”)、回车符(“\r”)或紧接换行符(“\r\n”)的回车符序列。返回的字符串不包含终止回车符或换行符

它没有说任何关于编码改变的内容,所以根据这一点,它应该在我的数据中读取完整的CRLF,而不仅仅是CR


更新:我已经解决了这个问题,并实现了自己的数据读取方法,但我的问题仍然是:为什么
ReadLine
没有按照tin上的说明做?

我在论坛上偶然发现了以下讨论:

根据“EBCDIC换行映射原因”一节 无效字符',靠近底部,IBM037有两个代码表示一行 提要0x15和0x25..NET似乎使用0x25:

字节[]字节= System.Text.Encoding.GetEncoding(“IBM037”).GetBytes(“hello\r\n”)

我看到另一个网页将其映射到0x15。难怪ASCII赢了

通过检查von EBCDIC 037,确认字节21(0x15)被定义为“换行符”,37(0x25)被定义为“换行符”,其中字节13(0x0D)是良好的旧“回车符”

因此ASCII不是EBCDIC 037的子集。

因此,您的测试代码有缺陷,因为您在执行以下操作时,将字节0x10和0x13添加到应为EBCDIC编码的字节:

bytes.AddRange(new byte[] { (byte)'\r', (byte)'\n' });
请尝试以下操作:

bytes.AddRange(Encoding.Convert(Encoding.ASCII, EBCDIC, Encoding.ASCII.GetBytes(
    "Some nice ascii text\r\nSome more nice ascii text")));
读取结果字节的工作方式与预期相同,因为“\r\n”被转换为EBCDIC的字节13和37。
ReadLine()
然后正确跳过字节37,即EBCDIC的“换行符”

这是因为
ReadLine()
比较的是Unicode字符,而不是字节。EBCDIC“换行符”(0x25)字节被解码为Unicode字符“\n”

结论
  • 一切正常
  • 并非所有编码都将ASCII作为子集
  • ReadLine()适用于Unicode字符,因此它一定是编码/解码问题
  • 检查原始问题的输入数据。它可能包含无效的换行符(对于EBCDIC)
  • 您将
    (byte)'\r'
    (byte)'\n'
    填充到一个流中,您告诉
    流阅读器
    是用EBCDIC编码的

    (byte)'\r'
    的值为0x0d,这恰好是ASCII和EBCDIC中的回车符

    (byte)'\n'
    的值为0x0a,这是ASCII中的换行符,但不是EBCDIC中的换行符

    如果您查看EBCDIC编码器类如何将值0x0a解码为.NET Unicode
    char
    类型,您会发现Unicode
    char
    的数值是142(或0x8e)。并且该字符不是换行符(我不知道为什么它被解码为142)

    您在第二行的开头看到“10”被打印出来,不是因为那里有换行符,而是因为值为142的字符被重新编码回值为10的EBCDIC字节(在子表达式
    EBCDIC.GetBytes(行)

    因此,简单地回答您的问题,
    ReadLine()
    只会看到回车符,而不会看到回车符后跟换行符

    while
    循环更改为如下所示:

    while ((line = reader.ReadLine()) != null)
    {
        line.ToList().ForEach(c => { Console.Write(c); Console.Write(" "); });
        Console.WriteLine();
        line.ToList().ForEach(c => { Console.Write(Convert.ToInt32(c)); Console.Write(" "); });
        Console.WriteLine();
        EBCDIC.GetBytes(line).ToList().ForEach(c => { Console.Write(c); Console.Write(" "); });
        Console.WriteLine();
        Console.WriteLine();
        Console.WriteLine();
    }
    
    您将获得第二行的以下输出,它将该行(从EBCDIC转换)显示为字符,这些字符的Unicode值,最后是转换回EBCDIC的字符值:

    ? S o m e   m o r e   n i c e   a s c i i   t e x t
    142 83 111 109 101 32 109 111 114 101 32 110 105 99 101 32 97 115 99 105 105 32 116 101 120 116
    10 226 150 148 133 64 148 150 153 133 64 149 137 131 133 64 129 162 131 137 137 64 163 133 167 163
    

    .不要使用
    ReadLine()
    如果你不同意的话。我知道文档是怎么说的,但我是说我的文件在换行后立即得到了回车符,所以它/不应该/像现在这样将它们拆分。然后显示所有相关的代码和测试数据,重现问题。将文件作为二进制文件读取并控制如何文本是您自己提取的?是使用ascii\r\n还是ebcdic???。ebcdic有一个新行字符,但没有换行符。新行通常映射到CR(\r)。换行符将被视为ebcdicI中的数据字符。我完全知道EBCDIC有自己的换行符,但这仍然不符合Microsoft提供的文档。他们说ReadLine将读取CR、LF或CRLF,他们没有说不同的编码会改变这一点。另外,在最后一点4,请重新阅读我问题的开头。是的。CR和LF是字符,而不是字节。ReadLine()对字符进行操作,而不关心这些字符曾经被编码过的字节。你是对的,我一直在思考ASCII。谢谢!@Logan,我很高兴听到这一点。如果我的解释有点笨拙的话,很抱歉。在考虑到这一点重新阅读后,第二次读起来更有意义:)我想你的解释更容易理解。Nic电子邮箱!
    ? S o m e   m o r e   n i c e   a s c i i   t e x t
    142 83 111 109 101 32 109 111 114 101 32 110 105 99 101 32 97 115 99 105 105 32 116 101 120 116
    10 226 150 148 133 64 148 150 153 133 64 149 137 131 133 64 129 162 131 137 137 64 163 133 167 163