显示标签为“Java”的博文。显示所有博文
显示标签为“Java”的博文。显示所有博文

2009年3月22日

玩玩Ant

多次听到牛人们闲谈时说Ant如何如何好,自己就是没有什么感觉。用Eclipse不是一切都能搞定么?

今天,为了发布一个自己写的关于LanguageIdentification的小小开源软件包,不得不弄个build.xml在里面。因此自己开始好好学习Ant了。

本想到书店买书或者到图书馆借书,结果都未果。好在找到了这个网页:

我喜欢简单:ant - java 构建工具

然后开始理解,模仿,添加,最终解决了需要处理的问题。

还有一个难点没解决,那就是如果一个Jar含有多个可以执行的主类,在ant里面写执行语句时如何指定执行主类?这个问题最终采用添加windows的bat和linux的sh文件脚本来得以解决。呵呵,笨了一点,但是确实解决了问题。(感谢bluejade的提醒)

哈哈,点击一个build.xml相关的执行bat文件,一切都在掌握之中。全自动构建就是好啊!

2009年3月21日

诡异的Java HashMap的序列化Bug (ID 4756277)

今天写个小的文本处理程序,用到了一个临时类的序列化,在类中加入了一个私有变量后运行总会出现错误

加入的私有变量如下:
private ArrayList sortedEntriesByValue;

得到的运行结果是:
java.io.NotSerializableException: java.util.HashMap$Entry

真是百思不得其解。Google了半天,发现Tomcat里面有人提到这个问题,原因好像是Session类的什么地方需要修改。这个原因和我的问题真是风马牛不相及。

再仔细查找,最终发现,原来这是Java语言目前存在的一个Bug。原来HashMap对应衍生元素除了Key可以被序列化,其他的如keySet, EntrySet, Values都不能被序列化。当然,HashMap本身还是可以被序列化的。

最后我选择了新建一个ArrayList来将KeySet中的元素转换为String后存放起来,就可以实现我需要的序列化了。

呵呵,我该买彩票啊,随便玩玩居然玩到Java自带的Bug里面去了。这个Bug为什么会存在Java里面呢,而且这个Bug是2002就被发现了啊。Sigh~! Java也不是完美的啊!

详细的sun网站上登记的Bug信息如下:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4756277


Bug ID:
4756277

Votes
7

Synopsis
(coll) HashMap's keySet(), entrySet() and values() are not serializable

Category
java:classes_util

Reported Against
1.3.1

Release Fixed

State
3-Accepted, bug

Priority:
4-Low

Related Bugs

Submit Date
01-OCT-2002

Description

FULL PRODUCT VERSION :
Occurs in both 1.3.0 and 1.3.1:

java version "1.3.0_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0_02)
Java HotSpot(TM) Client VM (build 1.3.0_02, mixed mode)

java version "1.3.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1-b24)
Java HotSpot(TM) Client VM (build 1.3.1-b24, mixed mode)

FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]
(Service Pack 2)

A DESCRIPTION OF THE PROBLEM :
The collections returned by calls to
java.util.HashMap#entrySet, #keySet and #values are not
Serializable which, although consistent with the
documentation, is confusing and counter-intuitive because
HashMap itself is Serializable.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a HashMap.
2. Attempt to serialize the result of calling keySet(),
entrySet() or values() on that HashMap.


EXPECTED VERSUS ACTUAL BEHAVIOR :
I would (naively, optimistically :-) assume these
collections to be Serializable, since the object from which
they come is itself Serializable.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
keySet():
Trying to serialize [two, one, three]...failed:
java.io.NotSerializableException: java.util.HashMap$1
at java.io.ObjectOutputStream.outputObject(ObjectOutputStream.java:1148)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:366)
at
test.MapCollectionsSerializeMain.trySerialize(MapCollectionsSerializeMain.java:15)
at
test.MapCollectionsSerializeMain.main(MapCollectionsSerializeMain.java:36)
entrySet():
Trying to serialize [two=two, one=one, three=three]...failed:
java.io.NotSerializableException: java.util.HashMap$3
at java.io.ObjectOutputStream.outputObject(ObjectOutputStream.java:1148)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:366)
at
test.MapCollectionsSerializeMain.trySerialize(MapCollectionsSerializeMain.java:15)
at
test.MapCollectionsSerializeMain.main(MapCollectionsSerializeMain.java:38)
values():
Trying to serialize [two, one, three]...failed:
java.io.NotSerializableException: java.util.HashMap$2
at java.io.ObjectOutputStream.outputObject(ObjectOutputStream.java:1148)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:366)
at
test.MapCollectionsSerializeMain.trySerialize(MapCollectionsSerializeMain.java:15)
at
test.MapCollectionsSerializeMain.main(MapCollectionsSerializeMain.java:40)

REPRODUCIBILITY :
This bug can be reproduced rarely.

---------- BEGIN SOURCE ----------
package test;

import java.io.*;
import java.util.*;

public class MapCollectionsSerializeMain
{

private static void trySerialize (Object o) {
try {
ObjectOutputStream stream
= new ObjectOutputStream(new ByteArrayOutputStream());
System.out.print("Trying to serialize " + o + "...");
stream.writeObject(o);
stream.flush();
System.out.println("done");
}
catch (Exception e) {
System.out.print("failed: ");
e.printStackTrace();
}
}

// ----------------------------------------------------------------------
// Entry point

public static void main (String[] args) {

Map map = new HashMap();
map.put("one", "one");
map.put("two", "two");
map.put("three", "three");

System.out.println("keySet():");
trySerialize(map.keySet());
System.out.println("entrySet():");
trySerialize(map.entrySet());
System.out.println("values():");
trySerialize(map.values());

}
}

---------- END SOURCE ----------

CUSTOMER WORKAROUND :
For the keySet() and valueSet() cases, one could create a
new HashSet from those sets, but that's inefficient and it
doesn't cover the values() case.

The right answer in the first place is presumably that any
Serializable object which contains a Set (or xxxxx abstract
type not documented as Serializable) must implement its own
readObject and writeObject methods which (de-)serialize the
set manually. Either that, or define the object to contain
a HashSet (or xxxxx type documented as Serializable).
(Review ID: 165184)
======================================================================

Work Around

N/A

Evaluation

N/A

2009年3月6日

Java编码

和自然语言处理打交道,必不可少的一环就是汉字编码问题。这不,今天又被折腾了一次。俺们的Mr. Hu说了,今年的目标之一是不折腾。所以得想个好招儿才行。

问 题是这样的,我在实验室已毕业的技术大牛Victor的指导下,准备采用Web Server的方式来整合我所带领的共指消解小组的各种特征抽取工作。各项工作起初都进展顺利,最后卡在了汉字编码上。由于建立的Web Server只接收UTF-8格式的URL,恰好俺对Java处理中文编码问题不是很熟悉,找了几种方案都感觉不是万全之计。最后采用了一种极其暴力的解 决方案。那就是将Eclipse的默认编码环境全部指定为UTF-8。

至此,我面前的一座大山被彻底摧垮,这种方式的空间里再现青山绿水的hexie :)

2008年6月5日

遭遇编码问题

        编写文本处理相关的程序,编码问题向来是一个非常容易出现问题的区域。这不,今天我就又一次被“折腾”了 :)

        前几天写好的Stanford Parser的Wrapper在进行Tokenize后和原文进行alignment以获取对应各元素的begin和end。但是今天出现一篇文档,在Upali那里出现问题,我这里也出现问题,Xiaofeng那里却一点问题没有。高兴的是总算还有一个好使的,沮丧的是同样的程序同样的文本,结果怎么就会不一样呢?一度无奈时我开始怀疑自己的rp了。但一想不对啊,upali的也不行啊,这位斯里兰卡大哥人很好的,编程绝对的高手,linux方面更是无人能及。

        最后最后,和xiangfeng详细对比了他的机器和我的机器的各种配置,发现居然是系统默认的编码格式不一样。系统默认语言是英文时对应的是Cp1252,默认为中文时对应编码为GBK。据查实,Java虚拟机就是根据默认编码来处理相关文本的,最终导致了处理一个Ascii不能显示的字符处xiaofeng的可以,我的不行。我在将系统默认语言改为英文后这个问题不再存在。Submit SVN后在Upali那里运行,结果还是不行,这可真是大跌眼镜啊。经过仔细盘查,终于发现Upali大哥是自己写的编码转换程序,分别采用过utf8和ISO-8859-1,经过俺修改为Cp1252后,程序终于运行起来了。正确运行那一刻upali大哥也高兴的惊呼一声。

    由此知道了为什么xiaofeng的机器好使,我和upali的都不行。

    这Java虚拟机还真霸道啊,改明儿个有空了,好好学习一下如何解决摆脱系统默认语言的限制。

2008年3月6日

神奇的Eclipse性能测试TPIP插件

昨天下午发现自己现在的Java代码运行一些复杂情况时相当的慢,运行一遍居然需要好几个小时,这种情况真是让人难以忍受。起初猜想了几处可能的问题,但是始终没有直接的感受,自己也说不清楚慢的根本原因。

记得上次在浏览Eclipse的各种插件的时候看到过有用于性能测试的插件,凭着模糊的记忆,开始搜索相关的插件。

开始实验了一个叫Profiler的插件,弄了半天才发现这个东西不支持较新的Eclipse和Jre,而且SF的项目首页上明确写了这个项目已经死掉。呵呵,只能怪自己没有先看文档啊。

后来发现了Eclipse下另外一个更好的插件TPTP(Eclipse Test & Performance Tools Platform Project)。记得这个东西是Eclipse 3.3 Europe发布时大力推崇的东东,只是原来不知道有这么强大。

这个东西的安装非常的麻烦,因为需要预装很多乱七八糟的插件,昨晚折腾到凌晨两点也没有弄好,包括在今年2月份才发布的最新版的Eclipse 3.4M5。好在今天上午继续搜索时发现了TPTP网站上有All-in-one版的TPTP。仔细观察,这个版本对应的Eclipse 是3.3.2,居然和Eclipse网站首页发布的最新版本号一致。这个说明,目前最新版的Eclipse中最稳定的还是3.3.2。

All-in-one版的TPTP包含了很多需要预装的插件,同时也包含了Eclipse。打开Eclipse后,按照官方提供的TPTP的所有文档中找到了一个示范性的举例,依葫芦画瓢,终于学会了使用这个东东。

这里展示几张官方的图片:

 

性能测试项目的结构

 

类之间的交互流程图

 

函数之间的工作流图

 

各包、类、函数的运行时间

 

这个东西能够非常详细的分析整个程序中各个包、类、函数的运行情况,包括时间占用、调用分析、内存占用、代码覆盖等等。使用起来真是非常的舒心。唯一一点不好的是,程序中类多一点的话,速度相当的慢。还好我不需要每天分析我的代码。忍受一两次也就行了。谁让我现在的项目中有好百十来个类呢。

嗯,靠着这个工具,我找到了我的代码中最为耗时的几个函数,感觉有点出乎意料啊。这就开始进一步分析去了 :)

2008年2月19日

学习单元测试

说到单元测试,我想很多朋友都听说过或者学过一点,但是真正贯彻执行的少之又少。我也是这样的 :)

为什么需要再学习一下单元测试呢?因为我现在正在进行的项目中遇到了很多的Bug,当然称之为Bug也不完全对,因为在最初编程的时候没有考虑到会出现这些情况。我想通过一种方法来加速我完成项目的时间以及提高完成代码的质量。

忽然在Yiyan上看到这样一篇文章《实践的一小步-代码质量的一大步》,其中提到了提高代码质量的三招实践攻略:单元测试,代码覆盖,持续整合。读罢顿觉不错,好的项目都是需要这个东西的。其实再仔细想想我们经常提到的发表论文前的相关实验,我敢保证,很少有人说他的实验部分的代码是100%没有Bug的。很多时候,实验结果都是会受到代码质量的影响的。

为了弥补自己软件开发方面的不足,我打算从单元测试开始学习。争取以后都能在写代码前写好测试用例。

在Google上随便转了一下,发现这篇文章《单元测试》写得很好。在提到测试用例和实际开发的顺序时,作者的态度是“在实际的工作中,可以不必过分强调先什么后什么,重要的是高效和感觉舒适”。他的结论是“先编写产品函数的框架,然后编写测试函数,针对产品函数的功能编写测试用例,然后编写产品函数的代码,每写一个功能点都运行测试,随时补充测试用例。所谓先编写产品函数的框架,是指先编写函数空的实现,有返回值的随便返回一个值,编译通过后再编写测试代码,这时,函数名、参数表、返回类型都应该确定下来了,所编写的测试代码以后需修改的可能性比较小。”感觉这个经验相当不错,之所以很多人不愿意写测试用例,我想也是和没有找到这样的经验有关。

对于单元测试,一些流行的误解如下:

  • 它浪费了太多的时间
  • 它仅仅是证明这些代码做了什么
  • 我是个很棒的程序员, 我是不是可以不进行单元测试?
  • 不管怎样, 集成测试将会抓住所有的Bug
  • 它的成本效率不高

这里就不一一解答这些误解了,感兴趣的朋友可以看看《单元测试》中的解释。

单元测试的优点有:

  • 它是一种验证行为

程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支缓。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。

  • 它是一种设计行为

编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。

  • 它是一种编写文档的行为

单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。

  • 它具有回归性

自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。

单元测试主要包括黑盒测试以及白盒测试,黑盒测试主要是在函数功能上进行测试,不会深入到函数内部;白盒测试是对函数的逻辑进行测试,需要深入到函数的内部,需要覆盖的逻辑单位主要有:语句、分支、条件、条件值、条件值组合,路径。语句覆盖就是覆盖所有的语句,其他类推。另外还有一种判定条件覆盖,其实是分支覆盖与条件覆盖的组合。跟条件有关的覆盖就有三种,解释一下:条件覆盖是指覆盖所有的条件表达式,即所有的条件表达式都至少计算一次,不考虑计算结果;条件值覆盖是指覆盖条件的所有可能取值,即每个条件的取真值和取假值都要至少计算一次;条件值组合覆盖是指覆盖所有条件取值的所有可能组合。作者做过一些粗浅的研究,发现与条件直接有关的错误主要是逻辑操作符错误,例如:||写成&&,漏了写!什么的,采用分支覆盖与条件覆盖的组合,基本上可以发现这些错误,另一方面,条件值覆盖与条件值组合覆盖往往需要大量的测试用例,因此,在作者看来,条件值覆盖和条件值组合覆盖的效费比偏低。

作者认为效费比较高且完整性也足够的测试要求是这样的:完成功能测试,完成语句覆盖、条件覆盖、分支覆盖、路径覆盖。这种测试要求其实是比较高的,但是借助于一些相关的工具还是能方便的完成的。

 

工具篇:

        单元测试最著名的工具我想就是Unit系列吧,C++的CppUnit,Java的JUnit,Python的PyUnit等等,还有一些其他工具吧。由于目前我主要采用Java进行编程,搜罗一下Java的单元测试工具(Eclipse下使用的):JUnit4, TestNG。阅读了一篇关于这两个重量级工具的对比文章(《追求代码质量: JUnit 4 与 TestNG 的对比》)后,我决定使用TestNG来作为我的单元测试工具。

 

经过三个小时的尝试后,不得不放弃TestNG,原因是它不支持Eclipse 3.3,感觉好像支持jdk1.6也不太好,似乎TestNG跟不上时代啊!再一眼,发现Junit到是默认出现在了Eclipse 3.3里面,那就试试Junit4咯。呵呵,只要能提高效率就要,为什么一定要用TestNG呢 :)

 

学习几个相关的JUnit教程,感觉这个系列最好:

  1. 在Eclipse中使用JUnit4进行单元测试(初级篇)
  2. 在Eclipse中使用JUnit4进行单元测试(中级篇)
  3. 在Eclipse中使用JUnit4进行单元测试(高级篇)

学完后有这几个心得:

1. Eclipse下创建单元测试非常方便,很多时候只需要点击鼠标即可完成绝大多数的工作,而且可以通过创建很多的单元测试后用打包的方式全部执行。

2. JUnit4比JUnit3多出了许多TestNG具有的特性

3. JUnit4主要有如下几点:

  1. 包含必要地Package
  2. 测试类的声明
  3. 测试方法的声明,以@开头,例如@Before、@Test、@After
  4. 忽略测试某些尚未完成的方法@Ignore
  5. 限时测试,防止死循环,用timeout如@Test(timeout = 1000)
  6. 测试异常,用expected 如 @Test(expected = ArithmeticException.class)
  7. 选择运行器@RunWith(...)
  8. 参数化测试,用于实例数较多的测试,用@RunWith(Parameterized.class)配合Collection的asList
  9. 打包测试,import org.junit.runners.Suite; @RunWith(Suite.class)

4.JUnit4的功能非常强大,自己以后的编程习惯应该是先写整个程序的类及函数框架,然后生成大量的对各个相关函数的单元测试,并且写好打包测试,然后每次实现一个函数就运行一次打包测试,从而逐渐消除所有的Failure和Error,其间如果需要对类及函数框架做出增删改,都必须立即更新单元测试的结构和代码,直到完成

5. 对于具有单元测试的项目,代码的组织可以是在当前文件夹下新建两个文件夹java及test,分别存放正式的代码文件和测试文件,这样有利于最终的文件提取。

 

哈哈,一口气写了这么多,就这样吧!算是自己对单元测试的一次较为深入的学习。

2008年2月5日

Stanford Parser加速之HashCode和序列化

现在的Java程序中用到了Stanford Parser,在程序功能完整实现后,我开始对这个Parser的使用进行加速。

由于采用的直接嵌入式的调用,没有遇到命令行调用方式下会遇到的资源重复加载的问题。在主控程序中对Parser采用一个类(A)的静态函数来加载所需资源到该类的静态成员变量上,就得到了一个全局的常驻内存资源。哈哈,第一次采用这种方案来实现全局资源,感觉甚为不错!在类A中另写了一个静态函数来执行句法分析。在对文件的处理循环中的某个特征抽取部分,使用类A的静态句法分析函数,从而实现了全局的调用。

加速方案我采用的主要有两种:

  1. 如果pair的第二个元素和第一个元素位于同一个句子,就只parse一次。
  2. 在类A的parse函数中对已经parse过的句子直接加载parse结果。

第一个方法实现起来非常简单,关键是第二个。尝试了几种解决方案后我最后结合hashcode和序列化方法实现了这个功能。这里记录一下,以备查找。

hashcode是对即将Parse的句子采用.HashCode()方法获得这个对象唯一的整数索引值,将这个索引值对应成文件名即可实现每个句子parse后对应一个唯一的文件。说起来这个hashcode函数还真是很神奇的 :)

序列化是我在Python里面常用的技巧,想来就是将对象存入文件以及从文加载对象两步。基于原理都是一样的。但是看了一下Java中对序列化的介绍后,感觉序列化功能也是有巨大应用功能的。参考了一下如下一段讲述Java序列化方法的文章后完成了我的Parse加速。

3.一般序列化实例
程序名称:SerializationDemo.java
程序主题:实现对象的序列化和反序列化
程序说明:该程序由实例化一个MyClass类的对象开始,该对象有三个实例变量,类型分别为String、int、double,是希望存储和恢复的信息。
import java.io.Serializable;
class MyClass implements Serializable {
    String s;
    int i;
    double d;
    public MyClass(String s, int i, double d) {
        this.s = s;
       this.i = i;
       this.d = d;
    }
    public String toString() {
       return "s=" + s + ";i=" + i + ";d=" + d;
    }
}
要想序列化对象,你必须先创建一个OutputStream,然后把它嵌进ObjectOutputStream。这时,你就能用writeObject ( )方法把对象写入OutputStream了。读的时候,你得把InputStream嵌到ObjectInputStream里面,然后再调用 readObject( )方法。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializationDemo {
        public static void main(String args[]) {
       FileInputStream in = null;
       FileOutputStream out = null;
       ObjectInputStream oin = null;
       ObjectOutputStream oout = null;
       MyClass object1 = new MyClass("Hello", -7, 2.7e10);
       System.out.println("object1:" + object1);
       // Object serialization
       try {
           out = new FileOutputStream("serial.txt");
           oout = new ObjectOutputStream(out);
           oout.writeObject(object1);
           oout.close();
       } catch (Exception e) {
           System.out.println("Exception during serialization:" + e);
           System.exit(0);
       }
       // Object deserialization
        try {
           MyClass object2;
           in = new FileInputStream("serial");
           oin = new ObjectInputStream(in);
           object2 = (MyClass) oin.readObject();
           oin.close();
           System.out.println("object2:" + object2);
       } catch (Exception e) {
           System.out.println("Exception during deserialization:" + e);
           System.exit(0);
       } finally {
           // …此处省略
       }
    }
}
结果:
object1:s=Hello;i=-7;d=2.7E10
object2:s=Hello;i=-7;d=2.7E10

 

现在我的程序正在健步如飞的运行着,任何两个一模一样的句子都只会被parse一次。

 

序列化和hashcode搭配起来真是不错~!