人是要逼的

2009年3月3号,送22去火车站,他留下了带不走的书、CD、雨伞、长鼻王等物,我唯独挑了一本书。

我曾经以为我永远都不会看这本书的,谁曾想,如今我居然在一天之内把这本书看完,第二天就要用这书里讲的技术来做项目。

很多很多年前,我以为自己永远都不会去碰Java,我讨厌这个慢如蜗牛的巨无霸,可是如今我已经跟它打了一年的交道。

你永远都不会知道明天面临的会是什么。

《Spring in action》这书很神奇,第一章会灌输给你一大堆的东西,然后告诉你看不懂没关系,后面会仔细讲的,等我看到后面的时候我发现果然仔细讲了,可是我已经在第一章琢磨懂了。。。

XXX in action貌似是很出名的一个系列,更出名的是Thinking in XXX系列,可惜没有Thinking in Spring,应该写一本,然后中文叫《思春》。

很难想象你被逼到一个项目用了3个以上你不懂的技术,你要用一天看完一本书,然后第二天就要去完成这个项目。虽然我还是没搞定。。。但是我深切体会到用最短的时间掌握最多的知识的时候是有快感的,很爽。

将来会无数次面临这样的压力吧,whatever,人总不能被逼死。

最近神奇的某电视台又出现了很神奇的报道,给我和我的搭档打击很大。在这全国大河蟹的情况下我们的未来似乎面临着巨大的风险,我的一些神奇的idea会被扼杀在摇篮中么?whatever,风险和收益成正比不是么,据说人生总是要归零好几次的。

我也发现当人面临巨大的压力时候一句很简单的鼓励都会有很神奇的功效,谁来鼓励我这颗豆呢?是你?你?还是你?

在我埋头看书的这个夜晚,是传说中的520,baidu的logo上写着非诚勿扰,然后就有些神奇的人趁着这个神奇的日子做了些神奇的事情。说实话,我讨厌那些挑这种日子的人,520,或者1111,一定是在满世界暧昧的氛围中寂寞得找不着北了,然后就恰好和另一个统一寂寞的找不着北的人组成了一套杯具。幸好我心如那个止水。

看了两集三国,感言——人啊,关键的时候站对队伍很重要。

陈宫这个杯具,蛐蛐玩得好好的,把个通缉犯当英雄,把犯人放了给犯人做小弟,结果跟着曹操杀了一圈人,突然发现这人没人性啊,想杀之造福社会,转念又一想,我放了他又杀了他好无聊啊,还是算了吧,结果没想到眼前这个恶魔成了未来的太上皇。

作为创业初期唯一的跟班,还对老大有恩的人,将来怎么着也是个C什么O吧,可惜了。

这还不算啥,错就错在放弃了绩优股跑去跟了个没脑子的吕布,看起来骁勇异常啊,结果貂mm说我不想再过打仗的日子了就甘愿中计把命赔进去了。

于婚姻,于生活,于事业,站错了队伍,就近乎断送了自己。

关于java的值传递和引用传递

今天修了一个bug,应该是很简单的问题,但是我找了好久,特地研究一下,做个记录。

68条Java最佳实践里第一条就提到:Java参数是以by value方式而非by reference方式传递的。这个很容易理解,举个例子

ArrayList<String> strList = new ArrayList<String>();
ArrayList<Integer> intList = new ArrayList<Integer>();

String str = "Hello world!";
strList.add(str);
System.out.println(strList.get(0));
str = "Bye world!";
System.out.println(strList.get(0));

int num = 911;
intList.add(num);
System.out.println(intList.get(0));
num = 119;
System.out.println(intList.get(0));

运行结果如下:

Hello world!
Hello world!
911
911

简单解释一下吧,当str或者num传给list时,传递的实际是str或num当时的值,即"Hello world!"和911,这样即使后来改变了str和num的内容,list里的值是不会变化的。

但是我们考虑下面这个情况,看起来有些不同

ArrayList<ArrayList<String>> listList = new ArrayList<ArrayList<String>>();
listList.add(strList);
System.out.println(listList.get(0));
strList.set(0, "Another world");
System.out.println(listList.get(0));

结果变成了这个样子:

[Hello world!]
[Another world]

咦,咋不一样了?仔细想想,对,确实是值传递,传了strList的值,而这个值是一个字符串的引用,所以当你更改这个引用的时候,字符串的值就变了。

有一点绕,coding之前要三思!

68条Java最佳实践

这里记录的实际上是《Practical Java》一书的目录,该书共总结了68条Java开发中的最佳实践(best practice)。我把这个目录敲到这里来也有助于自己加深印象,部分术语按照我自己的习惯做了调整。对Java有所了解的同学们看了这个目录大概也能知道它在说什么,毕竟这本书是在总结经验而非描述技术细节。这里面很多条也适用于大多数其它的面向对象语言。

一般技术

实践1:参数以 by value 方式而非 by reference 方式传递

实践2:对不变的 data 和 object reference 使用 final

实践3:默认情况下所有非静态方法都可被重载

实践4:在 arrays 和Vectors 之间慎重选择

实践5:多态优于 instanceof

实践6:必要时才使用 instanceof

实践7:一旦不再需要 object references,就将它设为 null

对象与相等性

实践8:区分 reference type 和 primitive type

实践9:区分 == 和 equals()

实践10:不要依赖 equals() 的缺省实现

实践11:实现 equals() 时必须深思熟虑

实践12:实现 equals() 时优先考虑使用 getClass()

实践13:调用 super.equals() 以唤起基类的相关行为

实践14:在 equals() 方法中谨慎使用 instanceof

实践15:实现 equals() 时需遵循某些规则

异常处理

实践16:认识“异常控制流”机制

实践17:绝对不可忽视异常

实践18:千万不要遮掩(hide)异常

实践19:明察 throws 子句的缺点

实践20:细致而全面地理解 throws 子句

实践21:使用 finallly 避免资源泄漏

实践22:不要从 try 块中返回

实践23:将 try / catch 块置于循环之外

实践24:不要将异常用于流程控制

实践25:不要每逢出错就使用异常

实践26:在构造函数中抛出异常

实践27:抛出异常之前先将对象恢复为有效状态

性能

实践28:先把焦点放在设计、数据结构和算法身上

实践29:不要依赖编译期优化技术

实践30:理解运行期代码优化技术

实践31:如欲进行字符串拼接,StringBuffer 优于 String

实践32:将对象的创建成本降至最小

实践33:慎防未用上的对象

实践34:将同步减至最低

实践35:尽可能使用 stack 变量

实践36:使用 static、final 和 private 方法以促成内联

实践37:instance 变量的初始化一次就好

实践38:使用基本类型(primitive types)使代码更快更小

实践39:不要使用 Enumeration 或 Iterator 来遍历 Vector

实践40:使用 System.arraycopy() 来复制 arrays

实践41:优先使用 array,然后才考虑 Vector 和 ArrayList

实践42:尽可能复用(reuse)对象

实践43:使用延迟求值(lazy evaluation)

实践44:以手工方式将代码优化

实践45:编译为本机代码(native code)

多线程

实践46:面对 instance 方法,synchronized 锁定的是对象而非方法或代码

实践47:弄清楚 synchronized statics 方法与 synchronized instance 方法之间的差异

实践48:以“private 数据 + 相应访问函数(accessor)” 替换 “public/protected数据”

实践49:避免无谓的同步控制

实践50:访问共享变量时请使用 synchronized 或 volatile

实践51:在单一操作中锁定所有用到的对象

实践52:以固定而全局性的顺序取得多个locks以避免死锁

实践53:优先使用 notifyAll() 而非 notify()

实践54:针对 wait() 和 notifyAll() 使用旋锁(spin locks)

实践55:使用 wait() 和 notifyAll() 替换轮询循环

实践56:不要对上锁对象的对象引用重新赋值

实践57:不要调用 stop() 或 suspend()

实践58:通过线程之间的协作来终止线程

类与接口

实践59:运用接口来实现多继承

实践60:避免接口中的方法发生冲突

实践61:如需提供部分实现,请似乎用抽象类

实践62:区分接口(interface)、抽象类(abstract class)和实体类(concrete class)

实践63:审慎地定义和实现不可变(immutable)类

实践64:欲传递或接受可变(mutable)对象的对象引用时,请实现 clone()

实践65:使用继承或委托来定义不可变类

实践66:实现 clone() 时记得调用 super.clone()

实践67:别只依赖于 finalize() 清理内存以外的资源

实践68:在构造函数内调用 non-final 方法时要当心

通过Enumeration和Iterator遍历Hashtable的效率分析

今天需要遍历一个Hashtable,查看了一下Hashtable类,发现它提供了如下几个方法可供我们遍历:

  1. keys() - returns an Enumeration of the keys of this Hashtable
  2. keySet() - returns a Set of the keys
  3. entrySet() - returns a Set of the mappings
  4. elements() - returns an Enumeration of the values of this Hashtable

4种方法,那种更好呢,写段代码来比较一下吧:

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map.Entry;

public class traveseHashTable {
    public static void main(String[] args) {
	Hashtable<String, String> ht = new Hashtable<String, String>();
	for (int i = 0; i < 10000; i++) {
	    ht.put("Key=" + i, "Val=" + i);
	}

	// 1. Enumeration
	long start = System.currentTimeMillis();
	Enumeration<String> en = ht.keys();
	while (en.hasMoreElements()) {
	    en.nextElement();
	}
	long end = System.currentTimeMillis();
	System.out.println("Enumeration keys costs " + (end - start)
		+ " milliseconds");

	// 2. Enumeration
	start = System.currentTimeMillis();
	Enumeration<String> en2 = ht.elements();
	while (en2.hasMoreElements()) {
	    en2.nextElement();
	}
	end = System.currentTimeMillis();
	System.out.println("Enumeration elements costs " + (end - start)
		+ " milliseconds");

	// 3. Iterator
	start = System.currentTimeMillis();
	Iterator<String> it = ht.keySet().iterator();
	while (it.hasNext()) {
	    it.next();
	}
	end = System.currentTimeMillis();
	System.out.println("Iterator keySet costs " + (end - start)
		+ " milliseconds");

	// 4. Iterator
	start = System.currentTimeMillis();
	Iterator<Entry<String, String>> it2 = ht.entrySet().iterator();
	while (it2.hasNext()) {
	    it2.next();
	}
	end = System.currentTimeMillis();
	System.out.println("Iterator entrySet costs " + (end - start)
		+ " milliseconds");
    }
}

这里创建了一个10000个元素的Hashtable来供我们遍历,打印出结果如下:

Enumeration keys costs 6 milliseconds
Enumeration elements costs 5 milliseconds
Iterator keySet costs 10 milliseconds
Iterator entrySet costs 10 milliseconds

我们看到,通过迭代来遍历比枚举要多花尽一倍的时间。所以建议大家最好通过枚举类来遍历Hashtable哦。

基于色差的图片比对方法

在自动化测试中我们有时会遇到一些自动化工具识别不到的对象,比如一些作图工具,这里以MindManager 7为例子吧,如下图:

demo1.jpg

用rft只能抓到绘图区域的文档对象,对于文档内容无能为力,要做这样的测试我们只能够通过截图,然后比较两张图片来间接完成验证点的测试。

我们可以假设上面那张图是我们需要的期望结果,而下面这个图是实际测试中截到的结果,我们下面需要做的就是让程序可以识别出这两张图的不同之处:

demo2.jpg

最简单的方法当然是用Java读取这两张图片,然后逐个像素点进行比对。可是我们需要考虑的一点是,由于设备分辨率色差设置等的不同,我们肉眼看起来相同的图片可能在RGB级别上是完全不同的,所以我们需要能抛弃设备依赖。

计算机中的图片通常是以RGB颜色存储的,而RGB是一种依赖于设备的颜色空间,所以我们要把RGB颜色空间转换到一种设备无关的空间里。LAB是一种接近人类视觉感知的颜色空间,它能够在一定程度上消除显示设备差异带来的色彩差别,RGB不能直接转换到LAB,而需要先转换到XYZ空间,再转换成LAB。汗,好复杂,具体细节可以看维基百科,这里不多说了。

关键代码:

RGB转XYZ空间

public static ColorXYZ RGB2XYZ(int rgb) {
    ColorXYZ xyz = new ColorXYZ();

    // 计算r,g,b分量
    int r = (rgb & 0xff0000) >> 16;
    int g = (rgb & 0xff00) >> 8;
    int b = (rgb & 0xff);
    if ((r == 0) && (g == 0) && (b == 0)) {
	// 都是0了还转什么转?
	xyz.x = 0;
	xyz.y = 0;
	xyz.z = 0;
    } else {
	xyz.x = (0.490 * r + 0.310 * g + 0.200 * b)
	/ (0.667 * r + 1.132 * g + 1.200 * b);
	xyz.y = (0.117 * r + 0.812 * g + 0.010 * b)
	/ (0.667 * r + 1.132 * g + 1.200 * b);
	xyz.z = (0.000 * r + 0.010 * g + 0.990 * b)
	/ (0.667 * r + 1.132 * g + 1.200 * b);
    }

    return xyz;
}

XYZ转LAB空间

public static ColorLAB XYZ2LAB(ColorXYZ xyz) {
    ColorLAB lab = new ColorLAB();

    double x = xyz.x / 95.047;
    double y = xyz.y / 100.000;
    double z = xyz.z / 108.883;

    x = (x > 0.008856) ? Math.pow(x, 1.0 / 3.0) : (7.787 * x + 16 / 116);
    y = (y > 0.008856) ? Math.pow(y, 1.0 / 3.0) : (7.787 * y + 16 / 116);
    z = (z > 0.008856) ? Math.pow(z, 1.0 / 3.0) : (7.787 * z + 16 / 116);

    lab.l = 116 * Math.pow(y, 1.0 / 3.0) - 16;
    lab.a = 500 * (Math.pow(x, 1.0 / 3.0) - Math.pow(y, 1.0 / 3.0));
    lab.b = 200 * (Math.pow(y, 1.0 / 3.0) - Math.pow(z, 1.0 / 3.0));

    return lab;
}

计算色差

public static double getDelta(ColorLAB lab1, ColorLAB lab2) {
    double deltaL = lab1.l - lab2.l;
    double deltaA = lab1.a - lab2.a;
    double deltaB = lab1.b - lab2.b;
    return Math.pow((Math.pow(deltaL, 2) + Math.pow(deltaA, 2) + Math.pow(
	    deltaB, 2)), 0.5);
}

基于色差比对图片

public static void compareImages(String expectedImage, String actualImage, String destImage) {
    BufferedImage expected = null, actual = null, dest = null;

    try {
	expected = ImageIO.read(new File(expectedImage));
	actual = ImageIO.read(new File(actualImage));
    } catch (IOException e) {
	e.printStackTrace();
    }

    dest = new BufferedImage(expected.getWidth(), expected.getHeight(), BufferedImage.TYPE_INT_RGB);

    for (int y = 0; y < expected.getHeight(); ++y) {
	for (int x = 0; x < expected.getWidth(); ++x) {
	    int expRGB = expected.getRGB(x, y);
	    int actRGB = actual.getRGB(x, y);
	    int newRGB = 0;
	    if (expRGB != actRGB) {
		double deltaE = getDelta(RGB2LAB(expRGB), RGB2LAB(actRGB));
		if (deltaE > MAX_DELTA_THRESHOLD) {
		    newRGB = 65280;
		}
	    }
	    dest.setRGB(x, y, newRGB);
	}
    }

    try {
	FileOutputStream out = new FileOutputStream(destImage);
	JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
	encoder.encode(dest);
	out.close();
    } catch (IOException e) {
	e.printStackTrace();
    }
}

MAX_DELTA_THRESHOLD 是允许的色差阈值,这里设置成10,比对后生成的差异图片如下图所示,绿色是差异处

demo3.jpg