C# Encoding

之前做公司项目的时候,对于C#编码这块总是一知半解,所以打算通过这篇笔记对C#编码(Encoding)进行彻底的扫盲,关于编码和字符集的基础知识,请参考字符集和字符编码(Charset & Encoding),看完这篇文章之后,来看本文会更加的轻松。

1、Encoding

(1)、如何生成一个Encoding即一种编码

Encoding位于System.Text命名空间下,是一个抽象类,它的派生类如下图:

要实例化一个Encoding一共有以下两种方式:

a、通过实例化它的派生类,然后通过里式转换实例化一个Encoding,代码如下:

Encoding e=new UTF8Encoding();

b、通过Encoding的静态属性ASCII,Unicode,UTF32,UTF7,UTF8,Default来生成,代码如下:

Encoding e = Encoding.UTF8;

其实b中的静态属性无非是new了一个a中的派生类,有图为证

注:上面通过静态属性生成的Encoding实例,符合单例模式,但是并不适用在多线程环境下,所以当你的Encoding需要全局唯一时,请使用静态属性的方式,而不是通过new的方式。

(2)Encoding.Default

注意(1)中b,Encoding的静态属性中有一个Default,它没有对应的派生类,但是它返回的也是一个Encoding对象,至于返回那种语言的Encoding,取决于取决于你电脑里-->控制面板->区域和语言 里面的设置,也就是ANSI,比如我的电脑设置的是中文,那么对应的就是gb2312,但是如果你的代码不止在一个国家使用,那么就不要使用Encoding.Default,这样会造成乱码,最好使用Encoding.UTF8.

2、如何调用常用编码之外的编码,通过GetEncoding()和GetEncodings()

上面介绍了ASCII,Unicode,UTF32,UTF7,UTF8常规的5中编码方式,但是有一些编码如gb2312就没有对应的派生类,那么获取这类语言对应的Encoding只能通过GetEncoding()和GetEncodings()方法来获取

(1)、GetEncodings()

通过GetEncodings()可以获取所有的编码,代码如下:

EncodingInfo[] infos = Encoding.GetEncodings();

目前为止一共有140种,通过GetEncodings()方法你可以方便的查看所有语言的编码信息,本人开发了一个简单窗体应用程序,来查询不同的编码信息。点击下载

(2)、GetEncoding()

通过这个方法可以获取指定语言的Encoding,当然你必须给出一个codePage或者是name,代码如下:

Encoding ei=Encoding.GetEncoding(936);
Console.WriteLine(ei.WebName);

3、通过Encoding完成字节和字符之间的转换

(1)、GetBytes() 含多种重载方法

通过GetBytes()可以把一个字符串或者是字符串数组转换成字节,代码如下

string str = "阿萨德喝酒啊是贷款";
byte[] bytes = Encoding.Unicode.GetBytes(str);

(2)、GetChars()含多种重载方法

通过GetChars()可以将字节数组转换成字符,代码如下

string str = "阿萨德喝酒啊是贷款";
byte[] bytes = Encoding.Unicode.GetBytes(str);
char[] a=Encoding.Unicode.GetChars(bytes);
Console.WriteLine(a);

(3)、GetByteCount()含多种重载方法

通过GetByteCount()可以获得将字符串或者字符串数组转换成字节数组的字节数组的长度,代码如下:

string str = "阿萨德喝酒啊是贷款";
int count= Encoding.Unicode.GetByteCount(str);
Console.WriteLine(count);

(4)、GetCharCount()含多种重载方法

通过GetCharCount()可以获得将字节数组转换成字符串或者字符数组的字符串长度,代码如下:

string str = "阿萨德喝酒啊是贷款";
byte[] bytes = Encoding.Unicode.GetBytes(str);
int count = Encoding.Unicode.GetCharCount(bytes);
Console.WriteLine(count);

4、BOM 判断文件的编码方式

     这个BOM并不是Html中的BOM,而是一种字节顺序标记,BOM的全称是全称是Byte Order Mark,是一段二进制,用于标识一个文本是用什么编码的,比如当用Notepad打开一个文本时,如果文本里包括这一段BOM,那么它就能判断是采用哪一种编码方式,并用相应的解码方式,就会正确打开文本不会有乱码.如果没有这一段BOM,Notepad会默认以ANSI打开,这种会有乱码的可能性.

下面是通过BOM来判断文件编码的一段工具方法,代码如下:

public static Encoding GetFileEncoding(string filePath)
        {
            Encoding Result = null;
            FileInfo info = new FileInfo(filePath);
            FileStream fs = default(FileStream);
            try
            {
                fs = info.OpenRead();
                Encoding[] unicodeEncodings =
                {
                    Encoding.BigEndianUnicode, 
                    Encoding.Unicode,
                    Encoding.UTF8,
                    Encoding.UTF32,
                    Encoding.UTF7,
                    new UTF32Encoding(true,true)
                };
                for (int i = 0; Result == null && i < unicodeEncodings.Length; i++)
                {
                    fs.Position = 0;
                    byte[] preamble = unicodeEncodings[i].GetPreamble();
                    bool isEqual = true;
                    for (int j = 0; isEqual && j < preamble.Length; j++)
                    {
                        isEqual = preamble[j] == fs.ReadByte();
                    }
                    if (isEqual)
                        Result = unicodeEncodings[i];

                }
            }
            catch (IOException ex)
            {
                throw ex;
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();//包括了Dispose,并通过GC强行释放资源
                }
            }
            if (object.ReferenceEquals(null, Result))
            {
                Result = Encoding.Default;
            }
            return Result;
        }

5、Encoder和Decoder

(1)、Encoder是一个抽象类,它在Encoding中以一个虚方法的形式出现,调用该方法,会返回一个指定编码的编码器

代码如下:

string str = "Encoder测试";
int charCount=str.Length;
Encoding ed=Encoding.UTF8;
char[] chars = str.ToCharArray();
int maxByteCount=ed.GetEncoder().GetByteCount(chars,0,charCount,false);//通过GetEncoder()获得的UFF8编码器获得str转换成byte数组的长度
byte[] result=new byte[maxByteCount];
ed.GetEncoder().GetBytes(chars, 0, charCount, result, 0, false);//通过GetEncoder()获得的UTF8编码器对str进行加密,并将加密后的字节数组赋给result
for (int i = 0; i < result.Length; i++)
{
     if (i != result.Length-1)
     Console.Write("{0:X}-", result[i]);//以16进制输出
     else
     Console.Write("{0:X}", result[i]);
}
Console.ReadLine();

(2)、Decoder也是一个抽象类,它在Encoding中也是以一个虚方法的形式出现,调用该方法,会返回一个指定编码的解码器

代码如下:

string str = "Encoder测试";
int charCount=str.Length;
Encoding ed=Encoding.UTF8;
char[] chars = str.ToCharArray();
int maxByteCount=ed.GetEncoder().GetByteCount(chars,0,charCount,false);
byte[] result=new byte[maxByteCount];
ed.GetEncoder().GetBytes(chars, 0, charCount, result, 0, false);
char[] resultChars=new char[ed.GetDecoder().GetCharCount(result,0,maxByteCount)];
ed.GetDecoder().GetChars(result, 0, result.Length, resultChars,0,false);
for (int i = 0; i < resultChars.Length; i++)
{
    if (i != resultChars.Length - 1)
       Console.Write("{0}-", resultChars[i]);
    else
       Console.Write("{0}", resultChars[i]);
}
Console.ReadLine();

通过分析上面两端发现,其实它们的作用和单纯的调用Encoding的静态属性进行编解码并没有什么区别,而且使用Encoding进行编解码更加的便捷,就不需要创建额外的Encoder和Decoder对象实例,现在就来讲解Encoder和Decoder真正的作用!

(3)、通过一个特殊的需求来说明GetDecoder和GetEncoder()的作用

通过Encoding的静态属性对(字符串或者字符数组)的整个片段进行编解码时,并不会出现任何问题,代码如下:

string str = "Encoding博客系列";
byte[] bytes = Encoding.UTF8.GetBytes(str);
char[] result = Encoding.UTF8.GetChars(bytes);
Console.WriteLine(result);

但是当处理部分片段,并且片段中有多字节字符或者字符串(如中文),就会出现乱码的情况,代码如下:

假设我们需要的在后处理一个特殊文件流,要求每次只处理4个字节,代码如下:

string path = Path.GetTempFileName();//创建临时文件,并返回该文件的路径
File.WriteAllText(path, "Encoding博客系列", new UTF8Encoding(false));//覆盖上面的临时文件,并向文件中追加一段字符串,采用UTF8编码
using (FileStream stream = File.OpenRead(path))
{
      byte[] buffer = new byte[4];
      int size;
      while ((size = stream.Read(buffer, 0, 4)) > 0)
      {
              char[] chars = Encoding.UTF8.GetChars(buffer, 0, size);
              if (chars.Length != 0)
              {
                        Console.Write("{0,-6}", new string(chars));
                        Console.Write("字节:");
                        for (int i = 0; i < size; i++)
                        {
                            Console.Write("{0:X2} ", buffer[i]);//将单个字节以2位16进制输出
                        }
                        Console.WriteLine();
                 }
                Thread.Sleep(1000);
        }
}
Console.Read();

 目前我不清楚为什么出现这个问题的原因,由于时间问题,我也不想深究下去,如果有兴趣,你可以去解读下Encoding的源码。

so,为了解决这个问题,就只能使用调用UTF8的解码器,对字符或者字符数组进行解码,修正代码如下:

string path = Path.GetTempFileName();//创建临时文件,并返回该文件的路径
File.WriteAllText(path, "Encoding博客系列", new UTF8Encoding(false));//覆盖上面的临时文件,并向文件中追加一段字符串,采用UTF8编码
Decoder dr = Encoding.UTF8.GetDecoder();
using (FileStream stream = File.OpenRead(path))
{
         byte[] buffer = new byte[5];
         int size;
         while ((size = stream.Read(buffer, 0, 5)) > 0)
         {
         char[] charsDecoder = new char[dr.GetCharCount(buffer, 0, size)];
         dr.GetChars(buffer, 0, size, charsDecoder, 0);
         char[] chars = Encoding.UTF8.GetChars(buffer, 0, size);
         if (chars.Length != 0)
         {
             Console.Write("{0,-6}", new string(charsDecoder));
             Console.Write("{0,-6}", new string(chars));
             Console.Write("字节:");
             for (int i = 0; i < size; i++)
             {
                 Console.Write("{0:X2} ", buffer[i]);//将单个字节以2位16进制输出
             }
                 Console.WriteLine();
           }
           Thread.Sleep(1000);
     }
}
Console.Read();

Encoder和Decoder 维护对 GetBytes() 和GetChars()的连续调用间的状态信息,因此它可以正确地对跨块的字符序列进行编码。Encoder 还保留数据块结尾的尾部字符并将这些尾部字符用在下一次编码操作中。例如,一个数据块的末尾可能是一个不匹配的高代理项,而与其匹配的低代理项则可能位于下一个数据块中。因此,Decoder 和 Encoder 对网络传输和文件操作很有用,这是因为这些操作通常处理数据块而不是完整的数据流。StreamReader和SteamWriter关于读和书的就是用Decoder和Encoder。

 综上所述:

1. CLR中字符串都是Unicode 16 编码

2. 尽量调用Encoding的静态属性UTF8,Unicode等,而不是去实例它们

3. 尽量避免用Encoding.Defalut

4. BOM是用来识别哪一种编码的,默认是带有的,如果不需要,那么调用它们的带有参数的构造器,找到相应参数传false

5. 在对文件流和网络流操作时,应该用Encoder和Decoder

本文参考自:http://www.cnblogs.com/criedshy/archive/2012/08/07/2625358.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏hbbliyong

C#基础知识回顾-- 反射(1)

反射(reflection)是一种允许用户获得类型信息的C#特性。术语“反射”源自于它的工作方式: Type对象映射它所代表的底层对象。对Type对象进行...

34510
来自专栏程序员互动联盟

【编程基础】聊聊C语言-兵马未动粮草先行(1)

上一篇我们讲的聊聊C语言-我的地盘我做主,相信大家对变量的存储类型和变量的作用域有了一定的了解。现在我们马上公布上期的答案如下: #include<stdio....

3308
来自专栏小樱的经验随笔

codeforces 767A Snacktower(模拟)

A. Snacktower time limit per test:2 seconds memory limit per test:256 megabytes ...

3307
来自专栏信安之路

php 不用字母,数字和下划线写 shell

还有这个师傅的 《记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门)》

3451
来自专栏Jackson0714

项目中遇到的扩展方法-总结和分享

3457
来自专栏大内老A

WCF技术剖析之十三:序列化过程中的已知类型(Known Type)

DataContractSerializer承载着所有数据契约对象的序列化和反序列化操作。在上面一篇文章(《数据契约(Data Contract)和数据契约序列...

25610
来自专栏码云1024

C#委托与事件

4016
来自专栏绿巨人专栏

TypeScript中的怪语法

4155
来自专栏Python爬虫与算法进阶

学点算法之栈的学习与应用

在学习栈前,脑海中对这个词只有一个印象:客栈 栈是什么 栈(有时称为“后进先出栈”)是一个项的有序集合,其中添加移除新项总发生在同一端。 这段话初学者是懵逼的...

3093
来自专栏崔庆才的专栏

Python操作Redis,你要的都在这了!

4.6K4

扫码关注云+社区