Cygwin中通过RJB在Ruby下调用ICTCLAS(JAVA)
参考文章:
参考文章1中,在windows中成功在Ruby中调用了ICTCLAS4J,当环境迁到Cygwin中时,出现了一些错误。本文中,将修正这些错误,在Cygwin中通过RJB在Ruby中调用ICTCLAS4J
先说明几个问题:
- Cygwin中没有合适的JDK,调用的是windows中的JDK,所以本文中JVM的环境是windows环境(比如:JVM的路径格式都是windows样式的)
- JDK默认的安装路径是 C:\Program Files\JAVA\JDKxxx ,其中"Program Files“中间的空格会给我们带来很大麻烦,比如
java -cp C:\Program Files\JAVA\JDKxxx\...
就会出错,不得不使用java -cp "C:\Program Files\JAVA\JDKxxx\..." ...
为了方便,我们要避免那个空格,有两个办法- 重装JDK,安到C:\JAVA\JDKxxx,比较笨拙但方便的方法
- 建立软链接,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的载入顺序问题,整个机制并没有给出错误或警告