晓风残月
专注于.NET技术
posts - 156,comments - 472,trackbacks - 40
MSDN 对 StreamReader.Read() 方法的解释是:

读取输入流中的下一个字符并使该字符的位置提升一个字符。

而该方法的返回值是一个 int ,之前竟然是这样转换的:

string str =System.Text.Encoding.Unicode.GetString(BitConverter.GetBytes(sr.Read()));

还是费了九牛二虎之力,才得到正确结果,才发现无论使用何种Encoding打开Stream,返回的总是 Unicode 编码。

今天才发现:可以直接将 int 强制转换成 char,并且可以正确的解码。因为,上面提到 Read 总是返回 Unicode 编码,而 char 在 .NET 内部正是用的 Unicode 编码。

char ch = (char)sr.Read();

事实上,MSDN这句话出现了两处字符已经暗示了 char 类型,只怪自己未能领悟。那么为什么 Read() 方法不直接返回 char ?StreamReader 的基类 TextReader 就是明确定义为字符的读取器。 找到MSDN对char类型的说明中提到:

多数 Unicode 字符可由一个 Char 对象表示,但编码为基字符、代理项对和/或组合字符序列的字符由多个 Char 对象表示。因此,String 对象中的 Char 结构不一定与单个 Unicode 字符等效。

看样子,返回 int 类型是为了兼容双字节(char为双字节)仍能无法表示的字符,只是此时就不知道怎么转换了? 用第一种方法强制解码?
有空找几个特殊字符来试试~
 
以下示例代码使用了GB2312作为流编码,当然其他编码也是一样的。
    public string Foo(string path)
    {
        StringBuilder outBuffer = new StringBuilder();       

        try
        {
            if (File.Exists(path))
            {
                File.Delete(path);
            }

            Encoding fileEncoding = Encoding.GetEncoding("GB2312");            
            using (StreamWriter sw = new StreamWriter(path, false, fileEncoding))
            {
                sw.WriteLine("");
                sw.WriteLine("");
                sw.WriteLine("中国人");
            }

            using (StreamReader sr = new StreamReader(path, fileEncoding))
            {
                while (sr.Peek() >= 0)
                {
                    // 曾经是这样读取的:将 int 转成 byte[],然后再解码,然后尝试了N多编码,
                    //费了九牛二虎之力,才得到正确结果,才发现无论使用何种Encoding打开Stream,返回的总是 Unicode 编码。
                    // outBuffer.Append(System.Text.Encoding.Unicode.GetString(BitConverter.GetBytes(sr.Read())));
                    // 
                    // 今天才在MSDN中发现:可以直接将 int 强制转换成 char,并且可以正确的解码。
                    // 因为,上面提到 Reader 总是返回 Unicode 编码,而 char 在 .NET 内部正是用的 Unicode 编码。
                    // 郁闷!
                    outBuffer.Append((char)sr.Read());                    
                }
            }
        }
        catch (Exception ex)
        {
            outBuffer.AppendFormat("The process failed: {0}", ex.ToString()).AppendLine();
        }
        //
        return outBuffer.ToString();
    }
posted on 2008-06-03 23:47 晓风残月 阅读(1922) 评论(30)  编辑 收藏 网摘

FeedBack:
2008-06-04 00:14 | Yannic Yang      
收下了,编码总是很讨厌的问题
  回复  引用  查看    
2008-06-04 00:47 | 阿牛      
read()返回int ,因为还要用-1等表示错误码

至于编码,是在 StreamReader(Encoding)时指定的。

流是没有编码的,是二进制的流,但字符是有编码的,想读成字符时就要指定编码了。


  回复  引用  查看    
2008-06-04 00:53 | 阿牛      
public string Foo(string path)
{

try
{
if (File.Exists(path))
{
File.Delete(path);
}

Encoding fileEncoding = Encoding.GetEncoding("GB2312");
using (StreamWriter sw = new StreamWriter(path, false, fileEncoding))
{
sw.WriteLine("我");
sw.WriteLine("是");
sw.WriteLine("中国人");
}

using (StreamReader sr = new StreamReader(path, fileEncoding))
{
return sr.ReadToEnd();
}
catch (Exception ex)
{
return string.Format(@"The process failed: {0}
", ex.ToString());
}
//

}
  回复  引用  查看    
2008-06-04 00:55 | Microshaoft      
如果Stream(文件)很大
逐个字符读取效率很低的

  回复  引用  查看    
2008-06-04 01:42 | dreamland      
一般先读取出字符的大小,然后申请一个缓冲区,直接用GetString读取字符串啊?
byte[] guid_buf = ReadBytes(CommandConst.GUID_LEN);
string result = Encoding.Default.GetString(guid_buf, 0, CommandConst.GUID_LEN - 1);

不明白有什么时候需要单个读取一个字符,但是读取一个字符的话,其实都是可以规划为读取一个字符的字符串吧?
  回复  引用  查看    
#6楼 [楼主]
2008-06-04 01:49 | 晓风残月      
--引用--------------------------------------------------
阿牛: read()返回int ,因为还要用-1等表示错误码

至于编码,是在 StreamReader(Encoding)时指定的。

流是没有编码的,是二进制的流,但字符是有编码的,想读成字符时就要指定编码了。


--------------------------------------------------------
没错,只有实例化StreamReader 的时候才需要指定 Encoding(默认UTF-8),而打开流不要指定,
我想表达的是,无论指定StreamReader以何种编码读取流(实际就是解码的过程,假如编码错了,自然结果也就错了),Read() 方法总是返回 Unicode 编码的字符。

  回复  引用  查看    
#7楼 [楼主]
2008-06-04 01:50 | 晓风残月      
@Yannic Yang
^_^
  回复  引用  查看    
2008-06-04 01:53 | 梁逸晨      
楼主,您有没有什么办法自动判断目标字符串的编码类型?目标是未知的,GB2312或者UTF-8都说不定。
  回复  引用  查看    
#9楼 [楼主]
2008-06-04 01:55 | 晓风残月      
@Microshaoft
呵呵,效率是一个问题,
粗粒度处理当然可考虑 Read(char[],...), ReadBlock(), ReadLine 批量读取,
当少量数据时,特别是需要细粒度处理的时候,还是有他的特殊用法
  回复  引用  查看    
#10楼 [楼主]
2008-06-04 02:00 | 晓风残月      
--引用--------------------------------------------------
梁逸晨: 楼主,您有没有什么办法自动判断目标字符串的编码类型?目标是未知的,GB2312或者UTF-8都说不定。
--------------------------------------------------------
字符串的编码类型?已经是字符串(我认为已经解码正确了)还需要判断?是不是指自动判断文件的编码?
  回复  引用  查看    
#11楼 [楼主]
2008-06-04 02:02 | 晓风残月      
--引用--------------------------------------------------
dreamland: 一般先读取出字符的大小,然后申请一个缓冲区,直接用GetString读取字符串啊?
byte[] guid_buf = ReadBytes(CommandConst.GUID_LEN);
string result = Encoding.Default.GetString(guid_buf, 0, CommandConst.GUID_LEN - 1);

不明白有什么时候需要单个读取一个字符,但是读取一个字符的话,其实都是可以规划为读取一个字符的字符串吧?
--------------------------------------------------------
实际上,按块读取有很多方法,StreamReader 就有 Read(char[],...), ReadBlock(), ReadLine
这里提到读取单个字符,只是刚好有特殊要求
  回复  引用  查看    
2008-06-04 02:11 | 梁逸晨      
--引用--------------------------------------------------
晓风残月: --引用--------------------------------------------------
梁逸晨: 楼主,您有没有什么办法自动判断目标字符串的编码类型?目标是未知的,GB2312或者UTF-8都说不定。
--------------------------------------------------------
字符串的编码类型?已经是字符串(我认为已经解码正确了)还需要判断?是不是指自动判断文件的编码?
--------------------------------------------------------

对的,就是判断文件的编码

是这样的,比如说我自己就尝试在AJAX中使用服务端代理来做跨域操作,这样的话,这个代理文件就涉及到有可能读取多种类型的其它域的数据。

换句话说,和远程采集是一个道理的。如果以Encoding.GetEncoding("GB2312"); 去读取UTF-8的远程文件,就出现乱码,反之也一样。现在我想找个能够实现自动判断远程文件的编码类型的方法,我以前听说在读取到的文件头里面有几个什么字符的。还听说一种说法是这些字符也不一定是每个文件里面都有。
  回复  引用  查看    
2008-06-04 09:14 | 路过 [未注册用户]
You can read single bytes or entire blocks of bytes at a time. Two specific instance methods can be used to synchronously read from a Stream: int ReadByte() and int Read(byte[] buffer, int offset, int count). ReadByte reads only 1 byte at a time and returns -1 to indicate an EOS condition. Read, on the other hand, reads a block of bytes, and returns 0 to indicate EOS.

Note: You might wonder why ReadByte returns a 4-byte Int32 yet only advances through streams at a rate of 1 byte at a time. Why doesn't the API simply use a Byte, then? One reason is that Byte is not signed, yet the method uses a signed integer -1 to indicate EOS. Technically, an Int16 could have been used but because the C# int keyword — the most widely used integer type on the platform — maps to an Int32, using Int32 eliminates unnecessary runtime coercions.

节选自 Professional .NET Framework 2.0 第七章

  回复  引用    
2008-06-04 09:26 | airwolf2026      
@梁逸晨,如果没有记错的话,那种只能判断UTF编码的,好像叫BOM头(记性不好了,你可以找下哈),类似FFEF这样的头,很多文本编辑器可以要这个头或者不要这个头.
  回复  引用  查看    
2008-06-04 10:06 | 聪少      
--引用--------------------------------------------------
梁逸晨: --引用--------------------------------------------------
晓风残月: --引用--------------------------------------------------
梁逸晨: 楼主,您有没有什么办法自动判断目标字符串的编码类型?目标是未知的,GB2312或者UTF-8都说不定。
--------------------------------------------------------
字符串的编码类型?已经是字符串(我认为已经解码正确了)还需要判断?是不是指自动判断文件的编码?
--------------------------------------------------------

对的,就是判断文件的编码

是这样的,比如说我自己就尝试在AJAX中使用服务端代理来做跨域操作,这样的话,这个代理文件就涉及到有可能读取多种类型的其它域的数据。

换句话说,和远程采集是一个道理的。如果以Encoding.GetEncoding("GB2312"); 去读取UTF-8的远程文件,就出现乱码,反之也一样。现在我想找个能够实现自动判断远程文件的编码类型的方法,我以前听说在读取到的文件头里面有几个什么字符的。还听说一种说法是这些字符也不一定是每个文件里面都有。
--------------------------------------------------------

我也想知道有没什么方法可以判断一个文件,或者一段二进制的编码.
  回复  引用  查看    
2008-06-04 12:48 | PerfectDesign      
不可以自动判断,除非使用http协议的规范类似的。charset=utf-8
就可以
http协议头默认是ascii body由charset指定
  回复  引用  查看    
2008-06-04 18:06 | Klesh Wong      
.NET内部以为windows内核都使用unicode编码
对Reader和Writer指定编码是针对"流内部内部",而在外部对流写入,或者读出,依然是unicode.
sw.WriteLine("我");
sw.WriteLine("是");
sw.WriteLine("中国人");
这里的"我","是","中国人"事实上也是unicode.分清楚指定编码针对的是内是外就容易理解了.
  回复  引用  查看    
2008-06-04 19:45 | G yc {Son of VB.NET}      
楼主,想要干什么, 没有看明白


.NET 默认识别 ANSI,和Unicode 编码(UTF-8, Unicode(LB) ,UTF32, 除了UTF-7 切记)


如果只是要处理编码的话,
应该看看 Encoder.GetBytes 和 Decoder.GetString 方法
都在System.Text. 命名空间下
另外, System.Text.Encoding 也可以解码文字,不过,根据MSDN说法,不支持 对非完整字节的处理,(恩,就是 当 汉字一类的东西,占用多个字节,但你缓冲的时候,只可能了缓冲了前半部分,如果此时解码,就会产生乱码,但Decoder可以支持这种情况,没有解码的部分会被缓存等到下一组数据到来后,继续解码)
  回复  引用  查看    
#19楼 [楼主]
2008-06-04 21:15 | 晓风残月      
@梁逸晨
你这里提到的自动判断依据应该就是 airwolf2026 提到的 BOM(Binary Order Mark,字节序列标记),据我了解 BOM 是 Unicode 编码家族特有的,其他诸如 ANSI 编码是没有的。
MSDN 中(http://msdn.microsoft.com/zh-cn/library/system.text.encoding.getpreamble.aspx)有关于 BOM 的简单介绍以及示例,可以参考下。
本身StreamReader (注意那几个带bool类型参数的重载构造函数)内置支持自动根据 BOM 确定编码方式,当然仅限于 Unicode 家族编码,假如无法找到 BOM 信息那么就会使用 UTF-8 来加载流。


  回复  引用  查看    
#20楼 [楼主]
2008-06-04 21:19 | 晓风残月      
@聪少
Unicode系统编码文件可以包含 BOM 信息做为文件头(2或者3个字节)
比如在 VS 的 Save as 窗口中就可以选择编码,其中你会发现
有两个UTF-8,一个是 With Signature,这就写入 BOM 信息

来自第三方的单纯一段字节,应该没有办法识别编码格式,除非他明确告诉你,头几个字节是存储编码标识的,比如 BOM
  回复  引用  查看    
#21楼 [楼主]
2008-06-04 21:25 | 晓风残月      
--引用--------------------------------------------------
PerfectDesign: 不可以自动判断,除非使用http协议的规范类似的。charset=utf-8
就可以
http协议头默认是ascii body由charset指定
--------------------------------------------------------
也不是不能自判断,只是能判断,也是需要额外的信息的,比如 Unicode 协议规定的 BOM,但也是可选的,并没有强制必须包含 BOM,再如 HTTP可能是最经典的,为 Server/Client 提供了那么的 header 来对 Request/Response 提供标识信息。

根据 Unicode 协议,建议是对存储编码字节(如文件)使用 BOM,以便处理方可正确识别编码,而对于诸如传输编码字节(如网络流)不建议使用 BOM 标识,以为有些网络服务器不识别 BOM 序列,会将其当作实际意义的字节处理。

总之,编码问题一直让人很头疼。
  回复  引用  查看    
#22楼 [楼主]
2008-06-04 21:34 | 晓风残月      
@Klesh Wong
想到".NET内部字符串使用Unicode编码”,对“为什么 StreamReader.Read() 总是返回的是Unicode序列整型值“的疑惑也全没了。
应该可以这样理解:StreamReader.Read() 返回已经是一个Unicode字符了,只是不是char而是int类型表示,代表的是这个字符在Unicode编码中的整型值。

一直对【字符,字节和编码】总是迷迷糊糊,看了下面这篇文章终于有了较深理解:
http://www.regexlab.com/zh/encoding.htm
  回复  引用  查看    
#23楼 [楼主]
2008-06-04 21:40 | 晓风残月      
--引用--------------------------------------------------
G yc {Son of VB.NET}: 楼主,想要干什么, 没有看明白
--------------------------------------------------------
之前是对无论以何种编码使用 StreamReader 其 Read 总是返回 Unicode 编码字符值,不理解现在终于明白了:因为 .NET 中使用的字符编码就是 Unicode。


.NET 默认识别 ANSI,和Unicode 编码(UTF-8, Unicode(LB) ,UTF32, 除了UTF-7 切记)
--------------------------------------------------------
默认识别是什么意思?通过 BOM 判断?假如刚好这个文件不包含BOM呢?


如果只是要处理编码的话,
应该看看 Encoder.GetBytes 和 Decoder.GetString 方法
都在System.Text. 命名空间下
另外, System.Text.Encoding 也可以解码文字,不过,根据MSDN说法,不支持 对非完整字节的处理,(恩,就是 当 汉字一类的东西,占用多个字节,但你缓冲的时候,只可能了缓冲了前半部分,如果此时解码,就会产生乱码,但Decoder可以支持这种情况,没有解码的部分会被缓存等到下一组数据到来后,继续解码)
--------------------------------------------------------
嗯,记下了,之前看过一点Encoder,只是没有实际用过,也就没有体会了

  回复  引用  查看    
2008-06-05 09:00 | PerfectDesign      
@晓风残月
谢谢你的解答
但是使用BOM就会有而外的字节开销来记录编码类型?
  回复  引用  查看    
#25楼 [楼主]
2008-06-05 10:35 | 晓风残月      
@PerfectDesign
目前 Unicode 字节顺序标记 (BOM) 的序列化结果(十六进制)如下所示:
UTF-8:EF BB BF
UTF-16 Big-Endian 字节顺序:FE FF
UTF-16 Little-Endian 字节顺序:FF FE
UTF-32 Big-Endian 字节顺序:00 00 FE FF
UTF-32 Little-Endian 字节顺序:FF FE 00 00

也就是说最多4个字节,到底是不是额外的开销,相对你的总字节数了。
当然我们可以自己定义一个标识表示作为文件头,可能只用一个字节就够了,只是BOM是标准化的,可保证兼容性,而自定义的只有自己才能处理
  回复  引用  查看    
2008-06-05 11:51 | 要有好的心情      
"才发现无论使用何种Encoding打开Stream,返回的总是 Unicode 编码":是不是因为 创建流的数据源本身就是Unicode 编码的 ? 打开一个 gbk编码的文档试试
  回复  引用  查看    
#27楼 [楼主]
2008-06-05 20:48 | 晓风残月      
@要有好的心情
是不是因为 创建流的数据源本身就是Unicode 编码的 ?
=======
不是,因为实际返回是一个字符的编码值(就像ASCII中A对应65这个整型值一样),而 .NET 内部所有字符编码都是 Unicode,所以 ....
  回复  引用  查看    
2008-06-05 20:54 | G yc {Son of VB.NET}      
--引用--------------------------------------------------
要有好的心情: "才发现无论使用何种Encoding打开Stream,返回的总是 Unicode 编码":是不是因为 创建流的数据源本身就是Unicode 编码的 ? 打开一个 gbk编码的文档试试
--------------------------------------------------------
.NET 内部使用就是 Unicode (16) 表示 字符串
如果你要读出来(文本)了,基本都会变成 Unicode 的

  回复  引用  查看    
2008-06-06 07:56 | 梁逸晨      
知道了,多谢楼主
  回复  引用  查看    
2008-07-18 17:55 | jinglin [未注册用户]
the character 'æ' is unicode character, i want to read a line from text document. it contains 'æ', but can't read it correct, what should i do?
  回复  引用    

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-06-04 00:35 编辑过
Google站内搜索

China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》



相关文章:

相关链接:

历史上的今天:
2006-06-03 Visual Studio 2005 Web Application Projects