Cygwin中通过RJB在Ruby下调用ICTCLAS(JAVA)

参考文章:

  1. ruby 下使用 ICTCLAS(JAVA)
  2. RJB 在windows下的一些安装事项
  3. ICTCLAS4J 的编译脚本

参考文章1中,在windows中成功在Ruby中调用了ICTCLAS4J,当环境迁到Cygwin中时,出现了一些错误。本文中,将修正这些错误,在Cygwin中通过RJB在Ruby中调用ICTCLAS4J

先说明几个问题:

  1. Cygwin中没有合适的JDK,调用的是windows中的JDK,所以本文中JVM的环境是windows环境(比如:JVM的路径格式都是windows样式的)
  2. JDK默认的安装路径是 C:\Program Files\JAVA\JDKxxx ,其中"Program Files“中间的空格会给我们带来很大麻烦,比如
    java -cp C:\Program Files\JAVA\JDKxxx\... 
    就会出错,不得不使用
    java -cp "C:\Program Files\JAVA\JDKxxx\..." ...
    为了方便,我们要避免那个空格,有两个办法
    1. 重装JDK,安到C:\JAVA\JDKxxx,比较笨拙但方便的方法
    2. 建立软链接,windows下建立软链接的工具是微软junction,将JDK目录映射到C:\JAVA\JDKxxx

下面来逐步解决出现的问题:

1. 将环境迁移到cygwin下

安装好Ruby , RJB后 , 运行

require 'rubygems' 
require 'rjb' 
Rjb::load 
str = Rjb::import("java.lang.String")

 

得到错误

Error occurred during initialization of VM
java/lang/NoClassDefFoundError: java/lang/Object

 

如果Google这个错误,可以知道是rt.jar没有载入进来,但是无论怎么设置Rjb::load 的 classpath 参数都是无效的,比如

require 'rubygems'
require 'rjb'
Rjb::load(classpath = ".;c:\\Java\\jdk1.6.0_18\\jre\\lib\\rt.jar")
str = Rjb::import("java.lang.String")

得到的是一样的错误,说明不是classpath的问题 问题分析:(如果只需要解决方案,可以跳过问题分析,因为这个问题分析只是臆测,并未得到证实) 如果运行java –verbose,可以看到java载入类的顺序大致如下:

[Loaded java.lang.Object from shared objects file]
[Loaded java.io.Serializable from shared objects file]
[Loaded java.lang.Comparable from shared objects file]
[Loaded java.lang.CharSequence from shared objects file]
[Loaded java.lang.String from shared objects file]
...
[Opened C:\Java\jdk1.6.0_18\jre\lib\rt.jar]
[Loaded java.nio.charset.Charset$3 from C:\Java\jdk1.6.0_18\jre\lib\rt.jar]

为了启动速度,JAVA 6开始将一些类预先载入,不再依赖于rt.jar,也就是classpath的读取在预载入之后,预载入中没有 rt.jar,就会出现错误
查找资料后,设置-Xbootclasspath也许会解决我们的问题,试验一下

java -Xbootclasspath:c:\\Java\\jdk1.6.0_18\\jre\\lib\\rt.jar -verbose

结果如下:

[Opened c:\Java\jdk1.6.0_18\jre\lib\rt.jar]
[Loaded java.lang.Object from c:\Java\jdk1.6.0_18\jre\lib\rt.jar]
[Loaded java.io.Serializable from c:\Java\jdk1.6.0_18\jre\lib\rt.jar]
[Loaded java.lang.Comparable from c:\Java\jdk1.6.0_18\jre\lib\rt.jar]
[Loaded java.lang.CharSequence from c:\Java\jdk1.6.0_18\jre\lib\rt.jar]
[Loaded java.lang.String from c:\Java\jdk1.6.0_18\jre\lib\rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from c:\Java\jdk1.6.0_18\jre\lib\rt.jar]

That’s Great!

问题解决:

将测试代码改成

require 'rubygems'
require 'rjb'
Rjb::load(classpath = "." , 
[
"-Xbootclasspath:c:\\Java\\jdk1.6.0_18\\jre\\lib\\rt.jar"
])
str = Rjb::import("java.lang.String")

前面的错误将解决,我们也将迎来新的错误:

Error occurred during initialization of VM
java.lang.UnsatisfiedLinkError: no zip in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1734)
        at java.lang.Runtime.loadLibrary0(Runtime.java:823)
        at java.lang.System.loadLibrary(System.java:1028)
        at java.lang.System.initializeSystemClass(System.java:1086)

2. no zip in java.library.path

显然因为zip.dll没有包含在library路径中,很自然有下面的解决方案

require 'rubygems'
require 'rjb'
Rjb::load(classpath = "." , 
[
"-Xbootclasspath:c:\\Java\\jdk1.6.0_18\\jre\\lib\\rt.jar" ,
"-Djava.library.path=c:\\Java\\jdk1.6.0_18\\jre\\bin"
])
str = Rjb::import("java.lang.String")

不过运行后 no zip 依旧

问题分析:

zip.dll 是在 rt.jar的类要求链接的库,rt.jar都是预载入的,zip.dll当然也要预载入…

问题解决:

require 'rubygems'
require 'rjb'
Rjb::load(classpath = "." , 
[
"-Xbootclasspath:c:\\Java\\jdk1.6.0_18\\jre\\lib\\rt.jar" ,
"-Dsun.boot.library.path=c:\\Java\\jdk1.6.0_18\\jre\\bin"
])
str = Rjb::import("java.lang.String")
input = str.new("we got it")
puts input.toString()

We got it

3. 至此,RJB基本可以在cygwin下使用,但是当我们使用ICTCLAS的时候,还是出现了一些问题的

先根据参考文章3,编译ICTCLAS4J,因为我们在之后要hack ICTCLAS4J

测试代码:

require 'rubygems'
require 'rjb'
root_ICTCLAS = "C:\\cygwin\\home\\Tachikoma\\workspace\\try\\ICTCLAS"
root_jdk = "c:\\Java\\jdk1.6.0_18"

Rjb::load(classpath = ".;#{root_ICTCLAS}\\bin;#{root_ICTCLAS}\\commons-lang-#2.5\\commons-lang-2.5.jar" , 
[
"-Xbootclasspath:#{root_jdk}\\jre\\lib\\rt.jar" ,
"-Dsun.boot.library.path=#{root_jdk}\\jre\\bin" ,
"-Duser.dir=#{root_ICTCLAS}"
])
segtag_class = Rjb::import('org.ictclas4j.segment.SegTag')
segtag = segtag_class.new_with_sig("I",1)
seg_res = segtag.split("今天好累...")
result = seg_res.getFinalResult()
puts result

运行后出现错误:

test.rb:12:in `method_missing': unknown exception (NullPointerException)
        from test.rb:12

问题分析: 猜测错误是没有用的,只能侵入ICTCLAS4J的代码,看个究竟。略去调试过程,得到的第一个问题是在org\ictclas4j\bean\Dictionary.java 71行,file.canRead()返回false。解释一下:ICTCLAS4j/Data中的数据文件没有读取权限,所以返回了空指针,导致失败。这个问题涉及到Windows,Cygwin,Ruby,RJB,JAVA之间的权限联动问题,我也说不清楚,只提供解决方法。发现了file = new File(filename) ,当filename为绝对路径时,具有读权限,为相对路径是,没有读权限。
问题解决: 在org\ictclas4j\bean\Dictionary.java load函数和save函数中,关于file的初始化部分,修改成

file = new File(filename);
filename = file.getAbsolutePath();
file = new File(filename);

(就两个地方需要修改,就不重构提取函数了)

运行代码,错误依旧?

由于省去了调试过程,所以看不到出错地点,不过我确定文件权限的问题,已经就此解决

那么剩下的错误是由什么引起的?

4. GBK的错误

我们在org\ictclas4j\bean\Dictionary.java 125行左右处理IOException的代码改成如下

} catch (IOException e) {
	e.printStackTrace();
	//logger.error(e);
}

 

(关于ICTCLAS4源代码作者如此处理这个错误…发点牢骚,logger只让程序员看到,但是用户也应当看到一些错误信息)

再运行测试代码,得到错误:

java.io.UnsupportedEncodingException: GBK
        at java.lang.StringCoding.decode(StringCoding.java:170)
        at java.lang.String.<init>(String.java:443)
        at java.lang.String.<init>(String.java:515)
        at org.ictclas4j.bean.Dictionary.load(Dictionary.java:102)
        at org.ictclas4j.bean.Dictionary.load(Dictionary.java:52)
        at org.ictclas4j.segment.PosTagger.<init>(PosTagger.java:39)
        at org.ictclas4j.segment.SegTag.<init>(SegTag.java:33)

没有支持GBK的编码包,这个好办,加载C:\\Java\\jdk1.6.0_18\\jre\\lib\\charsets.jar就可以了

测试代码修改如下

require 'rubygems'
require 'rjb'
root_ICTCLAS = "C:\\cygwin\\home\\Tachikoma\\workspace\\try\\ICTCLAS"
root_jdk = "c:\\Java\\jdk1.6.0_18"

Rjb::load(classpath = ".;#{root_ICTCLAS}\\bin;#{root_ICTCLAS}\\commons-lang-#2.5\\commons-lang-2.5.jar;C:\\Java\\jdk1.6.0_18\\jre\\lib\\charsets.jar" , 
[
"-Xbootclasspath:#{root_jdk}\\jre\\lib\\rt.jar" ,
"-Dsun.boot.library.path=#{root_jdk}\\jre\\bin" ,
"-Duser.dir=#{root_ICTCLAS}"
])
segtag_class = Rjb::import('org.ictclas4j.segment.SegTag')
segtag = segtag_class.new_with_sig("I",1)
seg_res = segtag.split("今天好累...")
result = seg_res.getFinalResult()
puts result

5. 终于,终于…

$ ruby test.rb 
今/g 天/g 好/g 累/v ../m 

 

很好的结果

P.S. 如果看到的是乱码,记得把ruby的源文件按照utf-8的编码存储

6. 小感慨下

写完了才发现:有用的部分只是我的调试过程中很小的部分,实际调试的过程中,侵入了RJB的代码,ICTCLAS4J的代码,不得不花时间建立些小工具方便调试。

休息休息…

7.不得不承认,再过了一天后,我们的程序出了问题

问题大致是这样的:我们的程序只能对于"今天好累..."这样的短句分词,当涉及到"他从马上摔下来了“这样的例子,依然会看到NullPointerException这样的错误。

Hack进ICTCLAS的源程序,也没什么收获。只得对比Windows中调用ICTCLAS(通过segtag.bat脚本)和我们在Cygwin下调用ICTCLAS脚本的过程,在java选项中加入-verbose查看类载入过程(判断还是由于哪个类载入不正确或者Data文件夹下字典文件读取不正确造成的),没有发现特别的区别,只是charsets.jar也被作为bootclass载入了,于是修改测试代码中Rjb的载入部分如下:

 

Rjb::load(classpath = ".;#{root_ICTCLAS}\\bin;#{root_ICTCLAS}\\commons-lang-2.5\\commons-lang-2.5.jar" , 
[
"-Xbootclasspath:#{root_jdk}\\jre\\lib\\rt.jar;C:\\Java\\jdk1.6.0_18\\jre\\lib\\charsets.jar" ,
"-Dsun.boot.library.path=#{root_jdk}\\jre\\bin" ,
"-Duser.dir=#{root_ICTCLAS}"
])

将charsets.jar也调入bootclass中,运行结果成功

这个错误的修正,也算是运气,实际并没有什么理论的依据,而且对于charsets.jar的载入顺序问题,整个机制并没有给出错误或警告

 

 

posted @ 2010-04-15 14:17  Tachikoma  阅读(1662)  评论(0编辑  收藏  举报