Ruby字符串处理的CSI方式

 
Perl、Python的多字节字符处理方式是UCS(Universal Code Set),Ruby的多字节字符处理方式是CSI(Code Set Independent)。UCS的做法是,不管你读取的是哪一种编码的字节,读进程序以后都必须统一设定为某一种特定编码,因此程序内处理的实际字节可能会转换。而CSI的做法是读取的字节不需要转换,只是把一个字节串加上一个编码的属性。
  
一、字节和字符的理解问题
  
UCS是相对比较难理解的,必须清楚地明白字节和字符的区别。字节是具体的,就是一串0和1,字符却是抽象的,都不好说明到底是什么,只能说是我们想表示的某一个符号。UCS方式是把具体的字节读进来,转换成抽象的字符,然后把字符提供给编程者处理,这个时候理论上编程者是不去管字节、甚至不应该看到字节的。但是,抽象的字符也必须以具体的字节形式存在,否则计算机无法内部存储和处理。这就出现一个问题,从文件读取的是字节,内存里存储、处理的也是字节,却告诉编程者,没有字节了,你现在处理的是字符,好理解吗?
  
Perl是把一串字节加上utf8标记,告诉你有utf8标记的是字符,没有utf8标记的是字节。Perl靠修修补补,不动原有字符串架构而把多字节字符问题解决掉了,但是这个utf8标记的做法很别扭,纯粹实用主义。Python则是分成两个类,Py2里的str实际是字节串,unicode是字符串。这两个名称用得不好,到Py3里原来的str改叫bytes,原来的unicode改叫str,这才算名符其实,可是“Python3的str不是Python2的str,而是Python2的unicode”又把一些人绕晕了。Py2的时候,GvR好像自己都没把字节和字符的关系弄清楚,表现是str类和unicode类同时都有decode和encode两个方法。“编码”是将字符用字节来表示的动作,而“解码”是把字节还原成抽象的字符的动作,所以
encode应该是只有字符串类才有的,一个字符串encode的结果是返回一个字节串,而decode是只有字节串类才有的,一个字节串decode的结果是返回一个字符串。Py2里这事儿处理得糟糕,到Py3才改正确了,str类只有encode方法,bytes只有decode方法。
  
语言的设计者一开始都未必能把字节、字符的关系问题处理干净、甚至弄清楚,这个“字节”“字符”之间的区分虽然是科学的,但是对于一般人来说未必很实用。
  
Ruby的CSI方式,就把“抽象的字符”这么一个概念给去掉了。Ruby里的string,全部都是一串字节,但是是带上了encoding属性的字节串,因为encoding属性的存在,所以在任何时候它都能被看成是字符串。这样没有了Python里那种str和bytes的分野,字节和字符在Ruby里是完全统一的。甚至非文本的二进制字节串,都有一个encoding,叫做“ASCII-8BIT”。默认情况下对它的处理是按字符的,不过还有each_byte这样的方法是处理字节的。如果想专门处理字节,那么把这个字符串force_encoding('ASCII-8BIT')一下,按字符处理也就跟按字节处理完全重叠。
  
二、Ruby可以模拟UCS方式
  
UCS说到底就是读文件或别的IO对象的字节,然后统一转换为某一种编码的字节串给到编程者手上,但是不让你看到字节,只让你看到“字符”。Ruby当然也可以做到读取字节→统一转换→给你,只不过你还是可以看到字节,但是你可以视而不见,只看字符。
  
除了字符串有encoding属性外,Ruby的IO对象、File对象也有encoding属性,而且是两个。一个是external_encoding,一个是internal_encoding。external_encoding是说这个文件的实际字节流应该用什么编码来看待、处理,而internal_encoding是说读了这个文件的字节后再把它转换成别的什么编码的字节串。比如f = open("gbk_file.txt","r:gbk:utf-8")中的"r:gbk:utf-8",gbk就是外部编码,utf-8是内部编码,f对象每次读行或读字符时,GBK编码的字节就自动转换为UTF-8编码的了。“上下左右”在文件里是“\xC9\xCF\xCF\xC2\xD7\xF3\xD3\xD2”,读进来以后变成"\xE4\xB8\x8A\xE4\xB8\x8B\xE5\xB7\xA6\xE5\x8F\xB3"这样一串字节且带上了UTF-8的编码属性。
  
Encoding类有一个类方法default_internal=,设置为某一编码,然后你打开文件时如果不再另行设置internal_encoding,则所有的读入字节都会默认转换成default_internal的字节。这跟UCS的效果是一样的,但是比UCS灵活,不是强制性的。UCS的那个统一编码一般是固定的(Python好像可以改,但恐怕没人改),为了通用性,不是UTF-8就是UTF-16之类,反正必然是Unicode的一种;而Ruby里随你设置。比如我处理的全部是普通中文文本,有GBK的、Big5的、UTF-8的,我可以Encoding.default_internal="gbk",这样处理编码杂乱的文件时也不会出现问题,已经统一成GBK了。
  
两个不兼容编码的字符串互相操作会出错,这不是怪Ruby的问题。Ruby默认是CSI的,Encoding.default_internal默认是nil,也就是不转换字节,读进来的是什么字节,它就还是什么字节。Ruby也提供了模仿UCS的途径,你要想像UCS那样统一字节处理,当然可以,你要做的是考虑设置好一个默认的internal_encoding。Ruby是很自由的,依我看,是比Perl更加推崇There's more than one way to do it的。
  
三、Ruby的CSI方式的好处
  
  
这不是官方说法,只是我用的时候的一些感觉
  
1、可以不转换字节处理外部文件。比如你读取GBK编码的文件进行处理,并依然输出为GBK编码,UCS方式你就必须:读取GBK→转换成UCS→处理UCS→转换成GBK→输出GBK。而Ruby的CSI可以把两个转换步骤省掉,变成:读取GBK→处理GBK→输出GBK。这个一方面是要省点资源,但是Ruby本来就不是节约资源的主,所以我也不好意思拿这个来夸Ruby。更重要的好处,可能是能避免在两个转换阶段出现问题。
  
Python版我回答过这样一个问题: http://www.newsmth.net/bbstcon.php?board=Python&gid=76573
  
他读一个文件读不成功,为什么?因为里面有个“䜣”字,这个字GBK里有,但是Unicode尚未支持,这导致了在转换成UCS这个阶段出错了。在原文的情况下,这是个错字,手动改正再处理,但假如我就是要处理这么个字呢?Python是没法简单做到的;Ruby如果以模拟UCS的方式(就是加一个转换内部编码,open(file,'r:gbk:utf-8'))也是没法做到,但是用默认的CSI方式,读GBK就按GBK处理,不转换,是完全没问题的。
  
中文情况还好,像这种只是特殊情况,一般人很少遇到。但日文编码问题好像很复杂,甚至某种通用编码不跟ASCII兼容,转换来转换去搞不好就出错了。松本这样做字符串的编码,我以为有个想法是减少内部环节,也就能减少发生问题的可能性。
  
  
2、可以做到(UCS方式难以做到的)跟特定编码密切相关的事
  
s1 = "一".encode('gbk')
s2 = "一".encode('utf-8')
  
s1.succ # 得到gbk的“壹”字
s2.succ # 得到utf-8的“丁”字
  
字符串的succ方式是得到码表里的下一个字符,比如"a".succ得到"b","K".succ得到"L",这是按特定编码来算的,而GBK码表里的字序跟Unicode的字序是截然不同的,在GB编码里“一”字后面是“壹”,在Unicode里“一”字后面却是“丁”。GB的常用字是按音序排的,我现在想要按照GB编码,把常见的音节为yi的全取出来,该怎么办?
  
begin_char = "一".encode('gbk') # 第一个是“一”
end_char = "绎".encode('gbk') # 最后一个字是“绎”
(begin_char..end_char).each {|c| puts c}
  
拿某种UTF做UCS的就没法做到这一点
  
3、IO/File对象的external_encoding、internal_encoding设计非常之妙,完完全全可以动态设定编码,用一个set_encoding方法
  
Ruby的CSI方式,程序读文件时,是去按照这个file对象本身所具有的external_encoding属性理解字节,再按照这个file对象本身所具有的internal_encoding属性转换字节(如果internal_encoding设置为nil则不转换)。我可以去设想有这么一个文件,因为某种原因,是多编码字节混杂的畸形文件,比如前5行是UTF-8编码,后面是GBK编码,怎么处理?
  
file.set_encoding('utf-8')
5.times { line = file.readline }
file.set_encoding('gbk:utf-8') # :前面的是外部编码,后面的是转换成的内部编码
line = file.readline # 虽然原文件后面的编码是gbk的,但是也转换成utf-8了
  
可能比较实用一点的是ARGF读文件的问题,也许我有一堆中文文件,编码各不相同,你也可以想办法ARGF.set_encoding('gbk')或是set_encoding('utf-8')
  
ARGF这种Ruby是来自Perl,但是Perl好像也没法做到这么简便地动态改换读取的编码
  
  
四、Ruby的CSI方式的缺点
  
最大的缺点就是耗资源了,每个字符串都得加上一个encoding属性。不过松本从来想的是让程序员happy,而不是像Python那样节省资源(为什么Python里的list和tuple还得分开?还另弄一个array?好像是为了省资源吧,让程序员自己根据情况挑选用哪个)。
  
另一个看起来是缺点就是不同编码字符串互相操作时出错。我其实不觉得这是个缺点,说它是缺点的只是局限于UCS的思维方式,认为UCS才是正统,才是唯一正确的。你如果按CSI的方式去理解这个问题,这个就好像他用法语跟你讲话,你用日语回答他。而Ruby也提供了模拟UCS的方式,所以我认为没问题。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本火锅店点餐系统采用Java语言和Vue技术,框架采用SSM,搭配Mysql数据库,运行在Idea里,采用小程序模式。本火锅店点餐系统提供管理员、用户两种角色的服务。总的功能包括菜品的查询、菜品的购买、餐桌预定和订单管理。本系统可以帮助管理员更新菜品信息和管理订单信息,帮助用户实现在线的点餐方式,并可以实现餐桌预定。本系统采用成熟技术开发可以完成点餐管理的相关工作。 本系统的功能围绕用户、管理员两种权限设计。根据不同权限的不同需求设计出更符合用户要求的功能。本系统中管理员主要负责审核管理用户,发布分享新的菜品,审核用户的订餐信息和餐桌预定信息等,用户可以对需要的菜品进行购买、预定餐桌等。用户可以管理个人资料、查询菜品、在线点餐和预定餐桌、管理订单等,用户的个人资料是由管理员添加用户资料时产生,用户的订单内容由用户在购买菜品时产生,用户预定信息由用户在预定餐桌操作时产生。 本系统的功能设计为管理员、用户两部分。管理员为菜品管理、菜品分类管理、用户管理、订单管理等,用户的功能为查询菜品,在线点餐、预定餐桌、管理个人信息等。 管理员负责用户信息的删除和管理,用户的姓名和手机号都可以由管理员在此功能里看到。管理员可以对菜品的信息进行管理、审核。本功能可以实现菜品的定时更新和审核管理。本功能包括查询餐桌,也可以发布新的餐桌信息。管理员可以查询已预定的餐桌,并进行审核。管理员可以管理公告和系统的轮播图,可以安排活动。管理员可以对个人的资料进行修改和管理,管理员还可以在本功能里修改密码。管理员可以查询用户的订单,并完成菜品的安排。 当用户登录进系统后可以修改自己的资料,可以使自己信息的保持正确性。还可以修改密码。用户可以浏览所有的菜品,可以查看详细的菜品内容,也可以进行菜品的点餐。在本功能里用户可以进行点餐。用户可以浏览没有预定出去的餐桌,选择合适的餐桌可以进行预定。用户可以管理购物车里的菜品。用户可以管理自己的订单,在订单管理界面里也可以进行查询操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值