用jena api来理解RDF——空节点

2009年12月26日

在RDF入门的例子中,有这样一幅图:
200912262132
最中间的那个节点起一个过渡作用,这时,虽然它也是一个资源,但这个资源没有必要标上资源描述符,因为它可能只在应用程序局部使用,作为推理机的一个桥梁等等作用,换句说,这个资源别人没有必要去引用。这样的节点,我们称之为空节点:

  1. Model model = ModelFactory.createDefaultModel();
  2.  
  3.   Resource blankNode = model.createResource(new AnonId("tempNode"));
  4.   Property city = model.createProperty("http://www.crabobe.com/city");
  5.   Property street = model.createProperty("http://www.crabobe.com/street");
  6.   blankNode.addProperty(city, "深圳");
  7.   blankNode.addProperty(street, "龙岗");
  8.  
  9.   Resource crab = model.createResource("http://www.crabobe.com/crab");
  10.   Property address = model.createProperty("http://www.crabobe.com/address");
  11.   crab.addProperty(address, blankNode);
  12.  
  13.   model.write(System.out);

注意,包含中文的源码文件必须是UTF-8的,运行结果如下:

  1. <rdf:RDF
  2.     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  3.     xmlns:j.0="http://www.crabobe.com/" > 
  4.   <rdf:Description rdf:about="http://www.crabobe.com/crab">
  5.     <j.0:address rdf:nodeID="A0"/>
  6.   </rdf:Description>
  7.   <rdf:Description rdf:nodeID="A0">
  8.     <j.0:street>龙岗</j.0:street>
  9.     <j.0:city>深圳</j.0:city>
  10.   </rdf:Description>
  11. </rdf:RDF>

JENA , , ,

用jena api来理解RDF——资源概念

2009年12月26日

RDF作为资源描述框架,有2件事情是它的本职工作,第一,描述资源的唯一性,只有统一了,各种应用才能达成共识,好比秦始皇那会儿一样。第二,要把资源表述得有条有理。下面,拿出代码来解释。

  1. Model model = ModelFactory.createDefaultModel();
  2.  
  3. Resource crab = model.createResource("http://www.crabobe.com/crab");
  4.      
  5. model.write(System.out);

上面,我们用jena建立了crab这样一个资源,当然,crab只是java内存中的一个变量名而已,它真是的标识符号是http://www.crabobe.com/crab,也即,在这个世界上,这个资源是唯一存在的。

我们继续添加一个资源

  1. Model model = ModelFactory.createDefaultModel();
  2.  
  3. Resource crab = model.createResource("http://www.crabobe.com/crab");
  4. Property numerOfLeg = model.createProperty("http://www.crabobe.com/crab#numerOfLeg");
  5.      
  6. model.write(System.out);

这里我们添加了一个资源numerOfLeg,有人问,它是一个属性(Property)吧?没错,但在RDF中,属性也是一种资源,在JENA中,Property是Resource的子接口。既然它是一种资源,那必定得有唯一的标识符,这个标识符就是http://www.crabobe.com/crab#numerOfLeg

接着我们用numerOfLeg这个属性来描述crab

  1. Model model = ModelFactory.createDefaultModel();
  2.  
  3. Resource crab = model.createResource("http://www.crabobe.com/crab");
  4. Property numerOfLeg = model.createProperty("http://www.crabobe.com/crab#numerOfLeg");
  5. crab.addProperty(numerOfLeg, "8");
  6.      
  7. model.write(System.out);

运行的结果:

  1. <rdf:RDF
  2.     xmlns:j.0="http://www.crabobe.com/crab#"
  3.     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" > 
  4.   <rdf:Description rdf:about="http://www.crabobe.com/crab">
  5.     <j.0:numerOfLeg>8</j.0:numerOfLeg>
  6.   </rdf:Description>
  7. </rdf:RDF>

关于这个结果,虽然很简单,但是里面有个细节需要我们去关注!!同样是资源描述符,资源的描述符和属性的描述符是不一样的。区别就体现在,资源的描述符仅仅是作为rdf:Description标签的一个属性,而属性的描述符却要被用来作为XML标签。在这点上来看,属性的资源描述符一定要带有相对路径,假如我们这样写:
model.createProperty(“http://www.crabobe.com”);
那么这个属性标签的命名空间就没有了,如果命名空间标示为http://www.crabobe.com,那它自己的名称呢?所以,jena会报错。
所以,我们的写法可以是这样:

http://www.crabobe.com/crab

那么http://www.crabobe.com/就是命名空间,crab就是标签名称
也可以像例子都那样写:

http://www.crabobe.com/crab#numerOfLeg

那么命名空间就是http://www.crabobe.com/crab#,被jena简写成j.0,名称就是numerOfLeg。

现在,我们来验证资源的唯一性:

  1. Model model = ModelFactory.createDefaultModel();
  2.  
  3. Resource crab = model.createResource("http://www.crabobe.com");
  4. Resource crab1 = model.createResource("http://www.crabobe.com");
  5. Property numerOfLeg = model.createProperty("http://www.crabobe.com/crab#numerOfLeg");
  6. Property numerOfLeg1 = model.createProperty("http://www.crabobe.com/crab#numerOfLeg");
  7. crab.addProperty(numerOfLeg, "8");
  8. crab1.addProperty(numerOfLeg1, "8");
  9.      
  10. model.write(System.out);

这里,我们人为写出2个对象,但是资源描述符写成一样,这2个对象分别有各自属性,按照唯一性,那么内存中即使对象再是多,在RDF规范中,只会认资源描述符,只要资源描述符是一样的,那么就视为一个资源。运行的结果,符合我们的推理。

如果我们改一改:

  1. Model model = ModelFactory.createDefaultModel();
  2.  
  3. Resource crab = model.createResource("http://www.crabobe.com");
  4. Resource crab1 = model.createResource("http://www.crabobe.com");
  5. Property numerOfLeg = model.createProperty("http://www.crabobe.com/crab#numerOfLeg");
  6. Property numerOfLeg1 = model.createProperty("http://www.crabobe.com/crab#numerOfLeg");
  7. crab.addProperty(numerOfLeg, "8");
  8. crab1.addProperty(numerOfLeg1, "10");
  9.      
  10. model.write(System.out);

运行结果是什么?可以思考下,再运行结果,留待大家思考吧~

JENA ,

2010中文分词总结

2009年12月24日

2010年快到了,在最近这段时间里,我是时候维护下自己的博客了,大量资料堆砌着没有整理,有中文分词的、句法分析的、语义网的、本体的,大量的资料心得如果不固化在文字中,那么也会随着2009年的逝去而遗忘。废话不多说了,一点点开始总结吧。

中文分词,在80年代末、90年代初,我国学者就已经开始研究了,那时候的机器内存很小,基本停留在查基础词典然后匹配阶段,有向后最大匹配,向前最大匹配,双向扫描匹配,启发式匹配等等,这些都被后人称为机械式分词。就效率而言,已经非常高了,不亚于现在的中文分词,但是无法排除歧义,无法识别未登陆词。90年代末,兴起了统计学分词与语义分词2个学派,统计学分词现在已经非常成熟,成为主流;而语义分词因为其现实技术不到位,还在一步一步地往上爬,但是语义分词最终肯定会成为主流,至少和统计学分词平起平坐,这乃我的一家之言,呵呵。

我这里主要围绕统计学分词展开讨论与总结。(后面的术语可能会说错,但这里我打算深究所有的细节,毕竟只是总结思路)

首先我们从词库聊起,词库分为基础词库和训练词库

基础词库,存放大量的日常词语,在内存中,一般都是以词语的第一个词作为索引存放,比如:电灯、电脑、电视机这些以电开头的词存一块。这里有2个流派,一是以GBK编码存放,另一个是以UNICODE编码存放;前者紧凑,兼容性差;后者稀疏,但兼容性好。根据不同的业务,可以采用不同的编码。

这样的储存方式,好比将词库比作了HashMap,词语的第一个汉字就是hashcode,一次匹配到这个词所在的区块,然后可以根据2分查找来找出这个词是否存在。

训练词库里面存放N-gram模型,这个模型基于这样一种假设:文章中的一个词,必定与上文有关,或者下文有关。举个例子:

我今天非常愤怒。

如果是1阶N-gram模型,训练词库中就存放:
我|今天
今天|非常
非常|愤怒
愤怒|。

如果是2阶N-gram模型,训练词库中就存放:
我|今天|非常
今天|非常|愤怒
非常|愤怒|。
愤怒|。

有朋友问,你这不都分好词了吗?是的,但是这个是语料库中的内容。语料库就是人工分好词的训练样本,是人们手工一个一个去分词所得到的海量文章集,以这个语料库作为输入,输出N-gram模型的词库。

不仅要生成这样的模型,还需要去训练词库中每个结果的出现次数,便于日后根据这个统计值来计算分词。我们可以看到,N越大,词库记录的信息就越是丰富,将设常用汉字3000个,如果以1阶N-gram模型统计,那么需要9,000,000结果集,如果2阶就不得了了。因此,N虽然越大越好,但是随着而来的就是数据稀疏问题。

谈到数据稀疏问题,就要谈到平滑处理了。我们看下面一句话:

李宇春天后地位。

计算机看到这句话保管郁闷死,这里有2种分词方案:
李宇|春天|后|地位|。
李宇春|天后|地位|。

如果按照语义分词法,担保第一句就是一个病句,写个常用的句法分析就搞定,这个方法我觉得是最贴近人们的思维方式的,然而这个方法并不成熟,里面涉及很多问题到现在都是一个未知数,原因就在于自然语言的表达方式太过于丰富。这里不扯远了,我们聊聊统计学怎么来搞:

说穿了,很简单,就比谁的概率大,P(李宇/春天/后/地位/。) 和 P(李宇春/天后/地位/。)这2个概念谁大,计算机就按谁的切。这其实就是类似于人类的语感,熟读唐诗三百首,不会作诗也会吟。那么如何计算呢?

我们按照1阶N-gram来算:
P(李宇/春天/后/地位/。) = P(李宇/春天)* P(春天/后)* P(后/地位)* P(地位/。)
P(李宇春/天后/地位/。) = P(李宇春/天后)* P(天后/地位)* P(地位/。)

P(李宇春/天后)怎么算呢?P(李宇春/天后) = (李宇春/天后)出现的次数除以总样本的次数。这就是所谓的最大似然估计。

好了,回归到我们刚才说的平滑处理吧,通过刚才中间的讲述,聪明的读者一定会想到,如果(李宇春/天后)这样的组合在语料库中一次都没有出现,那么它的次数就为0!那岂不是它的概率也为0了吗?不错,不仅这个可能为0,大量现实中拿进去分词的句子中词的组合,都有可能为0,这就是所谓的数据稀疏问题,1阶已经如此了,更何况2阶呢?

接下来就是如何处理这样的稀疏问题了,因为我们知道(李宇春/天后) 这样的组合虽然在语料库中没有出现,但它在现实中肯定是存在的,既然存在就一定有概率,那么既然似然估计无能为力,我们只好手工去估计了,最简单的办法就是直接将所有为0的情况,都附一个概率,比如0.00001之类的,这个就会出现过大估计和过小估计。不过不管如何,总算是平滑处理了。

另一种较为流行的平滑处理,叫插入平滑处理。
P(李宇春/天后) = 0.7*P(李宇春/天后)* + 0.3*P(李宇春)
具体为什么,大家自己看吧。

就我刚才所讲的这些,配合一个良好的语料库和比较全面的基础词库,就已经可以分词了,还能排除歧义,准确率可以接近100%(不是我说的,呵呵)

接下来的任务就是未登陆词识别了,在日常我们处理的句子中,总有阿猫阿狗的人名一大堆,还有一些什么什么团、什么什么公司的,这些总不至于全部存入词库吧?因此,就需要未登陆词的识别。这项工作涉及到基于角色的隐马模型。所包含的原理算是2000年后,我国学者的重大突破。张华平等人为此做了很大的贡献。可惜,这些人啊,喜欢把理论知识用理论术语表达,论文、代码基本天书吧,关于这块日后撰文写清楚与大家分享。

平安夜快乐~

自然语言处理 , , , ,

HTML解析模块完成

2009年11月15日

1.根据DTD文件构造HTML树
2.遍历出网页中所有肉眼能看到的东西
3.找出DIV块文本、找出TABLE块文本
4.根据DTD中的实体定义,将HTML转义符转成原有的字符

下一步计划,完成分词模块

大杂烩

HtmlReader模块完成

2009年11月4日

通过2组引擎数组,完成对html网页的粗糙预处理

  1. final String[] blankFilter = {" \t", " \t", "\n", " \t\r\n", "<", " \t"};

意思:如果已经扫描到空格或者tab,那么后面不要出现空格或tab了
“\n”, ” \t\r\n” 同义
“<", " \t" 同义

  1. final String[] tagFilters = {"<!–", "–>", "<script", "</script>", "<style", "</style>", "<!DOCTYPE", ">"};

意思,删除script开头,script结尾的所有内容

  1. private Reader reader;
  2.     public void setReader(Reader reader){this.reader = new BufferedReader(reader);}
  3.  
  4.     char[] buf = null;
  5.     int bufEnd = 0;
  6.    
  7.     /* 
  8.      *  将reader中的内容存入500K的缓存中,如果不够采取加倍策略
  9.      *  read all content from reader into buf and update the bufEnd,
  10.      *  finally will close the reader */
  11.     boolean readyData() throws IOException, NotSetReaderException
  12.     {
  13.         if(reader == null)
  14.         {
  15.             throw new NotSetReaderException();
  16.         }
  17.        
  18.         /* for most page 500K is enough */
  19.         int MOST_PAGE_SIZE = 500 * 1024;
  20.        
  21.         /* do not bigger than 5M */
  22.         int MAX_PAGE_SIZE = 5 * 1024 * 1024;
  23.        
  24.         buf = new char[MOST_PAGE_SIZE];
  25.        
  26.         try {
  27.             int actualRead;
  28.             while((actualRead = reader.read(buf, bufEnd, MOST_PAGE_SIZE-bufEnd)) != -1)
  29.             {
  30.                 /* oh, fill all space, so there are something remain in stream */
  31.                 if(actualRead == MOST_PAGE_SIZEbufEnd)
  32.                 {
  33.                     /* update the bufEnd */
  34.                     bufEnd += actualRead;
  35.                    
  36.                     /* it’s enough */
  37.                     if(bufEnd >= MAX_PAGE_SIZE)
  38.                         break;
  39.                    
  40.                     /* assign bigger array */
  41.                     char[] tempBuf = new char[MOST_PAGE_SIZE * 2];
  42.                     MOST_PAGE_SIZE = MOST_PAGE_SIZE * 2;
  43.                     System.arraycopy(buf, 0, tempBuf, 0, bufEnd);
  44.                     buf = tempBuf;
  45.                 }
  46.                 /* nothing to read */
  47.                 else
  48.                 {
  49.                     /* update the bufEnd */
  50.                     bufEnd += actualRead;
  51.                 }
  52.             }
  53.  
  54.         } catch (IOException e) {
  55.             System.out.println("God said: something wrong when read the page in initialize");
  56.             throw e;
  57.         } finally {
  58.             reader.close();
  59.         }
  60.        
  61.         /* test for the buff
  62.         FileWriter writer = new FileWriter("11");
  63.         writer.write(buf,0, bufEnd);
  64.         writer.close();
  65.         */
  66.        
  67.         return true;
  68.     }
  69.    
  70.    
  71.     void removeUselessContent()
  72.     {
  73.         int saved = 0, reading = 0;
  74.        
  75.         /* 前者为条件(空格tab),后者为删除对象(空格tab) */
  76.         final String[] blankFilter = {" \t", " \t", "\n", " \t\r\n", "<", " \t"};
  77.        
  78.         /* 准备删除的无用标签,提供首末信息 */
  79.         final String[] tagFilters = {"<!–", "–>", "<script", "</script>", "<style", "</style>", "<!DOCTYPE", ">"};
  80.        
  81.         /* 无回溯高效扫描整个文档 */
  82.         while(true)
  83.         {
  84.             if(reading >= bufEnd)
  85.                 break;
  86.  
  87.             /* 一直扫描到没有过滤为止 */
  88.             while(true)
  89.             {
  90.                 /* 监控是否有符合的过滤,只有当没有什么可以过滤了,
  91.                  * 才退出循环,标志为reading是否有读 */
  92.                 int checkFilter = reading;
  93.                
  94.                 /* 过滤篇首的所有空格 */
  95.                 while(saved == 0 && reading < bufEnd &&
  96.                         (buf[reading] ==   || buf[reading] == \t ||
  97.                                 buf[reading] == \r || buf[reading] == \n))
  98.                 {
  99.                     reading++;
  100.                 }
  101.  
  102.                 reading = removeUselessBlank(blankFilter, reading, saved);           
  103.                
  104.                 reading = removeHtmlTag(tagFilters, reading);
  105.                
  106.                 /* 如果有reading有遇到过滤的条件,让其继续扫描过滤 */
  107.                 if(checkFilter == reading)
  108.                     break;
  109.             }
  110.            
  111.             if(reading < bufEnd)
  112.                 buf[saved++] = buf[reading++];
  113.  
  114.         }
  115.        
  116.         bufEnd = saved;
  117.        
  118.         ///* test for the buff
  119.         try{
  120.         FileWriter writer = new FileWriter("11");
  121.         writer.write(buf,0, bufEnd);
  122.         writer.close();
  123.         //*/
  124.         }catch(IOException e){}
  125.     }
  126.  
  127.  
  128.     private int removeUselessBlank(String[] filterString, int reading, int saved) 
  129.     {
  130.         char[] condition, forbidance;
  131.         for(int i = 0; i < filterString.length;)
  132.         {
  133.             condition = filterString[i++].toCharArray();
  134.             forbidance = filterString[i++].toCharArray();
  135.             while (saved1 >= 0 && equalAny(condition, buf[saved - 1])
  136.                     && equalAny(forbidance, buf[reading])) {
  137.                 reading++;
  138.             }
  139.         }
  140.         return reading;
  141.     }
  142.  
  143.     private boolean equalAny(char[] condition, char aim) {
  144.         boolean bool = false;
  145.         for (char c : condition) {
  146.             bool = bool || (c == aim);
  147.         }
  148.         return bool;
  149.     }
  150.  
  151.       private int removeHtmlTag(String[] filterString, int reading) {
  152.  
  153.         char[] styleBegin, styleEnd;
  154.         for (int i = 0; i < filterString.length;) {
  155.             styleBegin = filterString[i++].toCharArray();
  156.             styleEnd = filterString[i++].toCharArray();
  157.  
  158.             while (reading + styleBegin.length < bufEnd    && compareCharArray(buf, reading, styleBegin)) 
  159.             {
  160.                 reading = reading + styleBegin.length;
  161.                 while (reading + styleEnd.length < bufEnd && !compareCharArray(buf, reading, styleEnd)) 
  162.                 {
  163.                     reading++;
  164.                 }
  165.                 reading = reading + styleEnd.length;
  166.             }
  167.         }
  168.  
  169.         return reading;
  170.     }
  171.  
  172.     private boolean compareCharArray(char[] src, int from, char[] des) {
  173.         int i = 0;
  174.         while (i < des.length
  175.                 && (src[from] == des[i] || (src[from] + 32) == des[i])) {
  176.             from++;
  177.             i++;
  178.         }
  179.         return i == des.length;
  180.     }

大杂烩

关于隐马尔可夫模型在模式识别中应用的探讨

2009年10月1日

这篇文章不是为了介绍隐马儿可夫模型,而是为了阐述该模型在人工智能、自然语言处理、乃至语义网领域的应用。这是一种利用现有资源去预测未来的一种手段,通过训练当前数据,来预测现在某种推论成立的概率。

隐马儿可夫模型的要点如下:
1、存在2种类型的状态,内部状态和外部状态:内部状态是一种外部不可见的、但决定外部状态的一群状态;外部状态是直接被人们认识和利用的一群状态,或者说是一种最终的结果。
2、内部状态可以相互转移;而外部状态不会相互转移,只和其内部状态有关。因此只要涉及到状态转移,都是指内部状态。
3、内部状态的转移存在时间型、阶段性、步骤性:该模型的状态转移是一步一步、一个时间点一个时间点、一个阶段一个阶段进行的,并非连续。很重要的一点就是:每一个时刻只会处于一种内部状态,并且该内部状态只会表现出一种外部状态。
4、内部状态的转移有一定的概率,比如T1时刻,状态W1,可能会在T2时刻转换为状态W2,其概率为80%。
5、每个内部状态必定会在某个时刻显现出一种外部状态,且存在一定概率。

200910011644

上面这个图,说明了一些问题:
1、W1、W2、W3代表该模型有3个内部状态,V1、V2、V3代表该模型有3个外部状态。
2、a12表示W1转换为W2的概率,并且a11+a12+a13=100%
3、b12代表状态W1发出V2状态的概率,用条件概率表达,即:P(V2|W1)

假设,起始状态为W2,那么我们想要在2个时间点,观察到V3、V12个外部状态(注意:是有顺序的)的概率为:(1*b23)+(a22*b21+a21*b11+a23*b31)。第一个括号里面的概率是指第一个时间点,也即初始状态,W2发出V3的概率;第二个括号由3项组成,分别代表W2转移到下一个状态的概率乘上在其基础上发出V3的概率。

对于模式V3V2V2V1V3V2,要想知道其出现的概率,那么我们需要知道初始状态是谁,转移状态概率矩阵,以及,每个状态产生外部形态的概率。通过这些信息我们可以轻易得到该模式出现的几率,但是有一个问题值得关注的是算法复杂度,如果按照我们刚才的思路,那么这个模式的概率将由36项相加所得,如果内部状态为50个,模式的长度(时间段)为60个呢?那么项的个数估计是天文数字了。对于这个问题,感兴趣的可以参考HMM向前算法。

刚才我们讨论的其实就是隐马尔可夫模型中的估值问题,现在我们来探讨和模式匹配密切相关的解码问题。解码问题的描述为:已知模式V3V2V2V1V3V2,已知状态转移概率矩阵(aij),以及,每个内部状态产生外部状态的概率(bjk),求产生这个外部状态序列的内部状态的序列,比如:W1W3W1W3W3W1。要产生这个外部状态序列的内部序列肯定会有很多,因此问题的答案也会有很多,但是每个答案都有自己的概率,并且所有答案相加为1。最大概率的那个答案,可以被认为是最接近事实的答案。

求解这个解码问题的思路其实很简单,也就是遍历所有的可能序列而已,然后找出概率最大的那条路径。为了清楚表达思路,我们将上面的有限状态机图转换为网络图,将每个状态和时间对应起来:

2009100511501

上图的黄色圈圈就是初始状态,那么要产生V2的概率就是b22

200910051207

现在要产生V2V1的内部状态序列可能有3种:W2W1其概率为b22*a21*b11 、W2W2其概率为b22*a22*b21、W2W3其概率为b22*a23*b31,挑选一个概率最大的,那么其内部状态路径就是所谓的最佳答案了。

现在要产生V2V1V3呢?路径就有9种了;外部序列越长,内部状态数越多,那么路径数就会成指数上升。幸运的是,在大部分的问题上,总有那么一个外部状态只有一个内部状态能显现它,这就意味着其他内部状态显现它的概率为0,这在很大程度上可以进行算法改进,也即:当遇到这样的外部状态时,我们可以开始清算之前的最优路径了,清算完毕后将这个对应的内部状态设为起始状态,继续开始寻找这样的外部状态。这种算法就在很大程度上阻止了算法复杂度的指数上升。

自然语言处理 , , ,

my birthday gift from my GF

2009年9月29日

      

       

                                     

大杂烩

对linux和windows的动态库总结

2009年9月26日

windows下:

根据调用方式的不同,对动态库的调用可分为静态调用方式和动态调用方式。

(1)静态调用,也称为隐式调用,由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(Windows系统负责对DLL调用次数的计数),调用方式简单,能够满足通常的要求。通常采用的调用方式是把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须在源文件中声明一下。 LIB文件包含了每一个DLL导出函数的符号名和可选择的标识号以及DLL文件名,不含有实际的代码(这里的lib文件和静态库是不一样的)。Lib文件包含的信息进入到生成的应用程序中,被调用的DLL文件会在应用程序加载时同时加载在到内存中。

(2)动态调用,即显式调用方式,是由编程者用API函数加载和卸载DLL来达到调用DLL的目的(比如LoadLibrary、GetProcAddress、FreeLibrary),比较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。

静态调用举例:

首先编制一个DLL,简单由2个文件组成:ShowInfo.h ShowInfo.c 代码如下:

ShowInfo.h:

  1. #ifdef SHOWINFO_EXPORTS
  2. #define SHOWINFO_API __declspec(dllexport)
  3. #else
  4. #define SHOWINFO_API __declspec(dllimport)
  5. #endif
  6. SHOWINFO_API void fnShowInfo(void);

ShowInfo.c:

  1. #include "ShowInfo.h"
  2. #include <stdio.h>
  3.  
  4. SHOWINFO_API void fnShowInfo(void)
  5. {
  6. printf("Crab\n");
  7. }

编译时,务必打开SHOWINFO_EXPORTS宏开关,这样DLL里面的函数申明就会带有__declspec(dllexport)关键字了(否则生成不了lib文件),构建完后,就生成了一个lib文件,一个dll文件

使用这个DLL的时候,需要做如下事情,第一:导入ShowInfo.h,但不要打开SHOWINFO_EXPORTS宏开关,这样函数声明就变成了__declspec(dllimport);第二:编译时导入刚才生成的lib包;第三:将dll放在exe的所在目录,或者VC的工程目录。

上面这个制作DLL和使用DLL的方式,其实就是JNI的方式,可以拿javah生成的对比下。

还有一种方式是用(.DEF)模块定义文件,个人暂不倾向这个,呵呵。

linux下:

linux环境要干净透明些,无需在函数名上挂狗牌。如果要制作动态库,写好C文件后,直接用 gcc ShowInfo.c -fPIC -shared -o libShowInfo.so 就搞定了。也就是加个-fPIC -shared 参数,指定GCC不要链接成可执行文件,而是动态库。这里要注意几点:一、libShowInfo.so的命名方式,一定要 lib*.so这样的命名;二、-shared是指编译成动态库,-fPIC学问稍微大些,如果不指定它,那么其他程序调用这个so的时候,它会为每个程序维护一个副本,其实,对于小型的dll,完全没有必要指定-fPIC,效率会非常高的,但是对于大型的dll,那么还是指定下吧。使用就更简单了,gcc Main.c -L. -lShowInfo -o Main,其中-L指明让GCC去哪里找动态库,-l则是指定动态库的名字(lib*.so中间的*就是名字),顺提一句,如要要使用数学类的方法,得加上 -lm,是指用一个数学动态库。

上面讲的,其实就是类似于windows的静态调用,在linux下,同样也是可以用动态方式加载和使用dll了,这里就不讲了。另外,在Linux里面,可以采用ldd命令来检查程序所依赖的共享库。

C/C++ , , , ,

彻底理解JAVA的编码转换问题

2009年9月16日

最近在写一些JNI接口的时候,碰到了一系列的编码转换问题,进行了一番总结,总算是彻底搞懂了。

在JAVA中处理字符串编码,我们可以理解为JVM里面的形态和外面的形态2种。比如:

首先,我们来探讨下编译期的编码问题:

  1. public class Main 
  2. {
  3.     String testStr = "你好";
  4. }

假设该java文件是GBK编码的,我们用如下命令:
javac -encoding GBK Main.java
假设该java文件是UTF-8编码的,我们用如下命令:
javac -encoding UTF-8 Main.java

虽然2个Main.java文件编码不一样,但是编译好的class 2进制文件,是一模一样的。那么是不是编译成UNICODE编码了呢?我看了下2进制文件里面的“你好”并非UNICODE编码,估计是处于安全考虑,进行了一些转换。不过,有一点我们可以总结了:N份源文件有着不同的编码格式,它们编译出来的class文件,都是统一的。至于中文被编译成什么编码格式了,我们不得而知,但可以猜测是UNICODE的变型。

上面的str变量就是字符串的内在形态,”你好”就是外在形态。str对象的任何操作都是基于内在形态的,我们把外形形态的字符串放入String类操作的时候,就要负责编码的转换。你可以看出上面这一行代码的编码转换吗?其实是有的:这里的”你好”是GBK编码的,因为它是Main.java里面的文字,而Main.java是GBK的;但是str的内部编码可不是GBK哦,所以这里JVM已经进行了转码。

上面是将中文写在代码里面,如果从文件中读入中文呢,我们是将再来看个例子:

  1. BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
  2. System.out.println(reader.readLine());

上面这个例子是从文件test.txt中读入一行打印,会不会出现乱码呢?我们来看一些情况:

注:下面4个试验,是用Eclipse来完成的
1.test.txt是UTF-8的,代码文件是GBK的(乱码)
2.test.txt是GBK的,代码文件是UTF-8的(乱码)
3.test.txt是UTF-8的,代码文件是UTF-8的(正常)
4.test.txt是GBK的,代码文件是GBK的(正常)

先说,为何乱码,道理非常简单,因为没有人告诉FileReader以什么编码去读test.txt。那么FileReader到底是用什么编码在读test.txt呢?
在Eclipse中,Eclipse会根据源码的编码格式来读,所以只要源码即:Main.java和test.txt编码一致即可。
但是在,命令行中,是根据操作系统的默认编码来读的,感谢索尔的提醒。

那么,我们为了不乱码,就需要告诉JAVA,外面文件的编码格式是什么,让JVM用正确的解码方法去解码字符串。于是,代码改为:

  1. BufferedReader reader = new BufferedReader(new InputStreamReader(
  2.                 new FileInputStream("test.txt"), "UTF-8"));
  3. System.out.println(reader.readLine());

这里的UTF-8就是test.txt的编码格式。程序运行正常。
上面2个例子,一个是通过代码里面写中文,一个是通过读取文件获得中文,最终这些中文在JVM的运行时,是以什么形式储存在内存中的呢?答案是:UNICODE,2个字节。

现在我们考虑一个复杂的场景:我们这个系统是一个中间站,需要把A模块的GBK字符串送到B模块处理,B模块是用UTF-8处理这些字符串的,当B模块处理完后,我们接收这些字符串,然后转发给A模块。这里就有着几个转码的步骤:

1.先从A模块取下字符串,我们告诉String,getBytesFromA()返回的是GBK编码的字节序,执行完后,所有得到的字节被解析为GBK编码,再转码成UNICODE编码,存储在strFromA对象中
String strFromA = new String(getBytesFromA(), “GBK”);
2.将strFromA从UNICODE转码成 UTF-8 ,然后传入sendBytesToB
sendBytesToB(strFromA .getBytes(“UTF-8″));
3.B模块执行完后,我们用getBytesFromB()从B模块取回字符串,告诉String用UTF-8来读取这些字节,同样所有得到的字节被解析为UTF-8编码,再转码成UNICODE编码,存储在strFromB对象中
String strFromB = new String(getBytesFromB(), “UTF-8″);
4.将strFromB从UNICODE转码成 GBK ,然后传入sendBytesToA
sendBytesToA(strFromB .getBytes(“GBK”));

总结如下:
1.JAVA在运行时,所有的编码都为UNICODE,存在char类型中,2个字节。如果遇到3个字节的UNICODE,用2个char存
2.回想,一个程序在运行期,内存里面的字符串从哪里来?2个途径,1.写在源码里面;2.运行期从文件或者网络等读取。对于1,我们需要在编译期告诉javac encoding是什么;对于2,我们需要告诉String或者FileReader等,读进来的字符串是何编码。一旦进入内存,将全部转为UNICODE
3.在运行期的字符串,可以通过getBytes方法来转化任意编码,如果不指定,那么就是用默认的编码(Eclipse的默认编码是源文件的编码,命令行的默认编码是操作系统的编码)。比如,一个字符串要保存到文件中,如果要存成UTF-8,最好在witer中告诉JAVA,你要保存为UTF-8。
4.最后总结,大型项目,切记编码问题,检视所有的外部来源是否都被指定编码格式了

java

CUnit的精简测试框架

2009年7月24日

CUnit使用起来需要去写一些和CUnit相关的代码才能跑起来,没有JUnit4.0那种POJO的方式来的这么清爽,于是我定义了一些宏,将和业务无关的CUnit代码屏蔽起来,使用的时候,针对每个.C文件写一个测试suit文件,让它们一起包含Test.h即可。

200907241927

Test.h出了包含一些头文件以外,还有一串宏,定义如下(下面的中文是举例):

  1. #include "include/Basic.h"
  2.  
  3. #define INITIALIZE_CUNIT() \
  4.  if (CUE_SUCCESS != CU_initialize_registry()) \
  5.   return CU_get_error();
  6.  
  7. #define FINISH_CUNIT() \
  8.  CU_basic_set_mode(CU_BRM_VERBOSE); \
  9.  CU_basic_run_tests(); \
  10.  CU_cleanup_registry(); \
  11.  return CU_get_error();
  12.  
  13. #define NewTestSuit(SuitName) \
  14.  CU_pSuite pSuite = NULL; \
  15.  pSuite = CU_add_suite("Suite_Of_"#SuitName, before, after);
  16.  
  17. #define AddTest(FunctionName) CU_add_test(pSuite, #FunctionName, FunctionName);
  18.  
  19. #define GoTests() \
  20.  test模块1(); \
  21.  test模块2(); \
  22.  test模块3();
  23.  
  24. void test模块1(void);
  25. void test模块2(void);
  26. void test模块3(void);

下面是TestMain.c,以后可以不用动。

  1. #include "Test.h"
  2. int main(){
  3.  INITIALIZE_CUNIT();
  4.  GoTests();
  5.  FINISH_CUNIT();
  6. }

下面给出 Test模块1.c 的例子

  1. #include "Test.h"
  2. static int before(void){
  3.  // 该模块的方法运行前的准备工作写在这里
  4.  return 0;
  5. }
  6. static int after(void){
  7.  // 运行完的销毁工作也在这里
  8.  return 0;
  9. }
  10. void test_方法1(void)
  11. {
  12.  CU_ASSERT_EQUAL(1, 1);
  13. }
  14. void test_方法2(void)
  15. {
  16.  CU_ASSERT_EQUAL(1, 1);
  17. }
  18. void test_方法3(void)
  19. {
  20.  CU_ASSERT_EQUAL(1, 1);
  21. }
  22.  
  23. void test模块1()
  24. {
  25.  NewTestSuit(模块1);
  26.  AddTest(test_方法1);
  27.  AddTest(test_方法2);
  28.  AddTest(test_方法3);
  29. }

需要注意的是,和JUnit4.0有点不同:
JUnit4.0运行流程为:before->方法1->after->before->方法2->after->before->方法3->after
CUnit的运行流程为: before->方法1->方法2->方法3->after

C/C++