家有新丁初懂事

Hanu似乎着急要表现他比其他孩子更能读懂大人的指令或者语言,煞是好玩。

1. 以逗孩子哭为乐事
不知道从什么时候起,Hanu已经能够读懂大人的表情,并很快启动相同的表情。比如我们对他笑,他也跟着傻笑。如果我们表现难过的要哭的脸,他马上也能做出一副要哭的样子,有时候甚至真的跟着大人的假哭一起真痛哭出来。随即,大人收住假哭转为哈哈大笑时,留下Hanu一个人莫名其妙的抹眼泪,写这些时,觉得很不好意思,以逗孩子哭为乐事,不好。

2. 搬凳子吃饭
大概民以食为天,Hanu对吃也是领会很快的。他有个专门的婴儿餐椅。每当吃饭时,都是由大人将其抱起放入婴儿餐椅,然后饭后再报出,将餐椅推放会角落里。现在Hanu已经能够根据指令说,小有拿凳子/椅子,我们吃饭了,然后这家伙就摇摇晃晃的去拿他的餐椅,自然搬不动,于是就各种推啊、拉啊的,设法将餐椅弄到指定的位置,然后开始就餐。餐毕,自然也是各种推啊、拉啊的将餐椅弄回原来的角落里。

3. 扔垃圾
可能是很早就不停的训练他,小有把这个/那个给外公/外婆/妈妈 等这样的任务。现在可以给他指令如,小有把这个扔垃圾桶去。这家伙就真拿着要扔的东西,七拐八拐地找到垃圾桶,掀开盖子,然后放进去要扔的东西,然后盖上盖子。如果垃圾桶碰巧是在另外一个物体下面或者背后,他会先把它弄到外面,然后放好了垃圾再弄到里面,真真归位的很好。拿痰盂这样的任务,他也做得很棒,去洗手间将痰盂找到,拿过来,掀开盖子,坐上,虽然不太懂得用,但做得有模有样,甚至自己纸尿裤要换时,也拉拉大人的手,然后指着痰盂,用他仅会的一个字“呃”,重复说着半天……

4. 坐好了,吃奶粉
每当要吃奶粉时,大人说,小有坐好了等着吃奶粉了,然后这家伙就自动找到经常吃奶粉的位子,方正地做好等着奶粉冲好了吃。理解的相当准确,做得也非常好。还有比较协调的就是,进屋找人时,在进屋之前,弯腰透过门帘下方先探寻一番,如果还没有,他还会找找门的背后。还有他甚至能从图画表中,找到金鱼、棉袄和小马等图画…..

5. 知道犯了严重错误
有天下班回家,小家伙就让我抱,然后去到我们的卧房,不停地指着桌子上的一个手机,反复地用你他仅会的那个万能字“呃,e,呃,e”地跟我说,我看到手机屏幕碎花了。后来经过大人说才知道,当天他拿着手机摔地上屏幕磕坏了,可能当时大人批评了他,这家伙等我一回家,马上要告诉我这件严重的事情….

6. 精准地抓握和捡拾
可能是有意识的训练他,Hanu现在能够在地上找到米粒并捡起来跟我。昨天在万达宝贝娱乐场,这家伙居然从地毯缝里扣半天,捡两个黑米粒一样的东西给我,真叫有耐心。孩子这个阶段手眼配合与协调的确是训练的,再比如他知道把汽车模型上的掉的轮子找到,并一起给我,然后示范似的告诉我,那个掉的轮子应该安在缺失的地方,但他还不会,求助于我….

7. 好玩的摇头
小家伙已经懂得了拍手和作揖,但还不会摇头和点头。上周末教他,摇头,惹得我们哈哈大笑半天。原因是他学摇头时,先将脑袋扭到一个方向,然后像机器人一样,一档一档地只往一个方向转,自然不几下就转不动了….

8. 也有不耐烦
不耐烦时,他居然会耍赖。往地上一躺,还故意伸伸脖子,将脸贴地面上。还有就是睡眠时,一定要个自己觉得不错的人,比如外婆,或者妈妈,如果不是,则一定要闹上一次,即便困得迷迷瞪瞪,将身边的人脸扭过来看看,不是想要的人的脸,则要哭喊出来。
不管谁抱,习惯性的将手探入领口,这是要干什么他?
不管几点睡,夜里多数情况下要加餐,真那么饿吗?

家有新丁初懂事,各种热闹各种累,为人父母,始知百善孝为先。这传统文化里,是人类在漫长历史中与自然环境做斗争中,不断繁衍生息所总结的经验。或许以后人能够依靠机器人获得衣食无忧,但无法取代这样一个过程,看一个孩子从襁褓之中成长为高大的青年,看一个青壮中年,逐渐老化、势弱至离开….

每一步,每一代,每一环,都不仅仅是物质的供养,更有心灵的抚慰、知识的传承和共同的面对、担当、共勉和分享。

若此,生生不息。

罗大佑 作词、谱曲,苏芮 演唱的,1982年电影 《搭错车》 的主题曲 《酒干倘卖无》(-R/I2Sf ,-R/i2Si  ):

…..

从来不需要想起
永远也不会忘记
没有天那有地
没有地哪有家
没有家哪有你
没有你哪有我
假如你不曾养育我
给我温暖的生活
假如你不曾保护我
我的命运将会是什么
是你抚养我长大
陪我说第一句话
是你给我一个家
让我与你共同拥有它

….
借此追忆 母亲,愿 她 在远方安好!
.
发表在 体育健身, 社会生活 | 标签为 , , , | 一条评论

中国国民党Apache Softwares亮月亮科技OpenAI ChatGPT

分类、层级与编码

在使用技术技术解决现实问题时,为了描述客观世界,有时候需要对一个集合中的对象进行分类、分层级。比如一群人,分类可以是男、女、老、友,分层级可以是按年龄少儿、青年、老人,少儿又可层级为新生儿、婴儿和小儿等。

为参与计算,通常会对这些分类和层级进行编码。常见的编码分为两类:一类是有限分类及层级,每层、每类的数量是有限的,比如常见的身份证编码,中国大陆的居民身份证号码由18位组成,前17位为数字,后一位多数人为数字,少数人为字母“X”;另外一类是无限分类及层级,比如计算机中常用的二叉树。

在有限分类及层级中,预先知道对象集合的规模,比如身份证的前6位是行政区编码,

110000    北京市
110100      市辖区
110101        东城区

这6位数分为三组,每两位为一层,如果只用数字来表示的话,意味着在每一层可以画出100个类别来,00-99。上面可以看到,北京市在省区的规划中代码是11,其中市辖区被记为01,也即1101,而东城区作为市辖区的下一级被记为01,也即110101。 这样计算机读取110101,展示给人们的就是北京市市辖区东城区,非直辖市的情况可以参考 -国家统计局 的 行政区划 -R/L2Sk 。与身份证类似,一些地理信息系统,也是采用这种编码的方式类记录地址信息到地址编码的。

如果某一层出现第101个类别怎么办?

有两种思路,一种是继续使用有限分类及层级,扩充每一级上的编码,比如上面的身份证编码,如果出现101个类别,则在每一层又2位扩增到3位,问题得以解决,但无法向前兼容,问题很大。

另外一个思路时,如无无法估量对象集合的数量,或者数量是动态变化调整的,就需要用无限分类及层级。这种分类不去规划每层的宽度和容量,而是扁平的设计成每层每个类别,只需记住自己的“父类”即可,这样各找个家,也可以将一个集合有机的串起来。

计算技术中的数据结构二叉树就是这样来设计,每层只知道parent是哪即可。同样的,我们常见的论坛(BBS)在设计论坛、子论坛和帖子编号时,也是基于这样的思路,因为无法估量某个论坛、子论坛到底会有多少个帖子。

boardId   parentBoardId
threadId  boardId
子    父
孙    子
曾孙   孙

使用这种对应关系即可串起来。

无限分类与层级结构简单,伸缩性极好,但也有不足,比如统计比较困难。比如在有限分类及层级中,只要按一定的分组就可以理出其下所有子类,如11开头的身份证都是北京的,而使用无限分类及层级的话,需要检索出所有parentId为11的记录。在参与进一步的计算时,前者可能是一步,而后者需要两步及以上才能完成。

宛如一个持有代码110101的人和一个持有01代码但直到其父是01的人同时站在面前,前者一看就知道其父、其祖,而后者只能回答其父,如果找其祖,需要使用后一个01再检索一次。

前者的优势是每次所携带的信息容量较大,但同时也耗费更多的空间,实现以空间换时间。

发表在 社会生活, 计算机技术 | 标签为 , , | 4条评论

Apache Softwares亮月亮科技OpenAI ChatGPTPdf Drive

shell for loop循环打印逐年的日期

这篇文章

Bash For Loop Examples (-R/o2SZ )

极好地解释了在Shell中使用for loop循环的用法。

循着这个说明,我写了循环打印逐年的日期的样例:

#!/bin/sh
years=”2013 2014 2015″;
for year in $years
do
for month in {1..12}
do
for day in {1..31}
do
echo “date:”$year””$month””$day;
done

done

done

 

发表在 编程技术, 计算机技术 | 留下评论

中国国民党Apache Softwares亮月亮科技OpenAI ChatGPT

转:中文编码杂谈

 

编码问题的例子

在windows自带的notepad(记事本)程序中输入“联通”两个字,保存后再次打开,会发现“联通”不见了,代之以“��ͨ”的乱码。这是windows平台上典型的中文编码问题。即文件保存的时候是按照ANSI编码(其实就是GB2312,后面会详细介绍)保存,打开的时候程序按照UTF-8方式对内容解释,于是就出现了乱码。避免乱码的方式很简单,在“文件”菜单中选择“打开”命令,选择保存的文件,然后选择“ANSI”编码,此时就能看到久违的“联通”两个字了。

在Linux平台上如果使用cat等命令查看文件中的中文内容时,可能出现乱码。这也是编码的问题。简单的说是文件时按照A编码保存,但是cat命令按照当前Locale设定的B编码去查看,在B和A不兼容的时候就出现了乱码。

为什么写这篇文章

中文编码由于历史原因牵扯到不少标准,在不了解的时候感觉一头雾水;但其实理解编码问题并不需要你深入了解各个编码标准,只要你明白了来龙去脉,了解了关键的知识点,就能分析和解决日常开发工作中碰到的大部分编码问题。有感于我看过的资料和文章要么不够全面,要么略显枯燥,所以通过这篇文章记录下笔者在日常工作中碰到的中文编码原理相关问题,目的主要是自我总结,如果能给读者提供一些帮助那就算是意外之喜了。由于严谨的编码标准对我来说是无趣的,枯燥的,难以记忆的,本文尝试用浅显易懂的生活语言解释中文编码相关的(也可能不相关的)一些问题,这也是为什么取名杂谈的原因。本文肯定存在不规范不全面的地方,我会在参考资料里给出官方文档的链接,也欢迎读者在评论中提出更好的表达方式&指出错误,不胜感激。

对编码问题的理解我认为分为三个层次,第一个层次:概念,知道各个编码标准的应用场景,了解之间的差异,能分析和解决常见的一些编码问题。第二个层次:标准,掌握编码的细节,如编码范围,编码转换规则,知道这些就能自行开发编码转换工具。第三个层次,使用,了解中文的编码2进制存储,在程序开发过程中选择合理的编码并处理中文。为了避免让读者陷入编码标准的黑洞无法脱身(不相信?看看unicode的规范就明白我的意思了),同时由于编码查询&转换工具等都有现成工具可以使用,本文只涉及第一个层次,不涉及第二层次,在第三层次上会做一些尝试。在本文的最后提供了相关链接供对标准细节感兴趣的同学继续学习。最后,本文不涉及具体软件的乱码问题解决,如ssh,shell,vim,screen等,这些话题留给剑豪同学专文阐述。

一切都是因为电脑不识字

电脑很聪明,可以帮我们做很多事情,最开始主要是科学计算,这也是为什么电脑别名计算机。电脑又很笨,在她的脑子里只有数字,即所有的数据在存储和运算时都要使用二进制数表示。这在最初电脑主要用来处理大量复杂的科学计算时不是什么大问题但是当电脑逐步走入普通人的生活时,情况开始变遭了。办公自动化等领域最主要的需求就是文字处理,电脑如何来表示文字呢?这个问题当然难不倒聪明的计算机科学家们,用数字来代表字符呗。这就是“编码”。

英文的终极解决方案:ASCII

每个人都可以约定自己的一套编码,只要使用方之间了解就ok了。比如说咱俩约定0×10表示a,0×11表示b。在一开始也的确是这样的,出现了各式各样的编码。这样有两个问题:1.各个编码的字符集不一样,有的多,有的少。2.相同字符的编码也不一样。你这里a是0×10.他那里a可能是0×30。于是你保存的文件他就不能直接用,必须要转换编码。随着沟通范围的扩大,采用不同编码的人们互相通信就乱套了,这就是我们常说的:鸡同鸭讲。如果要避免这种混乱,那么大家就必须使用相同的编码规则,于是美国有关的标准化组织就出台了ASCII(American Standard Code for Information Interchange)编码,统一规定了英文常用符号用哪些二进制数来表示。ASCII是标准的单字节字符编码方案,用于基于文本的数据。

ASCII最初是美国国家标准,供不同计算机在相互通信时用作共同遵守的西文字符编码标准,已被国际标准化组织(International Organization for Standardization, ISO)定为国际标准,称为ISO 646标准。适用于所有拉丁文字字母。ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数来表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符。而最高位为1的另128个字符(80H—FFH)被称为“扩展ASCII”,一般用来存放英文的制表符、部分音标字符等等的一些其它符号。

其中:0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字,65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。

ascii 编码表

现在所有使用英文的电脑终于可以用同一种编码来交流了。理解了ASCII编码,其他字母型的语言编码方案就触类旁通了。

一波三折的中文编码

第一次尝试:GB2312

ASCII这种字符编码规则显然用来处理英文没有什么问题,它的出现极大的促进了信息在西方尤其是美国的传播和交流。但是对于中文,常用汉字就有6000以上,ASCII 单字节编码显然是不够用。为了粉碎美帝国主义通过编码限制中国人民使用电脑的无耻阴谋,中国国家标准总局发布了GB2312码即中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码字符集——基本集》,1981年5月1日实施,通行于大陆。GB2312字符集中除常用简体汉字字符外还包括希腊字母、日文平假名及片假名字母、俄语西里尔字母等字符,未收录繁体中文汉字和一些生僻字。 EUC-CN可以理解为GB2312的别名,和GB2312完全相同。

GB2312是基于区位码设计的,在区位码的区号和位号上分别加上A0H就得到了GB2312编码。这里第一次提到了“区位码”,我就连带把下面这几个让人摸不到头脑的XX码一锅端了吧:

区位码,国标码,交换码,内码,外码

区位码:就是把中文常用的符号,数字,汉字等分门别类进行编码。区位码把编码表分为94个区,每个区对应94个位,每个位置就放一个字符(汉字,符号,数字都属于字符)。这样每个字符的区号和位号组合起来就成为该汉字的区位码。区位码一般用10进制数来表示,如4907就表示49区7位,对应的字符是“学”。区位码中01-09区是符号、数字区,16-87区是汉字区,10-15和88-94是未定义的空白区。它将收录的汉字分成两级:第一级是常用汉字计3755个,置于16-55区,按汉语拼音字母/笔形顺序排列;第二级汉字是次常用汉字计3008个,置于56-87区,按部首/笔画顺序排列。在网上搜索“区位码查询系统”可以很方便的找到汉字和对应区位码转换的工具。为了避免广告嫌疑和死链,这里就不举例了。

国标码: 区位码无法用于汉字通信,因为它可能与通信使用的控制码(00H~1FH)(即0~31,还记得ASCII码特殊字符的范围吗?)发生冲突。于是ISO2022规定每个汉字的区号和位号必须分别加上32(即二进制数00100000,16进制20H),得到对应的国标交换码,简称国标码,交换码,因此,“学”字的国标交换码计算为:

1
2
3
4
  00110001 00000111
+ 00100000 00100000
 -------------------
  01010001 00100111

用十六进制数表示即为5127H。

交换码:即国标交换码的简称,等同上面说的国标码。

内码:由于文本中通常混合使用汉字和西文字符,汉字信息如果不予以特别标识,就会与单字节的ASCII码混淆。此问题的解决方法之一是将一个汉字看成是两个扩展ASCII码,使表示GB2312汉字的两个字节的最高位都为1。即国标码加上128(即二进制数10000000,16进制80H)这种高位为1的双字节汉字编码即为GB2312汉字的机内码,简称为内码。20H+80H=A0H。这也就是常说的在区位码的区号和位号上分别加上A0H就得到了GB2312编码的由来。

1
2
3
4
  00110001 00000111
+ 10100000 10100000
   -------------------
  11010001 10100111

用十六进制数表示即为D1A7H。

外码:机外码的简称,就是汉字输入码,是为了通过键盘字符把汉字输入计算机而设计的一种编码。 英文输入时,相输入什么字符便按什么键,外码和内码一致。汉字输入时,可能要按几个键才能输入一个汉字。 汉字输入方案有成百上千个,但是这千差万别的外码输入进计算机后都会转换成统一的内码。

最后总结一下上面的概念。中国国家标准总局把中文常用字符编码为94个区,每个区对应94个位,每个字符的区号和位号组合起来就是该字符的区位码, 区位码用10进制数来表示,如4907就表示49区7位,对应的字符是“学”。 由于区位码的取值范围与通信使用的控制码(00H~1FH)(即0~31)发生冲突。每个汉字的区号和位号分别加上32(即16进制20H)得到国标码,交换码。“学”的国标码为5127H。由于文本中通常混合使用汉字和西文字符,为了让汉字信息不会与单字节的ASCII码混淆,将一个汉字看成是两个扩展ASCII码,即汉字的两个字节的最高位置为1,得到的编码为GB2312汉字的内码。“学”的内码为D1A7H。无论你使用什么输入法,通过什么样的按键组合把“学”输入计算机,“学”在使用GB2312(以及兼容GB2312)编码的计算机里的内码都是D1A7H。

第二次尝试:GBK

GB2312的出现基本满足了汉字的计算机处理需要,但由于上面提到未收录繁体字和生僻字,从而不能处理人名、古汉语等方面出现的罕用字,这导致了1995年《汉字编码扩展规范》(GBK)的出现。GBK编码是GB2312编码的超集,向下完全兼容GB2312,兼容的含义是不仅字符兼容,而且相同字符的编码也相同,同时在字汇一级支持ISO/IEC10646—1和GB 13000—1的全部中、日、韩(CJK)汉字,共计20902字。GBK还收录了GB2312不包含的汉字部首符号、竖排标点符号等字符。CP936和GBK的有些许差别,绝大多数情况下可以把CP936当作GBK的别名。

第三次尝试:GB18030

GB18030编码向下兼容GBK和GB2312。GB18030收录了所有Unicode3.1中的字符,包括中国少数民族字符,GBK不支持的韩文字符等等,也可以说是世界大多民族的文字符号都被收录在内。GBK和GB2312都是双字节等宽编码,如果算上和ASCII兼容所支持的单字节,也可以理解为是单字节和双字节混合的变长编码。GB18030编码是变长编码,有单字节、双字节和四字节三种方式。

其实,这三个标准并不需要死记硬背,只需要了解是根据应用需求不断扩展编码范围即可。从GB2312到GBK再到GB18030收录的字符越来越多即可。万幸的是一直是向下兼容的,也就是说一个汉字在这三个编码标准里的编码是一模一样的。这些编码的共性是变长编码,单字节ASCII兼容,对其他字符GB2312和GBK都使用双字节等宽编码,只有GB18030还有四字节编码的方式。这些编码最大的问题是2个。1.由于低字节的编码范围和ASCII有重合,所以不能根据一个字节的内容判断是中文的一部分还是一个独立的英文字符。2.如果有两个汉字编码为A1A2B1B2,存在A2B1也是一个有效汉字编码的特殊情况。这样就不能直接使用标准的字符串匹配函数来判断一个字符串里是否包含某一个汉字,而需要先判断字符边界然后才能进行字符匹配判断。

最后,提一个小插曲,上面讲的都是大陆推行的汉字编码标准,使用繁体的中文社群中最常用的电脑汉字字符集标准叫大五码(Big5),共收录13,060个中文字,其中有二字为重覆编码(实在是不应该)。Big5虽普及于中国的台湾、香港与澳门等繁体中文通行区,但长期以来并非当地的国家标准,而只是业界标准。倚天中文系统、Windows等主要系统的字符集都是以Big5为基准,但厂商又各自增删,衍生成多种不同版本。2003年,Big5被收录到台湾官方标准的附录当中,取得了较正式的地位。这个最新版本被称为Big5-2003。

天下归一Unicode

看了上面的多个中文编码是不是有点头晕了呢?如果把这个问题放到全世界n多个国家n多语种呢?各国和各地区自己的文字编码规则互相冲突的情况全球信息交换带来了很大的麻烦。

要真正彻底解决这个问题,上面介绍的那些通过扩展ASCII修修补补的方式已经走不通了,而必须有一个全新的编码系统,这个系统要可以将中文、日文、法文、德文……等等所有的文字统一起来考虑,为每一个文字都分配一个单独的编码。于是,Unicode诞生了。Unicode(统一码、万国码、单一码)为地球上(以后会包括火星,金星,喵星等)每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。在Unicode里,所有的字符被一视同仁,汉字不再使用“两个扩展ASCII”,而是使用“1个Unicode”来表示,也就是说,所有的文字都按一个字符来处理,它们都有一个唯一的Unicode码。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位(码位就是可以分配给字符的数字)。

提到Unicode不能不提UCS(通用字符集Universal Character Set)。UCS是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。UCS-2用两个字节编码,UCS-4用4个字节编码。Unicode是由unicode.org制定的编码机制,ISO与unicode.org是两个不同的组织, 虽然最初制定了不同的标准; 但目标是一致的。所以自从unicode2.0开始, unicode采用了与ISO 10646-1相同的字库和字码, ISO也承诺ISO10646将不会给超出0x10FFFF的UCS-4编码赋值, 使得两者保持一致。大家简单认为UCS等同于Unicode就可以了。

在Unicode中:汉字“字”对应的数字是23383。在Unicode中,我们有很多方式将数字23383表示成程序中的数据,包括:UTF-8、UTF-16、UTF-32。UTF是“UCS Transformation Format”的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。例如,“汉字”对应的数字是0x6c49和0x5b57,而编码的程序数据是:

1
2
3
BYTE data_utf8[] = {0xE6, 0xB1, 0x89, 0xE5, 0xAD, 0x97}; // UTF-8编码
WORD data_utf16[] = {0x6c49, 0x5b57}; // UTF-16编码
DWORD data_utf32[] = {0x6c49, 0x5b57}; // UTF-32编码

这里用BYTE、WORD、DWORD分别表示无符号8位整数,无符号16位整数和无符号32位整数。UTF-8、UTF-16、UTF-32分别以BYTE、WORD、DWORD作为编码单位。“汉字”的UTF-8编码需要6个字节。“汉字”的UTF-16编码需要两个WORD,大小是4个字节。“汉字”的UTF-32编码需要两个DWORD,大小是8个字节。根据字节序的不同,UTF-16可以被实现为UTF-16LE或UTF-16BE,UTF-32可以被实现为UTF-32LE或UTF-32BE。

下面介绍UTF-8、UTF-16、UTF-32、BOM。

UTF-8

UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:

Unicode编码(16进制) UTF-8 字节流(二进制)
000000 – 00007F 0xxxxxxx
000080 – 0007FF 110xxxxx 10xxxxxx
000800 – 00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000 – 10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0×00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。总结了一下规律:UTF-8的第一个字节开始的1的个数代表了总的编码字节数,后续字节都是以10开始。由上面的规则可以清晰的看出UTF-8编码克服了中文编码的两个问题。

例1:“汉”字的Unicode编码是0x6C49。0x6C49在0×0800-0xFFFF之间,使用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

例2:Unicode编码0x20C30在0×010000-0x10FFFF之间,使用用4字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。

UTF-16

UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下:  如果U<0×10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。中文范围 4E00-9FBF,所以在UTF-16编码里中文2个字节编码。如果U≥0×10000,我们先计算U’=U-0×10000,然后将U’写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

UTF-32

UTF-32编码以32位无符号整数为单位。Unicode的UTF-32编码就是其对应的32位无符号整数。

字节序

根据字节序(对字节序不太了解的同学请参考http://en.wikipedia.org/wiki/Endianness)的不同,UTF-16可以被实现为UTF-16LE(Little Endian)或UTF-16BE(Big Endian),UTF-32可以被实现为UTF-32LE或UTF-32BE。例如:

Unicode编码 UTF-16LE UTF-16BE UTF-32LE UTF-32BE
0x006C49 49 6C 6C 49 49 6C 00 00 00 00 6C 49
0x020C30 43 D8 30 DC D8 43 DC 30 30 0C 02 00 00 02 0C 30

那么,怎么判断字节流的字节序呢?Unicode标准建议用BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被作为BOM的字符”零宽无中断空格”。这个字符的编码是FEFF,而反过来的FFFE(UTF-16)和FFFE0000(UTF-32)在Unicode中都是未定义的码位,不应该出现在实际传输中。下表是各种UTF编码的BOM:

UTF编码 Byte Order Mark
UTF-8 EF BB BF
UTF-16LE FF FE
UTF-16BE FE FF
UTF-32LE FF FE 00 00
UTF-32BE 00 00 FE FF

总结一下,ISO与unicode.org都敏锐的意识到只有为世界上每种语言中的每个字符设定统一并且唯一的二进制编码才能彻底解决计算机世界信息交流中编码冲突的问题。由此诞生了UCS和unicode,而这两个规范是一致的。在Unicode里,所有的字符被一视同仁,也就是说,所有的文字都按一个字符来处理,它们都有一个唯一的Unicode码。UTF-8、UTF-16、UTF-32分别定义了怎样将Unicode定义的数字转换成程序数据。UTF-8以字节为单位对Unicode进行编码,一个英文字符占1个字节,汉字占3个字节;UTF-16以16位无符号整数为单位对Unicode进行编码,中文英文都占2个字节;UTF-32以32位无符号整数为单位对Unicode进行编码,中文英文都占4个字节。可以在http://www.unicode.org/charts/unihan.html 查看汉字的unicode码以及UTF-8、UTF-16、UTF-32编码。

中文二进制存储

介绍了这么多的编码知识,真正的文件内容是什么样子的呢?下面我们就通过实验看看在笔者Linux机器上 “中文”这两个字在不同的编码下保存的文件内容。下面是我的实验过程,有兴趣的同学可以在自己的机器上重做一下。window平台上的情况类似这里就不赘述了。

实验需要需要使用2个工具:

  1. od 查看文件内容:http://www.gnu.org/software/coreutils/manual/html_node/od-invocation.html
  2. iconv 编码转换工具:http://www.gnu.org/software/libiconv/
汉字 Unicode(ucs-2)10进制表示 Utf-8 Utf-16 Utf32 区位码 GB2312/GBK/GB18030
中 20013 E4 B8 AD 4E2D 00004E2D 5448 D6D0
文 25991 E6 96 87 6587 00006587 4636 CEC4

机器环境:
os: Red Hat Enterprise Linux AS release 4
Cpu: Intel(R) Xeon(R) CPU
locale:LC_ALL=zh_CN.utf-8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//生成utf8编码下的文件
echo –n "中文" > foo.utf8
//检查foo的内容:
od -t x1 foo.utf8
0000000 e4 b8 ad e6 96 87
//转换为utf16编码
iconv -f utf-8 -t utf-16 foo.utf8 > foo.utf16
//查看foo.utf16内容
od -t x1 foo.utf16
0000000 ff fe 2d 4e 87 65
Ff fe是BOM(还记得吗?通过BOM来字节流的字节序),其余部分的确是UTF-16LE编码的内容
//转换为utf32编码
iconv -f utf-16 -t utf-32 foo.utf16 > foo.utf32
//查看foo.utf32内容
od -t x1 foo.utf32
0000000 ff fe 00 00 2d 4e 00 00 87 65 00 00
Ff fe是BOM,的确是UTF-32LE编码的内容
//转换为gb2312编码
iconv -f utf-8 -t gb2312 foo.txt > foo.gb2312
od -t x1 foo.gb2312
0000000 d6 d0 ce c4
//转换为GBK编码
iconv -f utf-8 -t gbk foo.txt > foo.gbk
od -t x1 foo.gbk
0000000 d6 d0 ce c4
//转换为GB18030编码
iconv -f utf-8 -t gb18030 foo.txt > foo.gb18030
od -t x1 foo.gb18030
0000000 d6 d0 ce c4

C语言中文处理

先明确一个概念:程序内部编码和程序外部编码。程序内部编码指的是中文字符在程序运行时在内存中的编码形式。程序外部编码则是中文字符在存储或者传输时的编码形式。程序外部编码的最直观的例子就是当把中文存储到硬盘文件中时选择的编码。

根据程序内部编码和程序外部编码是否一致,C/C++的中文处理有两种常见的方式:

  1. 内外编码相同。输入输出时不需要考虑编码转换,程序内部处理时把中文字符当做普通的2进制数据流进行处理。
  2. 内外编码不同。输入输出的时候根据应用需要选择合适的编码格式进行编码转换;程序内部统一编码处理。

方法1的优点不言而喻,由于内外统一,不需要进行转换。不足是如果不是C标准库支持的编码方式,那么字符串处理函数需要自己实现。比如说标准strlen函数不能计算中文编码&UTF-8等的字符串长度,而需要根据编码标准自行实现。GBK等中文编码除了计算字符串长度的函数外,字符串匹配函数也要自己实现(原因看上文中文编码总结)。当需要支持的编码格式不断增多时,处理函数的开发和维护就需要付出更大的代价。

方法2针对方法1的不足加以改进。在程序内部可以优先选择C标准库支持的编码方式,或者根据需要自己实现对某一特定编码格式的完整支持,这样任何编码都可以先转换为支持的编码,代码通用性比较好。

那么C标准库对中文编码的支持如何呢?目前Linux平台一般使用GNU C library,内建了对单字节的char和宽字符wchar_t的支持。Char大家都很熟悉了,处理中文需要的wchar_t要重点介绍一下。从实现上来说在linux平台上可以认为wchar_t是4byte的int,内部存储字符的UTF32编码。由于标准库已经内建了对wchar_t比较完备的支持,如使用wcslen 计算字符串长度,使用wcscmp进行字符串比较等等。所以比较简单的方式是使用上面的方法2,同时选择wchar_t作为内部字符的表示。做到这一点还是比较容易的,在输入输出的时候通过mbrtowc/wcrtomb 进行单个字符的内外编码转换,以及通过mbsrtowcs/wcsrtombs 进行字符串的内外编码转换即可。这里需要注意两点:

  1. 代码中字符串常量的表示不同。举例说明:Char c=’a’; Wchar_t wc=L’中’;
  2. 上面两组函数的转换是依赖locale设置的,即locale决定了外部编码的类型。确切的说是LC_CTYPE决定了外部编码的类型。默认情况下程序启动时使用标准“C”locale,而不是LC系列的环境变量指定的。所以需要首先调用下面的函数:setlocale (LC_ALL, “”);这样程序就使用了用户通过设置LC系列环境变量选择的Locale。

关于locale的话题比较大,这里就不深入了,留待下一篇文章吧.

上面的方法很完美,是吗?不是吗?得到这么多的好处不是无代价的,最明显的代价就是内存,任何一个字符,不管中文还是英文如果保持在wchar_t里就需要4个byte,就这一个理由就足以限制了这个方案在关注内存使用的应用场景下的使用。

Python的中文处理

对Python来说由于内建unicde的支持,所以采用输入输出的时候进行转换,内部保持unicode的方式使用是个不错的方案。http://docs.python.org/tutorial/introduction.html#unicode-strings这里作为起点,有兴趣的同学自学吧。

编码选择建议:

  1. 只有英文:毫不犹豫选择内外编码都选择ASCII,通用且存储代价小。
  2. 主要存中文,对存储大小比较敏感:内外部编码根据文字使用范围选择GB2312或者GBK,自行实现使用到的字符串处理函数。
  3. 通用性第一,处理简单:外部选择UTF-8,内部可以使用UTF-8或者UTF-32(即wchar_t)

参考资料:

http://baike.baidu.com/view/25492.htm

http://baike.baidu.com/view/25421.htm

http://baike.baidu.com/view/40801.htm

http://www.sac.gov.cn/SACSearch/search?channelid=160591&templet=gjcxjg_detail.jsp&searchword=STANDARD_CODE=’GB%202312-1980′&XZ=Q

http://www.sac.gov.cn/SACSearch/search?channelid=160591&templet=gjcxjg_detail.jsp&searchword=STANDARD_CODE=’GB%2018030-2005′&XZ=Q

http://www.sac.gov.cn/SACSearch/search?channelid=160591&templet=gjcxjg_detail.jsp&searchword=STANDARD_CODE=’GB%2013000-2010′&XZ=Q

http://www.unicode.org/

http://www.ibm.com/developerworks/cn/linux/i18n/unicode/linuni/

http://www.gnu.org/software/libc/manual/html_node/index.html

http://www.gnu.org/software/libiconv/

—- —- —-

http://www.searchtb.com/2012/04/chinese_encode.html

发表在 服务器运维, 编程技术, 计算机技术 | 标签为 , , | 一条评论

中国国民党革委会中国国民党Apache Softwares亮月亮科技

perl统计算数发邮件的几个细节: utf8, mailx, json

最近重新实现了一个使用-perl 读取数据日志进行一些统计然后将统计结果通过邮件发送出来的任务。涉及到几个细节,记录如下。

1. Perl中的JSON使用
在perl中调用 -json 需要
use JSON;
my $json = JSON->new->allow_nonref;
my $scalar = $json->utf8(0)->decode($content_in_json);
这里的 utf8(0) 很诡异,意思是对后面的$content不要使用utf8进行编码,也即如果$content本身已经是utf8的话,不这样操作可能会报错wide character found at procedure entry的错误,或者乱码。相应的,如果$content本身不是utf8的,则需要使用utf8对其进行编码:
my $scalar = $json->utf8->decode($content);
另外,$scalar也需要注意可能是@array,也可能是%hash。需依照具体情况多探测,较好的方式是use Dumper;。

2. 写utf8的字符顺序标记BOM–Byte Order Mark
关于utf8 的BOM可以进一步搜索,如果要在一个文件的头部加注BOM需要使用binmode。之所以要加注,是因为一些软件如MS Word, Excel等,对 utf8 w/o BOM的文件(没有字符顺序标记的UTF)就会显示成乱码,而加注了BOM的文件,才能够显示正常。对一个文件加注BOM的需求就这样产生的,实现过程如下:
open(FILE, “> test.file”) or warn “cannnot open test.file for write.\n”;
binmode FILE;
print FILE “\xff\xbb\xbf”;
close(FILE);

3. mailx发送邮件带附件
这样的需求在几年前的时候就实现过,现在重新写,有些记不得的细节就从网上搜索,结果发现使用 mailx, nail等发送带附件的邮件,结果发现仍在使用 uuencode 这样的方式,我记忆中不是这样的,于是反复试验,真的不用,通过 man mailx 的手册就能够看到多加一个参数 -a attachedfile 的形式就实现了发送附件的邮件。小计如下:
my $mailcontent = “/tmp/mailcontent.tmp”;
`/usr/bin/echo “Hi all, \nThis is a test mail.\nThere is a file attached with this message.\n” > $mailcontent`;
`/usr/bin/mailx -s “mail with attached file.” -a attached.file  wadelau@ufqi.com < $mailcontent`;
事情就这样成了,通过一个临时邮件内容文件,结合管道命令,实现了发生带附件的邮件。
这让我想起,有些网上内容需要更新的问题,网络的发展,越往后期,目前的内容就可能会陈旧,而这些陈旧的内容在搜索引擎那里依然存在,有的内容可能获得较高的推荐权重,但实际上某些内容所述已经被淘汰,但用户却得不到最新的,是个问题。

总结随想,备忘。

赠送 -自然域名 注册码。

发表在 编程技术, 计算机技术 | 标签为 , , , | 一条评论

書中自有顔如玉愛自己悅己容2nd愛自己悅己容汽車老司機車載

贺 -世界互联网大会 , -wic 开

贺
-世界互联网大会
-wic
开,发 -自然域名 注册码:

31UGd2ou3PFxBkVGMkXfda52
31ferqRh9kUFw6SBAa9311c5
31TRAhMuCuHIEzro2KDe330e

31aZ8nNxybOiPFSwWEIc6436
31KT28S434vqsVFw4Uca982d
31XpEgQl9845XTnmTBled53b

31BYVkMU4KYyTaWf8KE2b098
312eB3ymXxW7RBjCCngda6db

由  -国信办  和 -浙江省 共同主办的顶级互联网 -乌镇  峰会。

发表在 -NatureDNS/-自然域名, 社会生活 | 标签为 , , | 2条评论

書中自有顔如玉愛自己悅己容2nd愛自己悅己容汽車老司機車載