2

背景:我必须编写一个应用程序,该应用程序采用设计不佳的 EBCDIC 文件,其中包含使用 ASCII 行终止符的二进制数据,有时二进制数据恰好包含 ASCII CRLF,这会导致行拆分不正确。我需要采用这种旧文件格式并在每条记录的末尾删除 CRLF。

似乎使用StreamReaderwithIBM037编码会导致该ReadLine()方法仅读取\r为行尾而不是\r\n我所期望的那样,因此我从第一个字符串(在第一个字符串之后)返回的每个字符串都ReadLine以 LF(0AASCII 格式)开头。

重现问题的示例程序:

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();
  }
}

输出应如下所示:

226150148133641491371311336412916213113713764163133167163
1022615014813364148150153133641491371311336412916213113713764163133167163

第二行开头的 10 不应该在那里,因为那是 CRLF 序列中的 LF。

我对ReadLine方法的理解是:

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

它没有说明任何关于编码改变的内容,因此它应该读取我数据中的完整 CRLF 而不仅仅是 CR。

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

4

2 回答 2

2

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

根据此文档,“EBCDIC lineFeed 映射导致无效字符”部分靠近底部,IBM037 有两个换行代码,0x15 和 0x25。.NET 似乎使用 0x25:

byte[] bytes = System.Text.Encoding.GetEncoding("IBM037").GetBytes("hello\r\n");

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

检查Wikipedia 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 "NewLine" (0x25) 字节被解码为 Unicode 字符 '\n'。

结论

  1. 一切正常。
  2. 并非所有编码都将 ASCII 作为子集。
  3. ReadLine() 适用于 Unicode 字符,因此它一定是编码/解码问题。
  4. 检查原始问题的输入数据。它可能包含无效的(对于 EBCDIC)换行符。
于 2015-02-19T07:07:38.513 回答
2

(byte)'\r'您将 a and填充(byte)'\n'到您告诉StreamReader它以 EBCDIC 编码的流中。

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

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

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

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

因此,要非常简单地回答您的问题,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
于 2015-02-19T08:50:25.967 回答