基于字符集、字符编码与HTTP编码解码之万象详解

在日常编写代码过程中,常常会碰到乱码问题,一个典型的情况是浏览网页,如果网站开发者缺少经验,就会带来这种令人头疼的问题。要了解乱码的症结,我们就得从字符集和字符编码说起,先来看看它们到底是什么:
1:字符集:是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
2:字符编码:是一套法则,最常规的理解就是:让程序根据这个法则对应到相应的字符集中将byte[]存取为string。
现在,我们要来看看这些东西在 .NET 中对应的是什么。

一:字符集和字符编码
如果想得到全部的字符集,则使用 System.Text.Encoding.GetEncodings() 方法,以下代码用于列出.Net支持的全部字符集:


代码如下:

foreach (var item in Encoding.GetEncodings())
{
    Console.WriteLine(item.Name);
}

字符串在进行如网络传输等场景时,要先转为 byte[] 。但是,首先,不同的字符编码规则,所转换生成的byte[]是不一样的。所以,再将byte[]转换回string的时候,要依据原先的字符编码规则。有如下几种情况能导致“乱码”的产生:
1:string to byte[] 和 byte[] to string,使用了不同的字符编码规则;
2:byte[] to string 的 时候,当前宿主环境没有对应的字符集;

示例:


代码如下:

string originalString = "Hello Test, 测试!";
byte[] utf8Bytes = Encoding.UTF8.GetBytes(originalString);
string utf8String = Encoding.UTF8.GetString(utf8Bytes);
string errorString = Encoding.ASCII.GetString(utf8Bytes);

观察Encoding类,实际上象上面UTF8这样的属性,只有几个,这些是最常用的字符集,要获取其它,如gb2312这样的字符集,则需要象如下这样来获得:


代码如下:

byte[] gbBytes = Encoding.GetEncoding("gb2312").GetBytes(originalString);
string utf8String = Encoding.GetEncoding("gb2312").GetString(gbBytes);

二:典型应用场景之 HttpWebResponse

很多人都作过页面抓取功能, HttpWebResponse 就会比较熟悉,当然如果不嫌麻烦,也可以用 Socket 实现,但是同时要解析很多属性以及处理象重定向之类的诸多问题。

2.1 http header 和http content是什么?
浏览一个网页,使用很多工具,或者使用.Net中的某些类进行抓取,都给我们结构化为 Http 头和正文这样的信息,其实,当我们发送一个请求,服务器返回给我们的是一串 byte[],我们完全可以自己去从这串 byte[] 解析出 http header 和 http content,它们之间其实仅仅非常简单的以两个 /r/n/ 分割开而已,历史上有著名的CRLF攻击,CR就是\r,LF就是\n,就利用的是这个规则。

2.2 我们如何察看http header,http content?
其实很简单,既然这些都是 byte[] ,所以,我们只要知道这段 byte[] 正确的字符编码规则,就能得到我们所需要看到的 html (html就是字符串而已)。使用 HttpWebResponse 这个类,就能请求一个 url ,该类自动为我们解析出了 httpheader ,有意思的是,它没有给我们解析出 content ,所以,我们需要自己完成正文的byte[] to string。

2.3 http content to string的具体做法
好的,实际上,httpheader  中已经告诉了我们一些字符集编码相关的信息,我们可能感兴趣,以及会混淆的这些http头如下:


代码如下:

Content-Type:WEB 服务器告诉浏览器自己响应的对象的类型和字符集。例如:Content-Type: text/html; charset='gb2312' ;
Content-Encoding:WEB 服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip 。这里我要多说一点,这个 Content-Encoding 的 Http header 会令人混淆,极度容易让人理解成是字符集或字符编码信息;

那么,这些 Http 头在HttpWebResponse 中是怎么代表的呢?


代码如下:

HttpWebResponse.Content-Type对应的是Http头的Content-Type比如"text/html;"后的那个Charset,实际是和HttpWebResponse.Charaterset是一致的。但是如果前者无,则后者

一般会指定一个默认的HttpWebResponse.Charaterset,默认为"iso-8859-1"。
HttpWebResponse.ContentEncoding 代表的是 http头中 Content-Encoding,与此类似的,还有一个http头,为Transfer-Encoding。注意,很恶心的一点是

HttpResponse.ContentEncoding跟HttpWebResponse.ContentEncoding代表的不是一个东西,它和HttpResponse.Charaterset在MSDN上是一致的解释。

根据上面的说法,似乎下面的代码就能得到http content的字符编码规则:


代码如下:

return Encoding.GetEncoding(
   string.IsNullOrEmpty(HttpWebResponse.Charaterset) ?
"iso-8859-1" : HttpWebResponse.Charaterset

但是,这里有一个很重要的但是,如果你尝试从Http头或者HttpWebResponse所给我的这些字符编码信息或属性去解码正文content的话,很可能马上就会迎来一个大大的挫折。我们很可能会发现以下几个可悲的事实:


代码如下:

1:http头的Content-Type中没有charset信息;
2:HttpWebResponse.Charaterset是空的;
3:http头的Content-Type和HttpWebResponse.Charaterset是不一致的;
4:http头的Content-Type和HttpWebResponse.Charaterset是一致的,但是解码还是错的;
5:尝试用"iso-8859-1"解码也是错的。

2.4 为什么还是有乱码问题?BOM能解决一切?

之所以碰到以上问题,其实仅仅是因为,服务器给我们传回来的是byte[],而任何程序员在写服务器端WEB程序的时候,都有可能有意或无意的转码出不规范的byte[]来。所以,如果我们尝试从http头的Content-Type和HttpWebResponse.Charaterset想要得到编码规则,我们就败了,我们败在了有标准,但是没人严格去执行标准。

有一些颇具迷惑性的API试图在告诉我们,使用我你就能得到该流正确的Encoding了,比如,StreamReader.CurrentEncoding,我们可以把HttpWebResponse的GetResponse中读取到

byte[],放置到MemoryStream中,然后利用如下代码:


代码如下:

StreamReader sr = new StreamReader(memoryStream, true)
return sr.CurrentEncoding;

似乎就可以得到Encoding了,其实非也,注意StreamReader构造器的第二个参数,为detectEncodingFromByteOrderMarks。ByteOrderMarks是什么呢?解释如下:


代码如下:

BOM(byte-order mark),即字节顺序标记,它是插入到以UTF-8、UTF16或UTF-32编码Unicode文件开头的特殊标记,用来识别Unicode文件的编 码类型。对于UTF-8来说,BOM并不是必须的,因为BOM用来标记多字节编码文件的编码类型和字节顺序(big-endian或little- endian)。

这表明了什么呢?表明了如果你的字节流未含有BOM,或者即便包含了BOM,但是字节流不是unicode-based的Encoding,则依旧不能得到正确的Encoding,具体我们也可以看StreamReader的源码来得到验证。这个万恶的CurrentEncoding属性并没有告诉你它的前提条件。

2.5 关于本例的一点补充
以上字节流的编码解码,很多地方用了Response做例子,但是,以上解码针对的是非压缩的Response,如果服务器已经对http流进行了压缩(其压缩格式在Content-Encoding中指明了),我们就得先解压缩,再解码Response流,然后再解码正文。考虑到本文的主题,特意剪裁了对于 Response 流的解压过程。

2.6 关于正确解码的尝试

有很多人尝试从byte[]本身去解析和判断编码规则的API,如:codeproject上也有相关的文章,但是可悲的事实是:并没有一种完美的方法来自动判断byte[]的编码规则。还记得我们的浏览器(如IE)的编码设置中的“自动选择”吗,其实这个自动选择的错误率还是蛮高的。所以,对于字节流的生成者,如BS程序开发者,可以通过规范输出:声明charset和编码规范的方式,这样才能让解析者(如浏览器)解析的时候尽可能的少出现乱码。

(0)

相关推荐

  • mysql修改数据库编码(数据库字符集)和表的字符编码的方法

    mysql将表的字符编码转换成utf-8 复制代码 代码如下: alter table tb_anniversary convert to character set utf8; 修改数据库mysql字符编码为UTF8 步骤1:查看当前的字符编码方法 复制代码 代码如下: mysql> show variables like'character%'; +--------------------------+----------------------------+ |Variable_name 

  • 微信开发中mysql字符编码问题

    问题描述:获取code以后不能用ajax请求微信api数据.这个和ajax跨域访问有关系得到用户信息之后存到mysql,发现中文全部变成了??(乱码) 通过上网查阅了相关资料,判断问题根本原因是字符编码问题. 解决方案: 修改配置文件/etc/mysql/my.conf 在[mysql]下 复制代码 代码如下: default-character-set=utf8 在[mysqld]下 复制代码 代码如下: character-set-server=utf8 重启就失败 复制代码 代码如下: j

  • shell实现字符编码转换工具分享

    复制代码 代码如下: #!/bin/bash : << mark转码工具,支持UTF-8转GBK和GBK转UTF-8孔令飞@2012-05-07mark #set -x scode="gbk"dcode="utf-8" function Usage(){ cat << EOFUsage: conv [OPTIONS] [DIR][-u] GBK to UTF-8[-g] UTF-8 to GBKEOF exit 1} #将当前目录下所有普通文

  • Java字符编码解码的实现详解

     字符集基础: Character set(字符集)          字符的集合,也就是,带有特殊语义的符号.字母"A"是一个字符."%"也是一个字符.没有内在数字价值,与 ASC II ,Unicode,甚至是电脑也没有任何的直接联系.在电脑产生前的很长一段时间内,符号就已经存在了. Coded character set(编码字符集)          一个数值赋给一个字符的集合.把代码赋值给字符,这样它们就可以用特定的字符编码集表达数字的结果.其他的编码字符

  • Java反转字符串和相关字符编码的问题解决

    复制代码 代码如下: public String reverse(char[] value){       for (int i = (value.length - 1) >> 1; i >= 0; i--){           char temp = value[i];           value[i] = value[value.length - 1 - i];           value[value.length - 1 - i] = temp;       }     

  • php字符编码转换之gb2312转为utf8

    在php中字符编码转换我们一般会用到iconv与mb_convert_encoding进行操作,但是mb_convert_encoding在转换性能上比iconv要差很多哦.string iconv ( string in_charset, string out_charset, string str ) 注意:第二个参数,除了可以指定要转化到的编码以外,还可以增加两个后缀://TRANSLIT 和 //IGNORE,其中 //TRANSLIT 会自动将不能直接转化的字符变成一个或多个近似的字符

  • 基于字符集、字符编码与HTTP编码解码之万象详解

    在日常编写代码过程中,常常会碰到乱码问题,一个典型的情况是浏览网页,如果网站开发者缺少经验,就会带来这种令人头疼的问题.要了解乱码的症结,我们就得从字符集和字符编码说起,先来看看它们到底是什么: 1:字符集:是一个系统支持的所有抽象字符的集合.字符是各种文字和符号的总称,包括各国家文字.标点符号.图形符号.数字等. 2:字符编码:是一套法则,最常规的理解就是:让程序根据这个法则对应到相应的字符集中将byte[]存取为string. 现在,我们要来看看这些东西在 .NET 中对应的是什么. 一:字

  • 对python中url参数编码与解码的实例详解

    一.简介 在python中url,对于中文等非ascii码字符,需要进行参数的编码与解码. 二.关键代码 1.url编码 对字符串编码用urllib.parse包下的quote(string, safe='/', encoding=None, errors=None)方法. 对json格式的参数名和值编码,用urllib.parse包下的 urlencode(query, doseq=False, safe='', encoding=None, errors=None, quote_via=qu

  • IDEA安装阿里巴巴编码规范插件的两种方式详解(在线安装和离线安装)

    目录 1.在线安装: 2.离线安装: IDEA安装阿里巴巴编码规范插件的两种方式:在线安装和离线安装. 1.在线安装: 打开file-settings-Plugins.如图: 搜索到点击右边的install按钮,即可在线安装. 在线安装非常简单. 2.离线安装: 下载插件地址:https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines/versions或者 链接: https://pan.baidu.com/

  • Go Java算法之解码方法示例详解

    目录 解码方法 方法一:动态规划(Java) 方法二:动态规划——优化(go) 解码方法 一条包含字母 A-Z 的消息通过以下映射进行了 编码 : 'A' -> "1" 'B' -> "2" ... 'Z' -> "26" 要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法).例如,"11106" 可以映射为: "AAJF" ,将消息分组为 (1 1 1

  • Python JSON编解码方式原理详解

    这篇文章主要介绍了Python JSON编解码方式原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 概念 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写.在日常的工作中,应用范围极其广泛.这里就介绍python下它的两种编解码方法: 使用json函数 使用 JSON 函数需要导入 json 库:import json.函数含义: 源码解析: # coding= utf-8 #

  • 基于Python数据可视化利器Matplotlib,绘图入门篇,Pyplot详解

    Pyplot matplotlib.pyplot是一个命令型函数集合,它可以让我们像使用MATLAB一样使用matplotlib.pyplot中的每一个函数都会对画布图像作出相应的改变,如创建画布.在画布中创建一个绘图区.在绘图区上画几条线.给图像添加文字说明等.下面我们就通过实例代码来领略一下他的魅力. import matplotlib.pyplot as plt plt.plot([1,2,3,4]) plt.ylabel('some numbers') plt.show() 上图是我们通

  • PHP基于phpqrcode类生成二维码的方法示例详解

    HP QR Code是一个PHP二维码生成类库,利用它可以轻松生成二维码,官网提供了下载和多个演示demo,查看地址: http://phpqrcode.sourceforge.net/ 下载官网提供的类库后,只需要使用phpqrcode.php就可以生成二维码了,当然您的PHP环境必须开启支持GD2. phpqrcode.php提供了一个关键的png()方法,其中 参数$text表示生成二位的的信息文本: 参数$outfile表示是否输出二维码图片 文件,默认否: 参数$level表示容错率,

  • shell字符截取命令之cut命令的实例详解

    shell字符截取命令之cut命令的实例详解 一 语法 cut [选项] 文件名 选项: -f 列号:提取第几列 -d 分隔符:按照指定分隔符分割列 二 实战 [root@localhost ~]# cat student.txt 1 furong F 85 2 fengj F 60 3 cang F 70 [root@localhost ~]# cut -f 2 student.txt furong fengj cang [root@localhost ~]# cut -f 2,4 stude

  • 基于spring boot 1.5.4 集成 jpa+hibernate+jdbcTemplate(详解)

    1.pom添加依赖 <!-- spring data jpa,会注入tomcat jdbc pool/hibernate等 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <

  • 基于java集合中的一些易混淆的知识点(详解)

    (一) collection和collections 这两者均位于java.util包下,不同的是: collection是一个集合接口,有ListSet等常见的子接口,是集合框架图的第一个节点,,提供了对集合对象进行基本操作的一系列方法. 常见的方法有: boolean add(E e) 往容器中添加元素:int size() 返回collection的元素数:boolean isEmpty() 判断此容器是否为空: boolean contains(Object o) 如果此collecti

随机推荐