百度加Google查找unicode字符操作方法,翻了好几页,总是找到不合适的源码或实例。没有办法,自己写吧!
需求:实现汉字字符转换成unicode,unicode恢复还原汉字字符
[code]
public class UnicodeUtil {
/**
* unicode字符恢复
* @param unicode
* @return 转换成功的字符
*/
public static String unicode2Char(String unicode) {
int len = unicode.length();
StringBuffer chinese = new StringBuffer(len);
char tmp;
for(int index=0;index<len;){
tmp = unicode.charAt(index++);
if(tmp=='\\'){
tmp = unicode.charAt(index++);
if(tmp=='u'){
StringBuffer cTmp = new StringBuffer(4);
for(int i=0;i<4;i++){
tmp = unicode.charAt(index++);
cTmp.append(tmp);
}
chinese.append((char) Integer.parseInt(cTmp.toString(), 16));
}
}else{
chinese.append(tmp);
}
}
return chinese.toString();
}
/**
* 字符转换成unicode
* @param s
* @return unicode字符
*/
public static String char2Unicode(String s) {
StringBuffer unicode = new StringBuffer();
for(char c:s.toCharArray()){
if(c>128){
unicode.append("\\u").append(Integer.toHexString(c));
}else{
unicode.append(c);
}
}
return unicode.toString();
}
}
[/code]
说干就干,要跨域,那么就要有两个域名,本机测试,只要指host就行了。host设置如下:
127.0.0.1 www.a.com
127.0.0.1 www.b.com
接着开始上代码:
http://www.b.com/b.jsp 通过js设置a的cookie
================
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%
response.addHeader("Cache-Control","no-cache");
response.addHeader("Expires","Thu,01 Jan 1970 00:00:01 GMT");
cookiev = "test";
%>
================
http://www.a.com/a_setcookie.jsp www.a.com下的cookie设置
================
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%
response.setHeader("P3P","CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"");
String cookiev = request.getParameter("cookiev");
Cookie _cookie = new Cookie("test",cookiev );
_cookie.setMaxAge(30*60*100);
_cookie.setPath("/");
_cookie.setDomain(".a.com");
response.addCookie(_cookie);
%>
================
http://www.a.com/a_getcookie.jsp www.a.com下的cookie遍历
================
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%
Cookie cookies[]=request.getCookies();
Cookie sCookie = null;
if(cookies==null){
out.print("none any cookie");
}else{
out.println(cookies.length+"
“);
for(int i=0;i
out.println("getVersion==>>>”+sCookie.getVersion()+”\n”);
out.println(“cookiename==>>>”+sCookie.getName()+”->cookievalue==>>>”+sCookie.getValue()+”
“);
}
}
%>
================
花絮:在firefox下可以不用设置response头的p3p申明,而ie是必须的。
在Eclipse里 export 选 JavaDoc,在向导的最后一页的Extra JavaDoc Options 里填上参数即可
比如项目采用的是UTF-8的编码就填:-encoding UTF-8 -charset UTF-8
译者注 :你可能会觉得Java很简单,Object的equals实现也会非常简单,但是事实并不是你想象的这样,耐心的读完本文,你会发现你对Java了解的是如此的少。如果这篇文章是一份Java程序员的入职笔试,那么不知道有多少人会掉落到这样的陷阱中。原文转自http://www.artima.com/lejava/articles/equality.html 三位作者都是不同领域的大拿,有兴趣的读者可以从上面这个连接直接去阅读原文。
摘要
本文描述重载equals方法的技术,这种技术即使是具现类的子类增加了字段也能保证equal语义的正确性。
在《Effective Java》的第8项中,Josh Bloch描述了当继承类作为面向对象语言中的等价关系的基础问题,要保证派生类的equal正确性语义所会面对的困难。Bloch这样写到:
除非你忘记了面向对象抽象的好处,否则在当你继承一个新类或在类中增加了一个值组件时你无法同时保证equal的语义依然正确
在《Programming in Scala》中的第28章演示了一种方法,这种方法允许即使继承了新类,增加了新的值组件,equal的语义仍然能得到保证。虽然在这本书中这项技术是在使用Scala类环境中,但是这项技术同样可以应用于Java定义的类中。在本文中的描述来自于Programming in Scala中的文字描述,但是代码被我从scala翻译成了Java
常见的等价方法陷阱
java.lang.Object 类定义了equals这个方法,它的子类可以通过重载来覆盖它。不幸的是,在面向对象中写出正确的equals方法是非常困难的。事实上,在研究了大量的Java代码后,2007 paper的作者得出了如下的一个结论:
几乎所有的equals方法的实现都是错误的!
这个问题是因为等价是和很多其他的事物相关联。例如其中之一,一个的类型C的错误等价方法可能意味着你无法将这个类型C的对象可信赖的放入到容器中。比如说,你有两个元素elem1和elem2他们都是类型C的对象,并且他们是相等,即elem1.equals(elm2)返回ture。但是,只要这个equals方法是错误的实现,那么你就有可能会看见如下的一些行为:
Set hashSet<C> = new java.util.HashSet<C>();
hashSet.add(elem1);
hashSet.contains(elem2); // returns false!
当equals重载时,这里有4个会引发equals行为不一致的常见陷阱:
1. 定义了错误的equals方法签名(signature) Defining equals with the wrong signature.
2. 重载了equals的但没有同时重载hashCode的方法。 Changing equals without also changing hashCode.
3. 建立在会变化字域上的equals定义。 Defining equals in terms of mutable fields.
4. 不满足等价关系的equals错误定义 Failing to define equals as an equivalence relation.
在剩下的章节中我们将依次讨论这4中陷阱。
陷阱1:定义错误equals方法签名(signature)
考虑为下面这个简单类Point增加一个等价性方法:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
// …
}
看上去非常明显,但是按照这种方式来定义equals就是错误的。
// An utterly wrong definition of equals
public boolean equals(Point other) {
return (this.getX() == other.getX() && this.getY() == other.getY());
}
这个方法有什么问题呢?初看起来,它工作的非常完美:
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
Point q = new Point(2, 3);
System.out.println(p1.equals(p2)); // prints true
System.out.println(p1.equals(q)); // prints false
然而,当我们一旦把这个Point类的实例放入到一个容器中问题就出现了:
import java.util.HashSet;
HashSet<Point> coll = new HashSet<Point>();
coll.add(p1);
System.out.println(coll.contains(p2)); // prints false
为什么coll中没有包含p2呢?甚至是p1也被加到集合里面,p1和p2是是等价的对象吗?在下面的程序中,我们可以找到其中的一些原因,定义p2a是一个指向p2的对象,但是p2a的类型是Object而非Point类型:
Object p2a = p2;
现在我们重复第一个比较,但是不再使用p2而是p2a,我们将会得到如下的结果:
System.out.println(p1.equals(p2a)); // prints false
到底是那里出了了问题?事实上,之前所给出的equals版本并没有覆盖Object类的equals方法,因为他的类型不同。下面是Object的equals方法的定义
public boolean equals(Object other)
因为Point类中的equals方法使用的是以Point类而非Object类做为参数,因此它并没有覆盖Object中的equals方法。而是一种变化了的重载。在Java中重载被解析为静态的参数类型而非运行期的类型,因此当静态参数类型是Point,Point的equals方法就被调用。然而当静态参数类型是Object时,Object类的equals就被调用。因为这个方法并没有被覆盖,因此它仍然是实现成比较对象标示。这就是为什么虽然p1和p2a具有同样的x,y值,”p1.equals(p2a)”仍然返回了false。这也是会什么HasSet的contains方法返回 false的原因,因为这个方法操作的是泛型,他调用的是一般化的Object上equals方法而非Point类上变化了的重载方法equals
一个更好但不完美的equals方法定义如下:
// A better definition, but still not perfect
@Override public boolean equals(Object other) {
boolean result = false;
if (other instanceof Point) {
Point that = (Point) other;
result = (this.getX() == that.getX() && this.getY() == that.getY());
}
return result;
}
现在equals有了正确的类型,它使用了一个Object类型的参数和一个返回布尔型的结果。这个方法的实现使用instanceof操作和做了一个造型。它首先检查这个对象是否是一个Point类,如果是,他就比较两个点的坐标并返回结果,否则返回false。
陷阱2:重载了equals的但没有同时重载hashCode的方法
如果你使用上一个定义的Point类进行p1和p2a的反复比较,你都会得到你预期的true的结果。但是如果你将这个类对象放入到HashSet.contains()方法中测试,你就有可能仍然得到false的结果:
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
HashSet<Point> coll = new HashSet<Point>();
coll.add(p1);
System.out.println(coll.contains(p2)); // 打印 false (有可能)
事实上,这个个结果不是100%的false,你也可能有返回ture的经历。如果你得到的结果是true的话,那么你试试其他的坐标值,最终你一定会得到一个在集合中不包含的结果。导致这个结果的原因是Point重载了equals却没有重载hashCode。
注意上面例子的的容器是一个HashSet,这就意味着容器中的元素根据他们的哈希码被被放入到”哈希桶 hash buckets”中。contains方法首先根据哈希码在哈希桶中查找,然后让桶中的所有元素和所给的参数进行比较。现在,虽然最后一个Point类的版本重定义了equals方法,但是它并没有同时重定义hashCode。因此,hashCode仍然是Object类的那个版本,即:所分配对象的一个地址的变换。所以p1和p2的哈希码理所当然的不同了,甚至是即时这两个点的坐标完全相同。不同的哈希码导致他们具有极高的可能性被放入到集合中不同的哈希桶中。contains方法将会去找p2的哈希码对应哈希桶中的匹配元素。但是大多数情况下,p1一定是在另外一个桶中,因此,p2永远找不到p1进行匹配。当然p2和p2也可能偶尔会被放入到一个桶中,在这种情况下,contains的结果就为true了。
最新一个Point类实现的问题是,它的实现违背了作为Object类的定义的hashCode的语义。
如果两个对象根据equals(Object)方法是相等的,那么在这两个对象上调用hashCode方法应该产生同样的值
事实上,在Java中,hashCode和equals需要一起被重定义是众所周知的。此外,hashCode只可以依赖于equals依赖的域来产生值。对于Point这个类来说,下面的的hashCode定义是一个非常合适的定义。
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override public boolean equals(Object other) {
boolean result = false;
if (other instanceof Point) {
Point that = (Point) other;
result = (this.getX() == that.getX() && this.getY() == that.getY());
}
return result;
}
@Override public int hashCode() {
return (41 * (41 + getX()) + getY());
}
}
这只是hashCode一个可能的实现。x域加上常量41后的结果再乘与41并将结果在加上y域的值。这样做就可以以低成本的运行时间和低成本代码大小得到一个哈希码的合理的分布(译者注:性价比相对较高的做法)。
增加hashCode方法重载修正了定义类似Point类等价性的问题。然而,关于类的等价性仍然有其他的问题点待发现。
陷阱3:建立在会变化字段上的equals定义
让我们在Point类做一个非常微小的变化
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) { // Problematic
this.x = x;
}
public void setY(int y) {
this.y = y;
}
@Override public boolean equals(Object other) {
boolean result = false;
if (other instanceof Point) {
Point that = (Point) other;
result = (this.getX() == that.getX() && this.getY() == that.getY());
}
return result;
}
@Override public int hashCode() {
return (41 * (41 + getX()) + getY());
}
}
唯一的不同是x和y域不再是final,并且两个set方法被增加到类中来,并允许客户改变x和y的值。equals和hashCode这个方法的定义现在是基于在这两个会发生变化的域上,因此当他们的域的值改变时,结果也就跟着改变。因此一旦你将这个point对象放入到集合中你将会看到非常神奇的效果。
Point p = new Point(1, 2);
HashSet<Point> coll = new HashSet<Point>();
coll.add(p);
System.out.println(coll.contains(p)); // 打印 true
现在如果你改变p中的一个域,这个集合中还会包含point吗,我们将拭目以待。
p.setX(p.getX() + 1);
System.out.println(coll.contains(p)); // (有可能)打印 false
看起来非常的奇怪。p去那里去了?如果你通过集合的迭代器来检查p是否包含,你将会得到更奇怪的结果。
Iterator<Point> it = coll.iterator();
boolean containedP = false;
while (it.hasNext()) {
Point nextP = it.next();
if (nextP.equals(p)) {
containedP = true;
break;
}
}
System.out.println(containedP); // 打印 true
结果是,集合中不包含p,但是p在集合的元素中!到底发生了什么!当然,所有的这一切都是在x域的修改后才发生的,p最终的的hashCode是在集合coll错误的哈希桶中。即,原始哈希桶不再有其新值对应的哈希码。换句话说,p已经在集合coll的是视野范围之外,虽然他仍然属于coll的元素。
从这个例子所得到的教训是,当equals和hashCode依赖于会变化的状态时,那么就会给用户带来问题。如果这样的对象被放入到集合中,用户必须小心,不要修改这些这些对象所依赖的状态,这是一个小陷阱。如果你需要根据对象当前的状态进行比较的话,你应该不要再重定义equals,应该起其他的方法名字而不是equals。对于我们的Point类的最后的定义,我们最好省略掉hashCode的重载,并将比较的方法名命名为 equalsContents,或其他不同于equals的名字。那么Point将会继承原来默认的equals和hashCode的实现,因此当我们修改了x域后p依然会呆在其原来在容器中应该在位置。
陷阱4:不满足等价关系的equals错误定义
Object中的equals的规范阐述了equals方法必须实现在非null对象上的等价关系:
* 自反原则:对于任何非null值X,表达式x.equals(x)总返回true。
* 等价性:对于任何非空值x和y,那么当且仅当y.equals(x)返回真时,x.equals(y)返回真。
* 传递性:对于任何非空值x,y,和z,如果x.equals(y)返回真,且y.equals(z)也返回真,那么x.equals(z)也应该返回真。
* 一致性:对于非空x,y,多次调用x.equals(y)应该一致的返回真或假。提供给equals方法比较使用的信息不应该包含改过的信息。
* 对于任何非空值x,x.equals(null)应该总返回false.
Point类的equals定义已经被开发成了足够满足equals规范的定义。然而,当考虑到继承的时候,事情就开始变得非常复杂起来。比如说有一个Point的子类ColoredPoint,它比Point多增加了一个类型是Color的color域。假设Color被定义为一个枚举类型:
public enum Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET;
}
ColoredPoint重载了equals方法,并考虑到新加入color域,代码如下:
public class ColoredPoint extends Point { // Problem: equals not symmetric
private final Color color;
public ColoredPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
@Override public boolean equals(Object other) {
boolean result = false;
if (other instanceof ColoredPoint) {
ColoredPoint that = (ColoredPoint) other;
result = (this.color.equals(that.color) && super.equals(that));
}
return result;
}
}
这是很多程序员都有可能写成的代码。注意在本例中,类ColoredPointed不需要重载hashCode,因为新的ColoredPoint 类上的equals定义,严格的重载了Point上equals的定义。hashCode的规范仍然是有效,如果两个着色点(colored point)相等,其坐标必定相等,因此它的hashCode也保证了具有同样的值。
对于ColoredPoint类自身对象的比较是没有问题的,但是如果使用ColoredPoint和Point混合进行比较就要出现问题。
Point p = new Point(1, 2);
ColoredPoint cp = new ColoredPoint(1, 2, Color.RED);
System.out.println(p.equals(cp)); // 打印真 true
System.out.println(cp.equals(p)); // 打印假 false
“p等价于cp”的比较这个调用的是定义在Point类上的equals方法。这个方法只考虑两个点的坐标。因此比较返回真。在另外一方面,“cp 等价于p”的比较这个调用的是定义在ColoredPoint类上的equals方法,返回的结果却是false,这是因为p不是 ColoredPoint,所以equals这个定义违背了对称性。
违背对称性对于集合来说将导致不可以预期的后果,例如:
Set<Point> hashSet1 = new java.util.HashSet<Point>();
hashSet1.add(p);
System.out.println(hashSet1.contains(cp)); // 打印 false
Set<Point> hashSet2 = new java.util.HashSet<Point>();
hashSet2.add(cp);
System.out.println(hashSet2.contains(p)); // 打印 true
因此虽然p和cp是等价的,但是contains测试中一个返回成功,另外一个却返回失败。
你如何修改equals的定义,才能使得这个方法满足对称性?本质上说有两种方法,你可以使得这种关系变得更一般化或更严格。更一般化的意思是这一对对象,a和b,被用于进行对比,无论是a比b还是b比a 都返回true,下面是代码:
public class ColoredPoint extends Point { // Problem: equals not transitive
private final Color color;
public ColoredPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
@Override public boolean equals(Object other) {
boolean result = false;
if (other instanceof ColoredPoint) {
ColoredPoint that = (ColoredPoint) other;
result = (this.color.equals(that.color) && super.equals(that));
}
else if (other instanceof Point) {
Point that = (Point) other;
result = that.equals(this);
}
return result;
}
}
在ColoredPoint中的equals的新定义比老定义中检查了更多的情况:如果对象是一个Point对象而不是ColoredPoint,方法就转变为Point类的equals方法调用。这个所希望达到的效果就是equals的对称性,不管”cp.equals(p)”还是”p.equals(cp)”的结果都是true。然而这种方法,equals的规范还是被破坏了,现在的问题是这个新等价性不满足传递性。考虑下面的一段代码实例,定义了一个点和这个点上上两种不同颜色点:
ColoredPoint redP = new ColoredPoint(1, 2, Color.RED);
ColoredPoint blueP = new ColoredPoint(1, 2, Color.BLUE);
redP等价于p,p等价于blueP
System.out.println(redP.equals(p)); // prints true
System.out.println(p.equals(blueP)); // prints true
然而,对比redP和blueP的结果是false:
System.out.println(redP.equals(blueP)); // 打印 false
因此,equals的传递性就被违背了。
使equals的关系更一般化似乎会将我们带入到死胡同。我们应该采用更严格化的方法。一种更严格化的equals方法是认为不同类的对象是不同的。这个可以通过修改Point类和ColoredPoint类的equals方法来达到。你能增加额外的比较来检查是否运行态的这个Point类和那个Point类是同一个类,就像如下所示的代码一样:
// A technically valid, but unsatisfying, equals method
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override public boolean equals(Object other) {
boolean result = false;
if (other instanceof Point) {
Point that = (Point) other;
result = (this.getX() == that.getX() && this.getY() == that.getY()
&& this.getClass().equals(that.getClass()));
}
return result;
}
@Override public int hashCode() {
return (41 * (41 + getX()) + getY());
}
}
你现在可以将ColoredPoint类的equals实现用回刚才那个不满足对称性要的equals实现了。
public class ColoredPoint extends Point { // 不再违反对称性需求
private final Color color;
public ColoredPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
@Override public boolean equals(Object other) {
boolean result = false;
if (other instanceof ColoredPoint) {
ColoredPoint that = (ColoredPoint) other;
result = (this.color.equals(that.color) && super.equals(that));
}
return result;
}
}
这里,Point类的实例只有当和另外一个对象是同样类,并且有同样的坐标时候,他们才被认为是相等的,即意味着 .getClass()返回的是同样的值。这个新定义的等价关系满足了对称性和传递性因为对于比较对象是不同的类时结果总是false。所以着色点 (colored point)永远不会等于点(point)。通常这看起来非常合理,但是这里也存在着另外一种争论——这样的比较过于严格了。
考虑我们如下这种稍微的迂回的方式来定义我们的坐标点(1,2)
Point pAnon = new Point(1, 1) {
@Override public int getY() {
return 2;
}
};
pAnon等于p吗?答案是假,因为p和pAnon的java.lang.Class对象不同。p是Point,而pAnon是Point的一个匿名派生类。但是,非常清晰的是pAnon的确是在坐标1,2上的另外一个点。所以将他们认为是不同的点是没有理由的。
canEqual 方法
到此,我们看其来似乎是遇到阻碍了,存在着一种正常的方式不仅可以在不同类继承层次上定义等价性,并且保证其等价的规范性吗?事实上,的确存在这样的一种方法,但是这就要求除了重定义equals和hashCode外还要另外的定义一个方法。基本思路就是在重载equals(和hashCode)的同时,它应该也要要明确的声明这个类的对象永远不等价于其他的实现了不同等价方法的超类的对象。为了达到这个目标,我们对每一个重载了equals的类新增一个方法canEqual方法。这个方法的方法签名是:
public boolean canEqual(Object other)
如果other 对象是canEquals(重)定义那个类的实例时,那么这个方法应该返回真,否则返回false。这个方法由equals方法调用,并保证了两个对象是可以相互比较的。下面Point类的新的也是最终的实现:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override public boolean equals(Object other) {
boolean result = false;
if (other instanceof Point) {
Point that = (Point) other;
result =(that.canEqual(this) && this.getX() == that.getX() && this.getY() == that.getY());
}
return result;
}
@Override public int hashCode() {
return (41 * (41 + getX()) + getY());
}
public boolean canEqual(Object other) {
return (other instanceof Point);
}
}
这个版本的Point类的equals方法中包含了一个额外的需求,通过canEquals方法来决定另外一个对象是否是是满足可以比较的对象。在Point中的canEqual宣称了所有的Point类实例都能被比较。
下面是ColoredPoint相应的实现
public class ColoredPoint extends Point { // 不再违背对称性
private final Color color;
public ColoredPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
@Override public boolean equals(Object other) {
boolean result = false;
if (other instanceof ColoredPoint) {
ColoredPoint that = (ColoredPoint) other;
result = (that.canEqual(this) && this.color.equals(that.color) && super.equals(that));
}
return result;
}
@Override public int hashCode() {
return (41 * super.hashCode() + color.hashCode());
}
@Override public boolean canEqual(Object other) {
return (other instanceof ColoredPoint);
}
}
在上显示的新版本的Point类和ColoredPoint类定义保证了等价的规范。等价是对称和可传递的。比较一个Point和 ColoredPoint类总是返回false。因为点p和着色点cp,“p.equals(cp)返回的是假。并且,因为cp.canEqual(p) 总返回false。相反的比较,cp.equals(p)同样也返回false,由于p不是一个ColoredPoint,所以在 ColoredPoint的equals方法体内的第一个instanceof检查就失败了。
另外一个方面,不同的Point子类的实例却是可以比较的,同样没有重定义等价性方法的类也是可以比较的。对于这个新类的定义,p和pAnon的比较将总返回true。下面是一些例子:
Point p = new Point(1, 2);
ColoredPoint cp = new ColoredPoint(1, 2, Color.INDIGO);
Point pAnon = new Point(1, 1) {
@Override public int getY() {
return 2;
}
};
Set<Point> coll = new java.util.HashSet<Point>();
coll.add(p);
System.out.println(coll.contains(p)); // 打印 true
System.out.println(coll.contains(cp)); // 打印 false
System.out.println(coll.contains(pAnon)); // 打印 true
这些例子显示了如果父类在equals的实现定义并调用了canEquals,那么开发人员实现的子类就能决定这个子类是否可以和它父类的实例进行比较。例如ColoredPoint,因为它以”一个着色点永远不可以等于普通不带颜色的点重载了” canEqual,所以他们就不能比较。但是因为pAnon引用的匿名子类没有重载canEqual,因此它的实例就可以和Point的实例进行对比。
canEqual方法的一个潜在的争论是它是否违背了Liskov替换准则(LSP)。例如,通过比较运行态的类来实现的比较技术(译者注: canEqual的前一版本,使用.getClass()的那个版本),将导致不能定义出一个子类,这个子类的实例可以和其父类进行比较,因此就违背了 LSP。这是因为,LSP原则是这样的,在任何你能使用父类的地方你都可以使用子类去替换它。在之前例子中,虽然cp的x,y坐标匹配那些在集合中的点,然而”coll.contains(cp)”仍然返回false,这看起来似乎违背得了LSP准则,因为你不能这里能使用Point的地方使用一个 ColoredPointed。但是我们认为这种解释是错误的,因为LSP原则并没有要求子类和父类的行为一致,而仅要求其行为能一种方式满足父类的规范。
通过比较运行态的类来编写equals方法(译者注: canEqual的前一版本,使用.getClass()的那个版本)的问题并不是违背LSP准则的问题,但是它也没有为你指明一种创建派生类的实例能和父类实例进行对比的的方法。例如,我们使用这种运行态比较的技术在之前的”coll.contains(pAnon)”将会返回false,并且这并不是我们希望的。相反我们希望“coll.contains(cp)”返回false,因为通过在ColoredPoint中重载的equals,我基本上可以说,一个在坐标1,2上着色点和一个坐标1,2上的普通点并不是一回事。然而,在最后的例子中,我们能传递Point两种不同的子类实例到集合中 contains方法,并且我们能得到两个不同的答案,并且这两个答案都正确。
•使用字符串缓冲区而不是字符串连接,当进行连续字符串操作时要避免不必要地创建那些最终必须经过垃圾回收的对象。
•避免连续写入 Java 控制台以减少字符串操作、文本格式化以及输出的花费。
•必要时通过使用变量的原语类型来避免对象创建和操作的花费。
•经常高速缓存用过的对象以减少必须的垃圾回收数量,并且避免重复创建对象的需求。
•尽可能分组本地操作以减少 Java 本地接口(JNI)的调用。
•只有在必要时再使用同步方法,以此限制在 JVM 和操作系统中的多任务。
•除非必要避免调用垃圾回收器。如果您必须调用它,只有在空闲时间或一些非关键阶段再这样做。
•可能时使用整型而不是长整型,因为 32 位操作快于 64 位。
•可能时申明方法为 final。JVM 处理 final 的方法较好。
•当创建常量时为了减少变量需要初始化的次数,使用关键字 static final。
•避免不必要的“casts”和“instanceof”引用,因为在 Java 中销毁操作不是在编译时而是在运行时执行的。
•当数组可以满足要求时尽可能避免使用向量。
•从向量末端添加和删除项以得到较高性能。
•避免在循环中分配对象。
•使用缓冲区 I/O 并调优缓冲区大小。
•使用连接池和准备缓存声明进行数据库访问。
•使用连接池连接到数据库并重用连接而不是重复打开和关闭连接。
•最大化线程生存期并最小化线程创建和销毁循环。
•最小化共享资源的争用。
•最小化短生存期对象的创建。
•避免远程方法调用。
•使用回调以避免阻塞远程方法调用。
•避免创建只用来访问一个方法的对象。
•尽可能保持同步方法处于循环外。
•在数据库中以 Unicode 形式存储字符串和字符数据。
•记录 CLASSPATH 以便最常用的库先出现。
我们在写代码时,常常会提到两条原则:
1、方法要尽量短,大方法要分解成小方法;
2、不要重复发明轮子。
我们在强调这两个原则的时候,往往只关注的是代码简洁、易维护等方便我们人的因素,其实这样做还可以大大方便java编译器优化代码。
java编译器优化简介:
Java 应用程序的编译过程与静态编译语言(例如 C 或 C++)不同。静态编译器直接把源代码转换成可以直接在目标平台上执行的机器代码,不同的硬件平台要求不同的编译器。 Java 编译器把 Java 源代码转换成可移植的JVM 字节码。与静态编译器不同,javac 几乎不做什么优化,在静态编译语言中应当由编译器进行的优化工作,在 Java 中是在程序执行的时候,由运行时执行优化。
即时编译
对于证实概念的实现来说,解释是合适的,但是早期的 JVM 由于太慢。下一代 JVM 使用即时 (JIT) 编译器来提高执行速度。按照严格的定义,基于 JIT 的虚拟机在执行之前,把所有字节码转换成机器码,但是以惰性方式来做这项工作:JIT 只有在确定某个代码路径将要执行的时候,才编译这个代码路径(因此有了名称“ 即时 编译”)。这个技术使程序能启动得更快,因为在开始执行之前,不需要冗长的编译阶段。
JIT 技术看起来很有前途,但是它有一些不足。JIT 消除了解释的负担(以额外的启动成本为代价),但是由于若干原因,代码的优化等级仍然是一般般。为了避免 Java 应用程序严重的启动延迟,JIT 编译器必须非常迅速,这意味着它无法把大量时间花在优化上。所以,早期的 JIT 编译器在进行内联假设(inlining assumption)方面比较保守,因为它们不知道后面可能要装入哪个类。
虽然从技术上讲,基于 JIT 的虚拟机在执行字节码之前,要先编译字节码,但是 JIT 这个术语通常被用来表示任何把字节码转换成机器码的动态编译过程 —— 即使那些能够解释字节码的过程也算。
HotSpot 动态编译
HotSpot 执行过程组合了编译、性能分析以及动态编译。它没有把所有要执行的字节码转换成机器码,而是先以解释器的方式运行,只编译“热门”代码 —— 执行得最频繁的代码。当 HotSpot 执行时,会搜集性能分析数据,用来决定哪个代码段执行得足够频繁,值得编译。只编译执行最频繁的代码有几项性能优势:没有把时间浪费在编译那些不经常执行的代码上;这样,编译器就可以花更多时间来优化热门代码路径,因为它知道在这上面花的时间物有所值。而且,通过延迟编译,编译器可以访问性能分析数据,并用这些数据来改进优化决策,例如是否需要内联某个方法调用。为了让事情变得更复杂,HotSpot 提供了两个编译器:客户机编译器和服务器编译器。默认采用客户机编译器;在启动 JVM 时,您可以指定 -server 开关,选择服务器编译器。服务器编译器针对最大峰值操作速度进行了优化,适用于需要长期运行的服务器应用程序。客户机编译器的优化目标,是减少应用程序的启动时间和内存消耗,优化的复杂程度远远低于服务器编译器,因此需要的编译时间也更少。
HotSpot 服务器编译器能够执行各种样的类。它能够执行许多静态编译器中常见的标准优化,例如代码提升( hoisting)、公共的子表达式清除、循环展开(unrolling)、范围检测清除、死代码清除、数据流分析,还有各种在静态编译语言中不实用的优化技术,例如虚方法调用的聚合内联。
持续重新编译
HotSpot 技术另一个有趣的方面是:编译不是一个全有或者全无(all-or-nothing)的命题。在解释代码路径一定次数之后,会把它重新编译成机器码。但是 JVM 会继续进行性能分析,而且如果认为代码路径特别热门,或者未来的性能分析数据认为存在额外的优化可能,那么还有可能用更高一级的优化重新编译代码。JVM 在一个应用程序的执行过程中,可能会把相同的字节码重新编译许多次。为了深入了解编译器做了什么,可以 -XX:+PrintCompilation 标志调用 JVM,这个标志会使编译器(客户机或服务器)每次运行的时候打印一条短消息。
栈上(On-stack)替换
HotSpot 开始的版本编译的时候每次编译一个方法。如果某个方法的累计执行次数超过指定的循环迭代次数(在 HotSpot 的第一版中,是 10,000 次),那么这个方法就被当作热门方法,计算的方式是:为每个方法关联一个计数器,每次执行一个后向分支时,就会递增计数器一次。但是,在方法编译之后,方法调用并没有切换到编译的版本,需要退出并重新进入方法,后续调用才会使用编译的版本。结果就是,在某些情况下,可能永远不会用到编译的版本,例如对于计算密集型程序,在这类程序中所有的计算都是在方法的一次调用中完成的。重量级方法可能被编译,但是编译的代码永远用不到。
HotSpot 最近的版本采用了称为 栈上(on-stack)替换 (OSR) 的技术,支持在循环过程中间,从解释执行切换到编译的代码(或者从编译代码的一个版本切换到另一个版本)。
从java编译、执行优化的原理可以看出,编译器会将“热门代码块”、“热门方法”持续优化,以提高性能,再回顾我们常常强调的两个原则:
1、尽量写小方法。小方法意味着功能单一、重用性高,自然会被很多地方用到,容易变成“热门方法”。
2、不重复发明轮子,尽量用已存在的轮子。大家共用一个“轮子”,自然就是“热门”轮子,编译器会知道这个轮子要好好优化,让他赚的更快。
“云计算”基本概念的介绍
根据维基百科上的定义,云计算是一种动态易扩展,而且通常是通过互联 网提供的虚拟化的资源计算方式,用户不需要了解云内部的细节,也不必具有云内部的专业知识,或者直接控制基础设施。云计算包括基础设施即服务 (IaaS)、平台即服务(PaaS)和软件即 服务(SaaS)以及其它依赖于互联网满足客户计算需求的技术趋势。云计算服务通常提供通用的通过浏览器访问的在线商业应用,软件和数据存储在服务器上。 本文的介绍主要在“平台即服务”这个层次上。在这个层次上,Google 所提供的技术即 Google App Engine 。
Google 的“云计算”平台
Google 作为一个搜索引擎,在其发展过程中不断地追求更高的可伸缩型和性能,逐渐发展出一套以自由技术为基础的良好的解决方案,其中包括 Google File System、BigTable 以及 Map Reduce 等。基于这些技术,Google 发展出了称之为 App Engine 的平台,可以允许开发人员在它之上开发应用,同时也利用上了 Google 所提供的基础设施,在可扩展性等非功能性的需求上获得好处,从而可以更加集中精力地解决业务的需求。
Google App Engine 最早于 2008 年 4 月发布,最初支持的开发语言是 Python 。今年 4 月,Google App Engine 已经宣布了对 Java 语言的支持。下图是 Google App Engine for Java 支持的概览。
图 1. Google App Engine for Java 支持的概览

在 Web 应用开发上,Google App Engine 支持 JSP 和 Servlet 技术;在数据存储上,Google App Engine 支持 JDO 和 JPA 两套机制,开发人员在面向对象的层次考虑数据的持久化,不需要考虑关系数据库。此外,Google App Engine 还提供一系列常用的服务,例如邮件收发、图像处理和缓存等。
本文将通过一个简单的实例来展示如何在 Google App Engine for Java 平台上构建 Web 应用。
准备开发环境
Google App Engine 支持两种开发方式:基于集成开发工具 Eclipse 和不基于 Eclipse 。由于 Eclipse 是广大 Java 开发人员使用最多的工具,本文将介绍基于 Eclipse 安装 Google App Engine SDK 并开发部署应用到 Google App Engine 上。下面是简要的过程:
安装 JDK 1.5 或者 1.6
安装 Eclipse 3.3 或者 3.4
下载 3.3 以上版本的 Eclipse 中的一款即可。
安装 Google App Engine 的 SDK
运行 Eclipse 之后,在 Help > Software Updates 中获取 Google App Engine 的插件。
图 2. 安装 Google App Engine Eclipse 插件
构建一个 Hello World 应用
安装成功之后,就可以构建一个 Google App Engine 的应用了。选择 File>New>Web Application Project。
图 3. 新建 Web 项目
输入项目名称 Hello World 。
图 4. 输入项目名称

运行该应用。
图 5. 运行
应用运行起来之后,访问 http://localhost:8080 。
图 6. 运行结果

将应用部署到 Google App Engine 上
恭喜您,到这里,你已经构建出了第一个 Google App Engine 上的应用,您可以将它部署到 Google 的 App Engine 平台上了。您需要做的就是申请一个帐号。
构建一个电子书分享网站
到这里,读者对 Google App Engine for Java 已经有了直接的印象。下面将通过一个更复杂的例子,讲述利用 Google App Engine 中的编程时的核心概念。
用例介绍
一个电子书分享网站,最基础的需要满足的功能是允许用户上传文件和搜索文件。对于用 户的浏览需求,列出最近上传的文件也是一个基本的需求。用例图如下图所示。
图 7. 用例图

存储对象模型
存储模型是 一个应用开发的核心之一,在 Google 的 App Engine 的平台,开发人员看不到数据库的概念,不需要创建数据库。 Google 的 App Engine 提供了基于 JPA 和 JDO 的两种技术给开发人员。这里我们利用 JDO 技术来做数据的存取。下面是电子书分享网站的 JDO 的对象模型。
图 8. JDO 对象模型

示例演示:上传电子书的用例
下面针对用户上传一个文档这个用 例,讲述如何实现这个功能。
构建上传文件页面
在工程的 war 目录下,添加一个新的文件 upload.html,在该文件中包含如下的代码。
<form method="post" action="upload" enctype="multipart/form-data">
<table>
<tbody>
<tr><td><strong>*Please select a file</strong></td></tr>
<tr><td><input name="efile" type="file" id="fileNode"></td></tr>
<tr><td><strong>*Please set the file's title</strong></td></tr>
<tr><td><input name="filename" type="text" size=60></td></tr>
<tr><td><strong>File tags</strong></td></tr>
<tr><td><input name="tags" type="text" size=60>
<label>seperate by whitespace, like
<i><b>Google appengine</b></i>
</label></td>
</tr>
<tr><td><strong>File description</strong></td></tr>
<tr><td>
<textarea name="description" rows="5" cols="50"></textarea>
</td></tr>
<tr><td><strong>your name</strong></td></tr>
<tr><td><input name="username" type="text" size=60></td></tr>
<tr><td><strong>your email</strong></td></tr>
<tr><td><input name="useremail" type="text" size=60></td></tr>
</tbody>
</table>
<input type="submit" value="upload">
</form>
构建文件上传处理的 servlet
利用 apache 的 commons 的文件上传的库,该 servlet 将上传的内容解析成 File 对象。
图 9. Servlet 模型

持久化改文件
应为 File 类已经添加了 JDO 需要的注释,如下。
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class File {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private String username;
@Persistent
private String useremail;
@Persistent
private Date uploadDate;
@Persistent
private String filename; //could be a filename or a url...
@Persistent
private String description; //could be a filename or a url...
@Persistent
private String mimeType; //pdf,ppt, chm etc.
@Persistent
private com.google.appengine.api.datastore.Blob file;
@Persistent
private Set<Key> tags = new HashSet<Key>(); // associated tags
利用 JDO 的规范提供的 API,可以持久化该对象到 Google 的 App Engine 的存储设施上。
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
pm.makePersistent(file);
} finally {
pm.close();
}
注意,这里需要首先在在项目的 classpath 的 META-INF 目录下有 jdoconfig.xml,其内容如下。
<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
<persistence-manager-factory name="transactions-optional">
<property name="javax.jdo.PersistenceManagerFactoryClass"
value="org.datanucleus.store.appengine.jdo.
DatastoreJDOPersistenceManagerFactory"/>
<property name="javax.jdo.option.ConnectionURL" value="appengine"/>
<property name="javax.jdo.option.NontransactionalRead" value="true"/>
<property name="javax.jdo.option.NontransactionalWrite" value="true"/>
<property name="javax.jdo.option.RetainValues" value="true"/>
<property name="datanucleus.appengine.autoCreateDatastoreTxns"
value="true"/>
</persistence-manager-factory>
</jdoconfig>
小结
本文介绍了云计算领域的一个重要厂商 Google 的 Appengine for Java 的开发平台。通过一个实际的例子讲述了如何使用该平台开发 Web 应用。
话说,我们在讨论系统中两个实体用继承还是组合的时候,引发了阿花和图腾的讨论。
问题:有三个类,A,B,C。他们间的关系是B继承A,C是由A组成的(注:C只有A一个属性,B没有任何的属性,完全是继承自A)。
争持不下的是,到底B和C对象,那个更占内存?
技术问题,要靠实践来证明。
为此我写了程序来验证,对象的大小,最简单的就是把他们序列化,比较序列化存成文件值的大小就可以验证对象的大小了。
事实证明:C对象和B对象间大小并没有绝对的关系,当只初始化C和B时,它们的大小是C<B,因为C只是初始化一个类,B要初始自身的时候,还要初始化它的基类。当初始化C和B,且要给C中的A赋值时,则C>B。
以下有几个我测试的小数据:
public class A implements Serializable{}//因为考虑到是通过序列化来比较两个对象的大小,所以相关的类要实现序列化接口
public class B extends A implements Serializable{}
public class C implements Serializable{private A a;public void setA(A a){
this.a=a;} }
public class D implements Serializable{}
当C中的A没有被初始化时,a与d的大小相同,b>c, 他们间的大小关系依次b>c>a=d;
当C中的A被初始化的时候,a与d的大小相同,b<c, 他们间的大小关系依次
c>b>a=d。
以上只是我执行代码后,获得数据,如果有不同的意见请不吝赐教。
这一段时间,Eclipse总是死掉,几乎是稍微操作快一点就会死掉,几分钟一次,搞得人郁闷至极。浪费了不少时间,在网上搜了下,看到很多朋友也出现类似的情况,在网上求救,但是网上的办法都只是说通过修改eclipse.ini文件来加大Eclipse的内存。
自己试了下,发现不管用。今天,又死掉了,不过爆出一个经常出现的错误,大意是:permgen space导致内存溢出。实在忍无可忍,上网一搜,发现网络真是个好东西,尤其是对我们这种自学者而言,无疑是授业解惑的良师益友。
这里总结一下自己的经验和网友的经验,希望能够对受此问题折磨的朋友有所参考价值。
解决办法如下:
在eclipse.ini配置文件中加上以下两行
-XX:PermSize=128M
-XX:MaxPermSize=128M
加上上2行后,我的eclipse.ini文件如下所示:
-showsplash
org.eclipse.platform
–launcher.XXMaxPermSize
512m
-vmargs
-Xms256m
-Xmx512m
-XX:PermSize=128M
-XX:MaxPermSize=128M
这里的内存大小根据自己的物理内存情况来决定吧。
从网上的资料看PermSize大一点肯定更好,而且最好是设置PermSize和MaxPermSize一样大。理由如下:
PermSize 和MaxPermSize如果设置为相同还可以在一定程度上提高性能,因为,PermSize在不断的变化中会需要转移其中的数据。如果固定了以后,则可以减少每次扩大PermSize带来的性能损失。
1、PermGen space简介
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域OutOfMemoryError: PermGen space从表面上看就是内存益出,解决方法也一定是加大内存。
说说为什么会内存益出:
(1)这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和和存放Instance的Heap区域不同。
(2)GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。这种错误常见在web服务器对JSP进行pre compile的时候。
如果你的WEB APP下都用了大量的第三方jar,其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。
解决方法: 手动设置MaxPermSize大小
修改TOMCAT_HOME/bin/catalina.sh,在echo “Using CATALINA_BASE: $CATALINA_BASE”上面加入以下行:
JAVA_OPTS=”-server -XX:PermSize=64M -XX:MaxPermSize=128m
建议:将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以减少jar 文档重复占用内存。
2、转载:OutOfMemory:PermGen Space异常的处理和分析
Java程序员没有遇到过OutOfMemory简直就是不可能的事情!
可见在Java的世界中,太多的不确定因素导致Java运行程序直接崩溃,直接抛出OutOfMemory异常,而一旦遇到了这个问题,调查起来就非常的困难。在JDK 5.0以前,OutOfMemory只有这么一句话: java.lang.OutOfMemory Exception…基本上无从下手,无从分析。从JDK 5.0以后对OutOfMemory增加了许多的详细说明,为这个异常的分析提供了很大的便利。
这次遇到的问题就是会抛出OutOfMemory:PermGen Space的异常,这个异常非常有意思,根据【此文章】的描述,这是一个Sun JVM的bug,从2003年开始,一只到现在都没有解决。而且提出来的解决方案是使用JRockit。Bug产生的原因已经找到,就是因为JVM在分配PermGen Space的时候出现了PermGen Space不足的情况,默认情况下 PermGen的大小为64M,在不换用JRockit的情况下,可以在启动JVM的时候添加一个参数: -XX: MaxPermSize= 128m| 256m| 512m。
那么究竟什么是PermGen呢?
PermGen 原来是指Permanent Generation,本身是在Java的垃圾收集机制(GC)中产生的一个概念。Java的垃圾收集机制最早只是遍历所有的对象,如果发现某个对象没有被引用,则回收,这是在早期的Java 1.0和Java 1.1的时候的GC规则。慢慢的,这样一种“愚蠢的”GC算法成为了JVM性能的瓶颈,在拥有大量数据的Java应用程序中,GC的算法被高度强化,于是各种各样高效的JVM GC算法被发展了起来。从J2SE也就是Java 1.2开始,JVM引入了多种GC算法,其中一种用的非常多的就是Generational Collection,中文也叫做“分代收集法”。
分代收集法摈弃了对所有对象的遍历,而是采用一些经验属性去避免额外的工作(While naive garbage collection examines every live object in the heap, generational collection exploits several empirically observed properties of most applications to avoid extra work)。其中导入了一个非常关键的概念:infant mortality (幼儿死亡率),这表示越是新生成的变量或者对象,越容易被收集。下面一张图表示了对象的生命周期,横轴表示的是测试到对象的生命周期,纵轴表示在一个指定的生命周期上被回收的对象数量。

可以看到,在使用了分代收集法以后,年轻一代的对象被收集的比例最高。并且在内存中的对象会按照不同的“年龄”来划分,当一个年龄段的对象满了以后,在这个年龄段上就会发生垃圾收集,从最年轻的一代开始,一直到“永生代”,在内存中,所有的对象可以划分为很多代,最后的一代“永生代”就是“Permanent Generation”,这里就是直接引出“Permanent Generation”概念的地方。具体可以参考下图:

根据前面所说的情况,在分代垃圾收集的情况下会产生Permanent Generation的概念,而这个分代垃圾收集法是并行收集和并发收集的基础,所以Permanent Generation会一直存在,那么这个Permanent Generation究竟是做什么用的呢?这里保存了JVM中所有对象的类信息,包括类的元数据,还有方法描述等等,所以这一代内存垃圾收集算法是不一样的,在Java大程序的情况下,尤其是J2EE 或者说Java EE的大型应用程序上,Permanent Generation的大小会直接限定能载入类的数量和大小。
【解决办法】就是设定JVM启动的时候参数,可以如下设置:
java -XX: PermSize=64m -XX: MaxPermSize=128m
另外PermSize 和MaxPermSize如果设置为相同还可以在一定程度上提高性能,因为,PermSize在不断的变化中会需要转移其中的数据。如果固定了以后,则可以减少每次扩大PermSize带来的性能损失。
更多的请参考 【Java官方站点】
另外,还可以在Java启动的时候添加下面的参数来看GC的运行情况:
Java -verbosegc
Analytics Plugin created by Web Hosting