行业资讯

Programming.log - a place to keep my thoughts on programming

发布时间:2026/7/5 3:28:03
Programming.log - a place to keep my thoughts on programming 目前程序设计语言似乎进入了一个蓬勃发展的时期Javascript、Perl、Python、Ruby、Groovy等一批较新的语言正越来越多地被熟悉和使用而C、C#、Java等主流语言也在不断地融入函数式和动态性特征。程序员的百宝箱中可供选择的宝贝是越来多了而社区中关于语言间的比较和争论也更为热烈我们常常见到关于“面向过程和面向对象的比较”、“动态语言和静态语言的比较”、“命令式和函数式范式的比较”等比较。我注意到这类讨论的关注点多集中于设计相关话题如“动态语言的Duck typing多态和静态语言的继承多态的比较”“Prototype based和Class based的比较”等。但我认为还有一个十分重要的方面值得关注这就是数据处理。数据处理之所以重要是因为不论是本地信息存储还是系统间信息交换都需要建立在一定的数据格式基础上。另外不管语言属于那种范式设计上采用什么模式在微观层次上程序很大一部分工作都是在做数据处理。所以从数据处理角度比较和理解语言间的差异有重要的现实意义。虽然数据通常是平台和语言无关的但不同的语言在处理某种格式的数据时会表现出不同的难度甚至某些数据格式只能采用特定的语言才能实现这就是数据亲和力的不同。语言的数据亲和力(Data Affinity)指的是语言与某种数据格式之间的相容程度它主要取决于语言的数据模型类型系统以及库的支持等。语言对某种数据格式亲和力越强则操作某类数据越容易。二进制字节块格式在偏底层的操作系统、嵌入式和通信系统中二进制的字节块是最常见的一种数据格式。二进制数据布局紧凑和接近机器的特点使得它常常作为系统间通信或系统文件的数据格式。但一般高级语言不方便直接和0101打交道而是基于记录、结构体和类等结构化表示操作数据这就存在着在底层的二进制字节块和高层的结构化数据之间的转换问题。C语言作为最主要的系统语言具有很高的字节块数据亲和力。这不仅因为C语言具有指针可以直接访问内存以外还因为C的结构体(struct)可以和字节块建立起直接的映射关系。例如在基于Socket连接的分布式系统中服务器端和客户端通过二进制的字节数据进行通信通信双方只要事先定义共用的结构体发送方先创建相应的结构体变量并填充字段然后把变量对应的内存块copy到Socket接收方从Socket读取字节块然后把字节块强制类型转换为相应的结构体指针即可读取个字段信息。整个过程中通信的双方都没有复杂的信息编码和解码的过程。示例代码如下struct t_data {int version;char type[10];float value;};//发送方struct t_data data;data.version 1;strcpy(data.type, “degree”);data.value 189.0;send(socket, (char*)data, sizeof(data));//接收方struct t_data data;read(socket, (char*)data, sizeof(data));printf(“%d, %s, %f”, data.version, data.type, data.value);上面的方法在实际应用中还需要注意内存对齐问题和大小端问题。内存对齐问题可以通过编译器预处理命令来进行控制保证内存中struct结构与传输的字节块具有相同的对齐方式大小端问题需要通信的双方采用同样的大小端方式否则就需要进行转换。C可以完全兼容C的结构体但C的类(包括class和struct)中如果定义了虚函数则会丧失结构的字节块数据亲和力这是C编程时需要权衡的一个因素。而除了C/C其他语言中则难以见到字节块数据亲和力其原因在于C/C允许控制结构体/对象的内存布局并允许对指针进行非类型安全的强制类型转换这都是在Java、Python等基于虚拟机的语言中不允许的。所以在Java、Python中进行字节块的编码解码就只能按照协议一个字段一个字段地按偏移量和长度进行解析。C/C的指针以及结构体和内存的直接映射带来了对字节块数据的亲和力但同时也留下了内存访问和类型安全的隐患而Java、Python等语言在拥有引用安全和类型安全的同时也失去了对字节块数据的亲和力。在基于虚拟机的语言中C#为struct提供了控制内存布局的能力同时在unsafe模式下还进行直接的内存操作。文本格式文本格式是另一种十分常见的数据格式。《Unix编程艺术》是这样评价文本格式的Text streams are a valuable universal format because theyre easy for human beings to read, write, and edit without specialized tools ”。基于文本流的管道处理是一种备受赞誉的Unix风格。Shell可以通过管道把各种功能单一的命令串联起来让文本流在管道上流动因而Shell语言具有很好的文本数据亲和力。许多文本数据处理任务Bash都可以一行搞定这就是Hacker们酷爱的One Liner风格。下面我们来看两个用Bash进行文本处理的例子1. 统计当前目录下的gz文件数目ls –l *.gz | wc –l2. 在Web服务器日志service.log中统计2011年6月26和27两天中每天各页面的PVcat service.log | grep ^2011-06-2[6-7] | cut –d ‘ ‘ –f 1, 3 | sort | uniq –cservice.log:2011-06-25 13:00:55 /music/c.htm Safari…2011-06-26 08:01:23 /main.htm IE2011-06-26 08:03:01 /sports/b.htm Chrome…2011-06-27 11:41:06 /main.htm IE2011-06-27 11:52:41 /news/a.htm Firefox输出:210 2011-06-26 /main.htm231 2011-06-26 /news/a.htm155 2011-06-26 /sports/b.htm288 2011-06-27 /main.htm292 2011-06-27 /news/a.htm161 2011-06-27 /sports/b.htm上面的两个简单文本数据处理任务如果是在C或C下实现则要麻烦得多代码量至少是十几行或者数十行加上编译调试整个开发效率可能比Shell低一个数量级。除了Shell外Perl也是以强大的文本数据处理而闻名的。我们来看一个Perl正则表达式的例子while (STDIN) {if (/hello\s(\w)/i) {print “say hello to $1“}elsif (/goodbye\s(\w)/i) {print “say goodbye to $1”}}输入HeLLo worldGoodbye bug输出say hello to worldsay goodbye to bug上面的例子中我们看到Perl直接进行字符串匹配并进行数据提取的强大威力。Perl基于正则表达式的字符串处理不仅比C/C等系统语言更强大甚至比Python这样的动态语言也更强大和更方便这是因为正则表达式是Perl语言的“一等公民”这就使得Perl比其他以库的方式支持正则表达式功能的语言具有更好的文本数据亲和力。后来的Ruby也学习Perl直接在语言上支持正则表达式。结构化文本格式XML是最近十几年来流行起来的一种通用半结构化的文本数据交换格式。XML除具有一般文本格式的优点外还具有能层次结构表达力和可扩展性的优势所以它至诞生以来就被大量用于配置文件和各种Web Service中。现代程序设计基本都少不了和XML打交道不过在C、Java和C#几种静态类型语言中处理XML却并不是一件十分轻松的事情。我们先来看一个Java解析和构建下面这个XML的例子langs typecurrent languageJava/language languageGroovy/language languageJavaScript/language /langs//Java解析XMLDocumentBuilderFactory dbf DocumentBuilderFactory.newInstance(); try { DocumentBuilder db dbf.newDocumentBuilder(); Document doc db.parse(src/languages.xml); Element langs doc.getDocumentElement(); System.out.println(type langs.getAttribute(type)); NodeList list langs.getElementsByTagName(language); for(int i 0 ; i list.getLength();i) { Element language (Element) list.item(i); System.out.println(language.getTextContent()); } }catch(Exception e) { e.printStackTrace(); }//Java创建XMLDocumentBuilderFactory dbf DocumentBuilderFactory.newInstance(); try { DocumentBuilder db dbf.newDocumentBuilder(); Document doc db.newDocument(); Element langs doc.createElement(langs); langs.setAttribute(type, current); doc.appendChild(langs); Element language1 doc.createElement(language); Text text1 doc.createTextNode(Java); language1.appendChild(text1); langs.appendChild(language1); Element language2 doc.createElement(language); Text text2 doc.createTextNode(Groovy); language2.appendChild(text2); langs.appendChild(language2);Element language3 doc.createElement(language); Text text3 doc.createTextNode(JavaScript); language3.appendChild(text3); langs.appendChild(language3);} catch (Exception e) { e.printStackTrace();}为了解析和创建小小的一段XML代码需要编写如此冗长的Java代码而实现同样的功能动态语言Groovy则十分简洁//Groovy解析XML def langs new XmlParser().parse(languages.xml) println type ${langs.attribute(type)} langs.language.each{ println it.text() }//Groovy创建XMLdef xml new groovy.xml.MarkupBuilder() xml.langs(type:current){ language(Java) language(Groovy) language(JavaScript) }上面Groovy操作XML的代码简洁而富有表达力代码与XML几乎是一一对应的如同直接在XML上进行操作的DSL一样而相应的Java代码则看不到XML的影子。这说明Groovy具有很高的XML数据的亲和力。为什么Java和Groovy在XML亲和力方面有这样的差异呢原因在于Java要求所有的方法和属性都必须先定义再调用严格的静态类型检查使得Java只能把XML元素作为“二等公民”来表达而Groovy则没有静态类型检查的限制可以自由地使用方法和属性来表达XML结构。上面用Groovy创建XML的例子中groovy.xml.MarkupBuilder类中实际上并没有langs, language这些方法但会在调用的时候自动创建相应的XML结构。除了XML外JSON是另一种通用的半结构化的纯文本数据交换格式它常被视为轻量级的XML。JSON的本意是Javascript的对象表示(Javascript Object Notation)它属于Javascript的语法子集Javascript对JSON有原生的支持。下面就是一个在Javascript中创建JSON对象的例子var json { “langs” : {type” : current”,language” : [Java”, Groovy”, Javascript”]