一起谈.NET技术,漫谈.NET开发中的字符串编码

         在《 .NET 4.0面向对象编程漫谈》基础篇《13.2.1 序列化与流》中,向大家介绍了如何向流中序列化一个对象。

         本篇扩充阅读将向读者介绍将字符串对象的序列化,这里面的关键是字符串应该如何编码和解码为二进制数值,从而可以把它们保存到文件流(FileStream)中,或者通过网络流(NetworkStream)将它们远程发送到另一台计算机上。

  1 引子

         在实际开发中,经常需要将一些字符串写入到文本文件中,或者从文本文件中读入字符串,在.NET应用程序中,通常使用StreamReader或StreamWriter两个类完成这一工作,比如以下代码将fileContent字串写入到FileName文件中:

static void WriteFileUseStreamWriter(String fileContent, String FileName)
{
  using (StreamWriter writer = new StreamWriter(FileName))
  {
    writer.Write(fileContent);
  }
}

       如果你使用.NET基类库中相关类(比如StreamReader或下面用到的File类)去读取这个文件,你会发现一切如你所愿地正常运转:

WriteFileUseStreamWriter("中国ab", "test.txt");
Console.WriteLine(File.ReadAllText("test.txt"));     //输出:“中国ab”        

        由于多数情况下我们都工作在中文Windows下,而且往往都是某个.NET程序写,另一个.NET程序读,所以,不少.NET程序员可能都没注意到这其中其实存在着一个字符编码的问题,在特定的场合下,这一问题会给我们带来麻烦。

         请看图1:

1 记事本支持的编码方式

         默认情况下,Windows记事本以ANSI编码方式保存文件。如图1所示,如果文本内容为“中国ab”,记事本将其以ASNI方式保存为“test.txt”,则以下代码将“罢工”了(参看图2):

Console.WriteLine(File.ReadAllText("test.txt"));  

2 汉字将显示为乱码

         如图 2所示,File.ReadAllText方法打开“test.txt”文件时,会发现英文字符可以正常显示,但中文将显示为乱码。

  2 了解字符的编码

         我们可以做个试验,使用记事本将“中国ab”这个中英混杂的字符串以不同编码方式保存为多个“.txt”文件,然后直接查看其二进制内容:

3  比对字符编码

          图 3展示了“中国ab”按四种编码方式(ANSI、UTF8、Unicode、Unicode Big Endian)得到的不同二进制数据。

         以英文字符“a”为例,ANSI和UTF8得到的数值都是“61”,但Unicode将它扩充为2个字节16位的二进制(“61 00”和“00 61”),所以我们又将这种编码方式称为UTF-16。

         UTF-16又可以细分为2种编码方式:Big Endian方式与Little_Edian方式,这两者的唯一区别在于字节排列顺序刚好相反, Little_Edian方式将“a”编码为“61 00”,而Big Endian方式则编码为“00 61”。

         现在看看中文字符,“中国”两个汉字,ANSI编码为“D6 D0 B9 FA”,4个字节,一个汉字占两个字节,而UTF8则编码为“E4 B8 AD E5 9B BD”,6个字节,一个汉字占3个字节!这说明UTF8是一种“变长”的编码,可能使用1~4个字节来表示某个字符。

         另外,我们看到UTF8和Unicode编码(不管是Big Endian还是Little Endian)前面都有几个标记字符,这些字符放在文本文件的开头,称为“BOM(Byte Order Mark,字节顺序标记)”指明了文本的编码方式,以下是.NET程序中常见的字符编码方式的BOM值:


编码


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

         了解了上述基础知识,我们就可以依据BOM值自动检测字符串的编码方式,从而正确从二进制数据流中解码,以下代码检测文本二进制数据是否采用UTF8编码:


//打开文件读取二进制数据
byte[] FileContents = File.ReadAllBytes(FilePath);
int filelength = FileContents.Length;
//检测BOM
if (FileContents[0] == 0xef && FileContents[1] == 0xbb && FileContents[2] == 0xbf)
{
//按UTF8解码字符串,注意要排除掉BOM占用的3个字节。
String content= Encoding.UTF8.GetString( FileContents, 3, filelength - 3);
Console.WriteLine(content);
}

         其他的编码方式都可以“依样画葫芦”。

  3 详解.NET基类库中与字符编码相关的类

         前述代码中的Encoding类是.NET实现字符编码解码的核心类型。图4展示了它的属性:

图 4 Encoding类型

         如图4所示,Encoding类型提供了UTF8、Unicode等编码和解码器,调用它的Get系列方法完成编码和解码工作,以下为示例代码:


//编码
byte[] bytes = Encoding.UTF8.GetBytes("中国ab");
foreach (byte value in bytes)
Console.Write(" {0}", value.ToString("x")); //转化为16进制
Console.WriteLine();
//解码
char[] chars = Encoding.UTF8.GetChars(bytes);
foreach (char ch in chars)
Console.Write(" {0}", ch);

 

  运行结果如下:

图5 编码和解码

         需要注意的是上述二进制值不包括BOM。

         事实上,.NET中的StreamWriter默认采用UTF8编码格式编码字符串,但并不将UTF8所对应的BOM值(“EF BB BF”)写入到二进制流中。以下是StreamWriter的一个构造函数声明:

public StreamWriter(string path) : this(path, false, UTF8NoBOM, 0x400)
{    }

         类似地,File.ReadAllText()方法在内部使用UTF8来读取指定文件中的字符串:

public static string ReadAllText(string path)
{
    //……
    return InternalReadAllText(path, Encoding.UTF8);
}

     由于默认编码方式一致,所以配套使用StreamWriter和File.ReadAllText()方法可以正确地从流中存取字符串。

    出于提升代码可维护性考虑,正确的用法应该是明确地指明编码方式:

static void WriteFileUseStreamWriterUseUTF8(String fileContent, String FileName)
{
    using (StreamWriter writer = new StreamWriter(FileName, false, Encoding.UTF8))
    {
                   writer.Write(fileContent);
    }
}

          这时,StreamWriter会在文件开头写入UTF8的BOM标记,从而让其他的应用程序可以很明确地知道本文件中字符串的编码方式。

  4 谈谈有趣的Encoding.Default属性        

         Encoding类中有一个有趣的Default属性,它的类型很奇怪,叫作“DBCSCodePageEncoding”,这个类型在MSDN中是查不到的。

         “DBCS”代表“double-byte character set(双字节字符集)”,它是与“SBCS(single-byte character set,单字节字符集)”相对应的,SBCS中,所有字符都只占一个字节,所以能表示的字符数有限,但在DBCS中,英文字母占一个字节,汉字等特殊字符占有两个字节,从而扩充了Windows能显示的字符数量。

         DBCSCodePageEncoding中的“Code Page”被称为“代码页”,每个代码页定义了特定的编码将如何对应于特定的字符(比如简体和繁体中文就分别定义在不同的代码页中),因此,同样的二进制数值,在不同的代码页中,会代表不同的字符。中文Windows通过使用基于代码页的DBCS编码方式,可以方便地以多种编码方式显示和处理字符串。

         我们在MSDN中可以查到所有代码页的编号,下面列出了可能比较常用的代码页标识:


代码页标识值


.NET中的名字


936


gb2312


950


big5


1200


utf-16


52936


hz-gb-2312


54936


GB18030


65000


utf-7


65001


utf-8

         .NET应用程序可以通过以下方式获取指定代码页的编码对象:        

Encoding encode=Encoding.GetEncoding(CodePage);

         以下代码将按照指定代码页编码字符串,并将其写入到文件中:

static void WriteFileUseStreamWriterUseCodePage(String fileContent,String FileName,
   int CodePage)
{
   using (StreamWriter writer = new StreamWriter(FileName, false,
        Encoding.GetEncoding(CodePage)))
   {
        writer.Write(fileContent);
   }
}

    现在,使用以下代码将按照UTF8编码字符串:

WriteFileUseStreamWriterUseCodePage("中国ab", "test.txt", 65001);

  5 结束语

         除了本文所介绍的将字符串保存到文本文件的这种场景,字符串的编码方式在基于套接字的TCP/UDP网络编程也非常重要,比如.NET提供了一个NetworkStream封装Socket实现网络通讯,如果希望将一个命令字符串从客户端送到服务端,服务端通过读取这个字符串完成特定的工作,则编码方式就很重要了,客户端与服务端必须采用一致的编码方式传送命令,否则,网络服务就有可能因为无法解析客户端发送过来的数据而Down掉。

         有关网络编程的内容很有趣,我的下一篇文章会介绍.NET套接字编程。

         好了,这篇介绍字符串编码的短文写完了,希望本文能对读者有所帮助,如有错误,敬请指正。

时间: 2024-09-23 10:07:55

一起谈.NET技术,漫谈.NET开发中的字符串编码的相关文章

ASP和数据库技术在WAP开发中的应用

数据|数据库 利用ASP技术可以开发出动态WAP页面,将ASP技术和数据库技术相结合,则可以开发更为复杂的WAP应用.有关ASP和Web数据库的知识请读者参考专门书籍,我们这里以WML为例,说明ASP和数据库技术在WAP开发中的应用. 在WML程序中使用ASP 在WML程序中使用ASP的规则很简单,只要做到以下5条即可: (1) 声明ASP采用的脚本语言.与ASP中声明的方式类似,格式为: <%@ Language=VBScript %> (2) 声明wml文件类型.这可采用ASP的Respo

AJAX开发技术在PHP开发中的简单应用技巧_AJAX相关

[AJAX介绍] Ajax是使用客户端脚本与Web服务器交换数据的Web应用开发方法.Web页面不用打断交互流程进行重新加裁,就可以动态地更新.使用Ajax,用户可以创建接近本地桌面应用的直接.高可用.更丰富.更动态的Web用户界面. 异步JavaScript和XML(AJAX)不是什么新技术,而是使用几种现有技术--包括级联样式表(CSS).JavaScript. XHTML.XML和可扩展样式语言转换(XSLT),开发外观及操作类似桌面软件的Web应用软件. [AJAX执行原理] 一个Aja

吴佰元:浅谈GPS技术在国土资源中的应用

[硅谷网9月26日讯]原文载于<科技与生活>杂志2012年第14期,文章称,社会在不断的前进与发展,当代信息化的时代里,对于各项技术的发展来讲,都是具有十分良好的前景与较大的发展空间.所以这些新技术的出现,为社会为人们的生活带来很多的便利.因此,在当代我们要充分利用这些新技术.新产品的力量推动我国各项事业不断前进的步伐. 关键词GPS技术:http://www.aliyun.com/zixun/aggregation/30834.html">国土资源:应用 国土资源是国民经济中

AJAX开发技术在PHP开发中的简单应用技巧

[AJAX介绍] Ajax是使用客户端脚本与Web服务器交换数据的Web应用开发方法.Web页面不用打断交互流程进行重新加裁,就可以动态地更新.使用Ajax,用户可以创建接近本地桌面应用的直接.高可用.更丰富.更动态的Web用户界面. 异步JavaScript和XML(AJAX)不是什么新技术,而是使用几种现有技术--包括级联样式表(CSS).JavaScript. XHTML.XML和可扩展样式语言转换(XSLT),开发外观及操作类似桌面软件的Web应用软件. [AJAX执行原理] 一个Aja

一起谈.NET技术,Silverlight开发廋身攻略(一)

相信大家在用Silverlight做应用开发时,随着项目的进展系统功能越来越多,肯定为Xap包越来越大而烦恼,Xap包的大小直接影响应用程序的加载速度,给用户造成不良的用户体验.我也曾为这个问题头痛过,经过在网上查阅资料和项目实践发现可通过以下三种方式给Silverlight应用程廋身. 1.定期检查系统项目模块的引用集,移除多余的引用库 2.图片资源文件,如果不是必须放在资源中的,可以采用按需加载 3.将系统划分成多个Silverlight项目,然后根据需要进行Xap加载 下面我将结合实例来对

一起谈.NET技术,敏捷开发的26条至理名言

我经常收集各种各样的至理名言,最近我重温敏捷开发:真正的问题是什么?下面是一份26条关键原则的清单,以指引敏捷软件开发团队. 1.完整地干完一件事后在开始另一件事:用厨房比喻来说就是:"先上这道菜,再开始做下一道".软件开发的最大问题就是同时开始几件事情,这将不可避免的造成某些工作被废弃,从而造成浪费.专注于一件事:完整地实现其功能:运行测试:编写文档:签入所有,把这当做一项工作完成,然后再开始下一件事. 2.不要破坏构建:非常明显,但必须被包含在任何软件开发建议清单中.程序员在签入之

一起谈.NET技术,MVC开发人员必须拥有的五大工具

1.Chirpy Zippy:Chirpy Zippy是一个Visual Studio插件,它可以混合,缩小和验证JavaScript,样式表和带点文件,它还能够自动更新T4MVC和其它T4模板. 下载地址:http://down.51cto.com/data/136754 过去,在将JavaScript脚本和CSS文件部署到Web服务器之前,我习惯使用压缩工具进行压缩再部署,安装Chirpy后,你的脚本和CSS文件将会基于其名字自动压缩,例如,假设你有一个名叫myscript.js的脚步文件想

一起谈.NET技术,Silverlight开发廋身攻略(二)

继上一篇<Silverlight开发廋身攻略(一)>,在上一节中我主要介绍了在实际开发中怎么动态加载图片资源的方法,并附有实例.在这一节中主要给大家介绍MEF(Managed Extensibility Framework)的知识来实现Xap包的动态加载. Managed Extensibility Framework(MEF)是.NET平台下的一个扩展性管理框架,它是一系列特性的集合,包括依赖注入(DI)以及Duck Typing等.MEF为开发人员提供了一个工具,让我们可以轻松的对应用程序

计算机中的字符串编码、乱码、BOM等问题详解_相关技巧

因为电脑是windows 7系统,开发环境又在linux,经常在linux碰到乱码问题,很是痛苦,于是决定好好了解编码的来龙气脉,并分享个各位,免得出现乱码时不知所措. 是否存在文件编码 在讲解字符编码之前,我们需先明确文件本身没有编码一说,只有文字才有编码的概念,我们通常说某个文件是什么编码,通常是指文件里字符的编码. vim为什么会出现乱码 我在linux下一般使用vim进行文件编辑,发现经常会碰到乱码的情况,那么为什么会出现乱码呢? 首先我们了解下vim编码方面的基础知识,关于编码方面vi