StreamReader 和 EBCDIC 的奇怪行为:为什么?
Strange behaviour with StreamReader and EBCDIC: Why?
背景:我必须编写一个应用程序,该应用程序采用设计不佳的 EBCDIC 文件,其中包含使用 ASCII 行终止符的二进制数据,有时二进制数据恰好包含 ASCII CRLF,这会导致行分割不正确。我需要采用这种旧文件格式并在每条记录的末尾删除 CRLF。
似乎使用带有 IBM037
编码的 StreamReader
会导致 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();
}
}
输出应该如下:
226150148133641491371311336412916213113713764163133167163
1022615014813364148150153133641491371311336412916213113713764163133167163
第二行开头的 10 不应该出现,因为那是 CRLF 序列中的 LF。
我对 ReadLine
方法的理解是:
A line is defined as a sequence of characters followed by a line feed ("\n"), a carriage return ("\r"), or a carriage return immediately followed by a line feed ("\r\n"). The string that is returned does not contain the terminating carriage return or line feed. Source
它没有说任何关于编码改变的事情,所以根据它应该读取我数据中的完整 CRLF 而不仅仅是 CR。
更新:我已经解决了这个问题并实现了我自己的读取数据的方法,但我的问题仍然如下:为什么 ReadLine
没有按照罐子上说的那样做?
我在 MSDN 论坛上偶然发现了以下讨论:
According to this document, section "EBCDIC lineFeed mappings cause
invalid characters', near the bottom, IBM037 has two codes for a line
feed, 0x15 and 0x25. .NET appears to use 0x25:
byte[] bytes =
System.Text.Encoding.GetEncoding("IBM037").GetBytes("hello\r\n");
I saw another web page that mapped it to 0x15. No wonder ASCII won...
检查 Wikipedia von EBCDIC 037 确认确实字节 21 (0x15) 被定义为 "Newline" 和 37 (0x25) 定义为 "Line feed" 其中字节 13 (0x0D) 是老好人 "Carriage return".
所以 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 "Newline"。
这是因为ReadLine()
比较的是Unicode字符,而不是字节。 EBCDIC "NewLine" (0x25) 字节被解码为 Unicode 字符 '\n'.
结论
- 一切正常。
- 并非所有编码都有 ASCII 作为子集。
- ReadLine() 适用于 Unicode 字符,因此它一定是一个 encoding/decoding 问题。
- 检查你原题的输入数据。它可能包含无效的(对于 EBCDIC)换行符。
你将 (byte)'\r'
和 (byte)'\n'
填充到一个流中,你告诉 StreamReader
是用 EBCDIC 编码的。
(byte) '\r'
的值为 0x0d,在 ASCII 和 EBCDIC 中恰好是回车 return。
(byte) '\n'
的值为 0x0a,这是 ASCII 中的换行符,但不是 EBCDIC 中的换行符。
如果您查看 EBCDIC 编码器 class 如何将值 0x0a 解码为 .NET Unicode char
类型,您会发现 Unicode char
的数值是142(或 0x8e)。而且那个字符不是换行符。 (不知道为什么解码成142)
您在第二行的开头看到“10”打印出来,不是因为那里有换行符,而是因为值为 142 的字符被重新编码回值为 10 的 EBCDIC 字节(在子表达式 EBCDIC.GetBytes(line)
).
所以为了简单地回答你的问题,ReadLine()
只看到一个马车 return,而不是一个马车 return 后跟一个换行符。
将您的 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
背景:我必须编写一个应用程序,该应用程序采用设计不佳的 EBCDIC 文件,其中包含使用 ASCII 行终止符的二进制数据,有时二进制数据恰好包含 ASCII CRLF,这会导致行分割不正确。我需要采用这种旧文件格式并在每条记录的末尾删除 CRLF。
似乎使用带有 IBM037
编码的 StreamReader
会导致 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();
}
}
输出应该如下:
226150148133641491371311336412916213113713764163133167163
1022615014813364148150153133641491371311336412916213113713764163133167163
第二行开头的 10 不应该出现,因为那是 CRLF 序列中的 LF。
我对 ReadLine
方法的理解是:
A line is defined as a sequence of characters followed by a line feed ("\n"), a carriage return ("\r"), or a carriage return immediately followed by a line feed ("\r\n"). The string that is returned does not contain the terminating carriage return or line feed. Source
它没有说任何关于编码改变的事情,所以根据它应该读取我数据中的完整 CRLF 而不仅仅是 CR。
更新:我已经解决了这个问题并实现了我自己的读取数据的方法,但我的问题仍然如下:为什么 ReadLine
没有按照罐子上说的那样做?
我在 MSDN 论坛上偶然发现了以下讨论:
According to this document, section "EBCDIC lineFeed mappings cause invalid characters', near the bottom, IBM037 has two codes for a line feed, 0x15 and 0x25. .NET appears to use 0x25:
byte[] bytes = System.Text.Encoding.GetEncoding("IBM037").GetBytes("hello\r\n");
I saw another web page that mapped it to 0x15. No wonder ASCII won...
检查 Wikipedia von EBCDIC 037 确认确实字节 21 (0x15) 被定义为 "Newline" 和 37 (0x25) 定义为 "Line feed" 其中字节 13 (0x0D) 是老好人 "Carriage return".
所以 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 "Newline"。
这是因为ReadLine()
比较的是Unicode字符,而不是字节。 EBCDIC "NewLine" (0x25) 字节被解码为 Unicode 字符 '\n'.
结论
- 一切正常。
- 并非所有编码都有 ASCII 作为子集。
- ReadLine() 适用于 Unicode 字符,因此它一定是一个 encoding/decoding 问题。
- 检查你原题的输入数据。它可能包含无效的(对于 EBCDIC)换行符。
你将 (byte)'\r'
和 (byte)'\n'
填充到一个流中,你告诉 StreamReader
是用 EBCDIC 编码的。
(byte) '\r'
的值为 0x0d,在 ASCII 和 EBCDIC 中恰好是回车 return。
(byte) '\n'
的值为 0x0a,这是 ASCII 中的换行符,但不是 EBCDIC 中的换行符。
如果您查看 EBCDIC 编码器 class 如何将值 0x0a 解码为 .NET Unicode char
类型,您会发现 Unicode char
的数值是142(或 0x8e)。而且那个字符不是换行符。 (不知道为什么解码成142)
您在第二行的开头看到“10”打印出来,不是因为那里有换行符,而是因为值为 142 的字符被重新编码回值为 10 的 EBCDIC 字节(在子表达式 EBCDIC.GetBytes(line)
).
所以为了简单地回答你的问题,ReadLine()
只看到一个马车 return,而不是一个马车 return 后跟一个换行符。
将您的 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