2009年12月31日星期四

[note]JQuery live方法给未来的元素绑定事件

live(type,fn) 给未来的元素绑定事件

它是jQuery 1.3中新增的方法,有些时候真的是非常的好用,比如做Ajax聊天的时候。下面是JQuery参考手册中的一个示例:

<p>Click me!</p>

$("p").live("click", function(){
    $(this).after("<p>Another paragraph!</p>");
});

2009年12月30日星期三

[note]Javascript里的this

this关键字指代它所在的域的对象。

看如下代码:

var test = function() {
   alert(this);
   this.f = function() {
       alert(this);
   };
};
test();
var t =new test();
t.f();

执行结果为:

Object Window
Object Object
Object Object

 执行test();时,test是作为一个函数,而此函数所在的域是Window,故显示的是Object Window。
执行var t =new test();时,test是作为一个初始化函数,生成了它的对象实例t,此时产生了一个新的域t,故显示Object Object。
执行t.f();时,f是一个在域t中执行的函数,而f不是一个域只是一个函数,故显示Object Object。

2009年12月28日星期一

[note]Java 访问权限public\protect\default\private

 

同一个类

同一个包

不同包的子类

不同包的非子类

Private

 

 

 

Default

 

 

Protected

 

Public

[原创][精品]CopyOnWirte模式解决“读多于写情况下的并发问题”

CopyOnWrite顾名思义是指:在写的时候先拷贝再赋值。

举例数组,一般的操作在add的时候直接在考虑在数组的末尾添加新的值。但是CopyOnWrite的模式则不同:它先拷贝所有的数组,然后修改拷贝数组,再将拷贝数组赋值到原数组的变量。

参考jdk中CopyOnWrite的实现,这里只举出一些典型方法。

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** The lock protecting all mutators */
    transient final ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private volatile transient Object[] array; //volatile 关键字使其每次被赋值之后都会被其他线程看到,而不会使用线程拷贝的副本。加上赋值操作本身是原子性的所以不会冲突

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }
   
    public E get(int index) {
        return (E)(getArray()[index]); //不需要同步
    }

    public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();  //锁定,如果已被锁定则等待
    try {
        Object[] elements = getArray();
        Object oldValue = elements[index];

        if (oldValue != element) { //写入的值与原值不一样
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len); //拷贝
        newElements[index] = element;
        setArray(newElements);//赋值
        } else {
        // Not quite a no-op; ensures volatile write semantics
        setArray(elements);
        }
        return (E)oldValue;
    } finally {
        lock.unlock();//解锁
    }
    }
 
CopyOnWirte避免了写入过程中影响读操作。使“读大大多于写的情况”下,不需要对读操作添加锁。

2009年12月25日星期五

[原创][精品]Java 双重检查锁定与单例模式(无序写入、线程安全)的总结(包含static变量的解释)

单例模式:
  • 模式一:
public class Singleton {
       private static Singleton instance = new Singleton();

       public static Singleton getInstance() {
              return instance;
       }
}
这个模式线程安全,但是也有自己的缺点。当为如下情况时,初始化就会产生问题!

public class SingletonAa {
    private static SingletonAa singletonAa = new SingletonAa();

    public static String name = "sam";

    public static String password;
    static {
        password = "tiger";
    }
    
    private SingletonAa(){
        if(name.equals("sam")) System.out.println("name is ok");
        if(password.equals("tiger")) System.out.println(" password is ok");
    }

    public static SingletonAa getInstance() {
        return singletonAa;
    }
}
因为初始化依赖于static变量,而这些初始化函数在变量被初始化之前被调用了...所以会跑出NullPointException。
  • 模式二:
public class Singleton {
       private static Singleton instance = null;

       public static synchronized Singleton getInstance() {
              if (instance == null) {
                      instance = new Singletion();
              }
              return instance;
       }
}
 
模式一会在程序使用时就初始化instance,而模式二是在第一次调用getInstance时使用。如果初始化较为繁琐,那么显然模式二会更好一点。但是该模式还是有缺点,就是对多线程使用了同步锁。
  • 模式三
public class Singleton {
       private static Singleton instance = null;

       public static synchronized Singleton getInstance() {
              if (instance == null) {
                      synchronized(Singleton.class) {
                              if (instance == null) {
                                        Singleton temp = new Singletion();    //防止无序写入导致的多线程问题
                                        instance = temp;
                              }
                      }
              }
              return instance;
       }
}

以上的模式是线程安全的,又是延迟初始化,而且同步的范围缩到最小。其中创建temp的原因是为了避免Java的无序写入导致的初始化未完成就返回结果。具体参看:http://www.ibm.com/developerworks/cn/java/j-dcl.html

  • 模式四:
是由Bob lee创建的。即线程安全又延迟初始化。非常完美的版本!

public class Singleton {
    static class SingletonsHolder {
        static Singletons instance = new Singletons();
    }
   
    public static Singletons getInstances() {
        return SingletonsHolder.instance;
    }
}
起初我看到这个代码的时候还认为它不是线程安全的,因为考虑到并发情况下,instance可能被初始化多次,所以得到的并不是单例而是多个。
但事实上经过测试得到的是同一单例,测试代码如下:

public class Singletons {
    public static boolean b = true;
    private Singletons() {
        System.out.println("In " + b);
        if (b) {
            System.out.println("Sleeping " + b);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Singletons " + b);
    }
   
    static class SingletonsHolder {
        static Singletons instance = new Singletons();
    }
   
    public static Singletons getInstances() {
        System.out.println("getInstances");
        return SingletonsHolder.instance;
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Start");
        new Thread() {
            public void run() {
                System.out.println(Singletons.getInstances());
            }
        }.start();
        Thread.sleep(1000);
        Singletons.b = false;
        new Thread() {
            public void run() {
                System.out.println("Two Start");
                System.out.println(Singletons.getInstances());
                System.out.println("Two End");
            }
        }.start();
        System.out.println("End ");
    }
}
上述代码完成的过程是:
  • 开始;
  • 线程1获取单例;
  • b为true,故线程1 sleep 5秒;
  • 主线程sleep 1秒;
  • b被设置为false;
  • 线程2获取单例;
得到的输出是:
Start
getInstance
In true
Sleeping true
等待了1秒
End
Two Start
getInstance
等待了4秒
Singletons false
org.my.test.singleons.Singletons@1a758cb
org.my.test.singleons.Singletons@1a758cb
Two End
可见最终得到的单例是同一个。分析我们知道测试的执行过程为:
  • 开始;
  • 线程1获取单例,进入Singletons初始化函数
  • b为true,故线程1 sleep 5秒;
  • 主线程sleep 1秒;
  • b被设置为false;
  • 线程2获取单例,进入Singletons的getInstances()方法,发现SingletonsHolder还未被初始化完成,并且发现它正在被初始化,故等待;
  • 线程1成功初始化instance完成,并输出结果;
  • 线程2等待结束获取了初始化完成的instance,并输出结果;
可见static变量被多线程访问会等待变量初始化的完成,然后才能够访问,从而确保只有一个实例的存在。

2009年12月23日星期三

[note]Java中if与? :的比较

本次测试比较了if与?:表达式的效率。

        Long start = System.currentTimeMillis();
        String s = "1";
        for (int i=1;i<1000000000;i++) {
              if (s.equals("1"))
                  s = "2";       //11710,11672,11678
              s = (s.equals("1"))? "2":s; //11147,11137,11269
              s = (s.equals("1"))? "2":"1";//9539,9528,9647
        }
        Long end = System.currentTimeMillis();
        System.out.println(end - start);

 注释是三次测试所花时间,由此得到:
  • 赋值常量比赋值变量要快;
  • if所花的时间比赋值所花的时间要多;

[note]synchronized关键字

1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线 程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的 synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

2009年12月22日星期二

[转帖][精品]String in Java

转自:http://hxraid.javaeye.com/blog/522167

作者:Java标准类库有几千个类,唯独String不太一样。为什么这么说?就因为每次上网冲杯Java时,都能看到关于String无休无止的争论。还是觉得有必要让这个讨厌又很可爱的String美眉,赤裸裸的站在我们这些Java色狼面前了。嘿嘿....

众所周知,String是由字符组成的串,在程序中使用频率很高。Java中的String是一个类,而并非基本数据类型。 不过她却不是普通的类哦!!!

 

【镜头1】 String对象的创建
   1、关于类对象的创建,很普通的一种方式就是利用构造器,String类也不例外:
           String s=new String("Hello world");
   问题:参数"Hello world"是什么东西,也是字符串对象吗?莫非用字符串对象创建一个字符串对象???

   2、当然,String类对象还有一种大家都很喜欢的创建方式:
           String s="Hello world";
   问题:有点怪呀,怎么与基本数据类型的赋值操作(int i=1)很像呀???

在开始解释这些问题之前,我们先引入一些必要的知识:
(1) Java class文件结构
       我们都知道,Java程序要运行,首先需要编译器将源代码文件编译成字节码文件(也就是.class文件)。然后在由JVM解释执行。
       class文件是8位字节的二进制流 。这些二进制流的涵义由一些紧凑的有意义的项 组成。比如class字节流中最开始的4个字节组成的项叫做魔数 (magic),其意义在于分辨class文件(值为0xCAFEBABE)与非class文件。class字节流大致结构如下图左侧。

                               

      其中,在class文件中有一个非常重要的项——常量池 。这个常量池专门放置源代码中的常量信息(并且不同的常量存放在不同标志的常量表中)。如上图右侧是HelloWorld代码中的常量表 (HelloWorld代码如下),其中有四个不同类型的常量表(四个不同的常量池入口)。关于常量池的具体细节,请参照《深入Java虚拟机》第二版第 6章。

Java代码
  1. public class HelloWorld{  
  2.     void hello(){  
  3.         System.out.println("Hello world");  
  4.     }  
  5. }  

      显然,HelloWorld代码中的"Hello world"被编译之后,可以清楚的看到存放在了class二进制流的常量池项中(上图右侧红框区域)。并且我们还发现常量池中专门有为String类型设置的常量表 。也就是说,在编译阶段,就已经将代码中的这种("****")形式作为了字符串常量存放在常量池中了 ,这一点和下面代码中出现的整形常量(142),浮点型常量(12.1)等的处理是没有区别的。

                   String s="Hello world";
                    int intData=142;
                    double dblData=12.1;

(2) Java虚拟机运行class文件
      当Java虚拟机需要运行一个class文件时,它首先会用类装载器装载进class文件。当然也就需要在内存中存放许多东西。比如class的二进制字 节码。还有需要存储class文件中得到的其他信息,比如程序创建的对象,传递给方法的参数,返回值,局部变量等等。怎么多麻烦的数据当然需要管 理,JVM会把这些东西都组织到几个“运行时数据区 ”中。这些数据区中就有我们动不动就谈到的"堆"呀,"栈"呀什么的?想要详细了解这部分东西可以看《深入Java虚拟机》第二版第5章。
   
      在这里我只谈谈“方法区 ”这个运行时数据区。在Java虚拟机中,关于被装载类型的信息会在一个逻辑上被称为"方法区"的内存中,当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入该文件。紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区中。
   
      方法区中的这些类型信息是很有用的,比如:这个类型的全限定名(net.single.HelloWorld);这个类型的直接超类的全限定名;这个类型 是类类型还是接口类型;这个类型的访问修饰符(public,final,static)等。还有两个大家都很熟悉的引用:指向类ClassLoader 的引用和指向Class类的引用。这是Java反射机制能够运行的关键所在。这里我们要提到的是一个非常重要的信息——该类型的常量池

    上面提到的,class文件结构中的常量池二进制流就被JVM存储在方法区中进行管理。当程序运行时需要使用到常量值的时候,直接在方法区常量池所在的内存中寻找就可以了。

(3) 操作码助忆符指令集
     将String s=new String("Hello world");编译成class文件后的指令(由eclipse打开class文件查看的):

Class字节码指令集代码
  1. 0  new java.lang.String [15]    
  2. 3  dup  
  3. 4  ldc <String "Hello word"> [17]   
  4. 6  invokespecial java.lang.String(java.lang.String) [19]  
  5. 9  astore_1 [s]  
  6. 10  retur  

    下面通俗的解释一下这些指令,详细见《深入Java虚拟机》第二版附表:按操作码助忆符排列的指令集。
    ★ new指令: 在内存的堆区域中为新字符串对象分配足够大的空间,并将对象的实例变量设为默认值。
    ★ ldc指令:在内存的方法区常量池中找到String类型字面值常量表 的入口,然后定位到的"Hello word"所在内存中的位置。
    ★ invokespecial指令:调用指定的类构造器(这里调用的是String(String)这一个构造器。将ldc指令所找到的"Hello word"的内容传入到new指令所开辟在堆中的字符串对象中。
    ★ astore_1:将new指令所开辟堆的内存位置存入局部变量s中

 

    将String s="Hello world";编译成class文件后的指令:

Class字节码指令集代码
  1. 0  ldc <String "Hello world"> [15]  
  2. 2  astore_1 [str]  
  3. 3  return  

    ★ ldc指令:在内存的方法区常量池中找到String类型字面值常量表 的入口,然后定位到的"Hello word"所在内存中的位置(如果常量池中没有"Hello word",则会在其中添加一个"Hello word")。
    ★ astore_1:将ldc指令定位到的常量池中的位置存入局部变量s中

镜头总结: String类型脱光了其实也很普通。真正让她神秘的原因就在于String类型字面值常量表 的存在。

相关问题解决 

   (问题1) 代码1                                                           代码2

              String sa=new String("Hello world");          String sc="Hello world";
              String sb=new String("Hello world");          String sd="Hello world";
              System.out.println(sa==sb);  // false          System.out.println(sc==sd);  // true
              变量sa,sb中存储的内容是JVM在堆中开辟的两个String对象的内存地址。==比较就是sa,sb变量存储的内容,也就是两个不同的内存地址,当然是false;
              变量sc,sd中存储的内容也是地址,但却都是方法区常量池中"Hello word"所在的地址,自然一样。

   (问题2) 代码1                                                          代码2
              String sa = "ab";                                        String sc="ab"+"cd";
              String sb = "cd";                                        String sd="abcd";
              String sab=sa+sb;                                     System.out.println(sc==sd); //true
              String s="abcd";
              System.out.println(sab==s); // false
              代码1中sa+sb被编译以后使用的是StringBuilder.append(String)方法。JVM会在堆中创建一个 StringBuilder类,将sa所指向常量池中的内容"ab"传入,然后调用append(sb所指向的常量池内容)完成字符串合并功能,最后将堆 中StringBuilder对象的地址赋给变量sab。而s存储的是常量池中"abcd"的地址。sab与s地址当然不一样了。
              代码2中"ab"+"cd"会直接在编译阶段就合并成常量"abcd",所以相同的字符串在常量池中的地址也相同了。

【镜头二】  String三姐妹(String,StringBuffer,StringBuilder),谁更性感?
        String不用多说,扒的差不多了。但他还有两个妹妹StringBuffer,StringBuilder长的也不错哦!我们首先对这三姐妹下个定义:
        String(大姐,出生于JDK1.0时代)          不可变字符序列
        StringBuffer(二姐,出生于JDK1.0时代)    线程安全的可变字符序列
        StringBuilder(小妹,出生于JDK1.5时代)   非线程安全的可变字符序列

讨论1、StringBuffer与String的可变性问题。
         我们先看看这两个类的简要源代码:

Java代码
  1. //String   
  2. public final class String  
  3. {  
  4.         private final char value[];  
  5.   
  6.          public String(String original) {  
  7.               // 把原字符串original切分成字符数组并赋给value[];  
  8.          }  
  9. }  
  10.   
  11. //StringBuffer   
  12. public final class StringBuffer extends AbstractStringBuilder  
  13. {  
  14.          char value[]; //继承了父类AbstractStringBuilder中的value[]  
  15.          public StringBuffer(String str) {  
  16.                  super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组  
  17.                  append(str); //将str切分成字符序列并加入到value[]中  
  18.         }  
  19. }  

      很显然,String和StringBuffer中的value[]都用于存储字符序列。但是,
      (1) String中的是常量(final)数组,只能被赋值一次。
      比如:new String("abc")使得value[]={'a','b','c'},之后这个String对象中的value[]再也不能改变了。这也正是大家常说的,String是不可变的原因 。   
      注意:这个对初学者来说有个误区,有人说String str1=new String("abc"); str1=new String("cba");不是改变了字符串str1吗?那么你有必要先搞懂对象引用和对象本身的区别。
      (2) StringBuffer中的value[]就是一个很普通的数组,而且可以通过append()方法将新字符串加入value[]末尾。这样也就改变了value[]的内容和大小了。
      比如:new StringBuffer("abc")使得value[]={'a','b','c','',''...}(注意构造的长度是str.length()+16)。如果再将这个对象append("abc"),那么这个对象中的value[]={'a','b','c','a','b','c',''....}。这也就是为什么大家说 StringBuffer是可变字符串 的涵义了。
      从这一点也可以看出,StringBuffer中的value[]完全可以作为字符串的缓冲区功能。
               String s1=new String("aaa");
               StringBuffer sb1=new StringBuffer(); //一个字符串缓冲区
               sb1.append(s1);//将字符串s1加进缓冲区
    注意,讨论String和StringBuffer可不可变。本质指对象内部的value[]字符数组可不可变,而不是对象引用可不可变。
讨论2、StringBuffer与StringBuilder的线程安全性问题
     StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。
     有多线程编程经验的程序员应该知道synchronized。这个关键字是为线程同步机制 设定的。我简要阐述一下synchronized的含义:
      每一个类对象都对应一把锁,当某个线程A调用类对象O中的synchronized方法M时,必须获得对象O的锁才能够执行M方法,否则线程A阻塞。一旦 线程A开始执行M方法,将独占对象O的锁。使得其它需要调用O对象的M方法的线程阻塞。只有线程A执行完毕,释放锁后。那些阻塞线程才有机会重新调用M方 法。这就是解决线程同步问题的锁机制。
     了解了synchronized的含义以后,大家可能都会有这个感觉。多线程编程中StringBuffered比StringBuilder要安全多了 ,事实确实如此。如果有多个线程需要对同一个字符串缓冲区进行操作的时候,StringBuffer应该是不二选择。
     注意:是不是String也不安全呢?事实上不存在这个问题,String是字符串常量,不可变。线程对于String对象只能读取,无法修改。试问:还有什么不安全的呢?
讨论3、String和StringBuffer的效率问题(这可是个热门话题呀!)

      首先说明一点:StringBuffer和StringBuilder可谓双胞胎,StringBuilder是1.5新引入的,其前身就是StringBuffer。在Core Java editior 7 中文版P611页中有这么一句话: StringBuffer比StringBuilder的效率稍低
      我们用下面的代码运行1W次字符串的连接操作,测试String,StringBuffer所运行的时间。

Java代码
  1. //测试代码  
  2. public class RunTime{  
  3.     public static void main(String[] args){  
  4.            //位置1  
  5.           long beginTime=System.currentTimeMillis();  
  6.           for(int i=0;i<10000;i++){  
  7.                 //位置2  
  8.           }  
  9.           long endTime=System.currentTimeMillis();  
  10.           System.out.println(endTime-beginTime);     
  11.     }  
  12. }  

    (1) String常量与String变量的"+"操作比较
         首先声明两个概念:String str="Heart"中"Heart"是String常量(存放在常量池中);而str则是String变量,是String对象的引用。
         ▲测试①代码:     (测试代码位置1)  String str="";
                                   (测试代码位置2)  str="Heart"+"Raid";
            [耗时:  0ms]
            
         ▲测试②代码        (测试代码位置1)  String s1="Heart";
                                                           String s2="Raid";
                                                           String str="";
                                  (测试代码位置2)  str=s1+s2;
            [耗时:  15—16ms]
        结论:String常量的“+连接” 稍优于  String变量的“+连接”。
        原因:测试①的"Heart"+"Raid"在编译阶段就已经连接起来,并形成了一个新的字符串常量"HeartRaid"。运行阶段只需要 将"HeartRaid"存入常量池。循环1W次也只不过将常量池中"HeartRaid"的地址取出1W次赋值给String对象的引用而已。
                测试②在编译阶段,编译器并不知道s1和s2代表什么,它会将这句话编译成几个指令,大体上是这样的。
                      首先:StringBuilder temp=new StringBuilder(s1),
                      然后:temp.append(s2)
                      最后:str=temp.toString();
                我们发现,虽然在中间的时候也用到了append()方法,但是在开始和结束的时候分别创建了StringBuilder和String对象。可想而知:调用1W次,是不是就创建了1W次这两中对象呢?不划算。

         不言而喻:String变量的"+"连接操作比String常量操作使用的更加广泛。
     (2)String对象的"累+"连接操作与StringBuffer对象的append()累和连接操作比较。
          ▲测试①代码:     (代码位置1)  String s1="Heart";
                                                       String s="";
                                    (代码位置2)  s=s+s1;
             [耗时:  4200—4500ms]
            
          ▲测试②代码        (代码位置1)  String s1="Heart";
                                                       StringBuffer sb=new StringBuffer();
                                    (代码位置2) sb.append(s1);
             [耗时:  0ms(当循环100000次的时候,耗时大概16—31ms)]
         结论:StringBuffer的append()累和连接    远好于    String对象的"累+"连接
         原因:测试①中s=s+s1我们知道编译器也会使用StringBuilder的append方法,但是首先编译器会将new StringBuilder存放s,然后在通过 StringBuilder.append(s1),最后在new一个新的String对象存放 StringBuilder的连接后的内容 。试想这个过程一次就需要创建一个新的String并赋值给引用s,10000次就需要创建了1W次新String对象。正式因为String中的value[]数组不可变,只能不停的创建新的String来存放变化中的字符串。效率可想而知了。
                  测试②中sb.append(s1);只需要将自己的value[]数组不停的扩大来存放s1即可。无需在堆中创建一大堆的对象。效率高就不足为奇了。
      
         不言而喻:大规模累加字符串的时候,StringBuffer就比她姐姐不知道强多少倍了。
镜头总结: (1) 在编译阶段就能够确定的字符串常量,完全没有必要创建String或StringBuffer对象。直接使用字符串常量的"+"连接操作效率最高。
                (2)否则,StringBuffer对象的append效率要高于String对象的"+"连接操作。
                (3) intern()方法很有用。
                     当调用String 对象的intern方法时,如果池已经包含一个等于此对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
                     也就是如果我们需要使用new关键字来创建很多内容重复的字符串的话,使用String.intern()方法可以提高效率。

2009年12月15日星期二

[转]linux 32位向64位的移植常见问题

1.      数据截断:

由于long类型变量的运算(赋值、比较、移位等)产生。long定义在x86上为32bits,而在ia64上为64bits.容易在与int型变量运算时出现异常。

处理方法:尽量避免不同类型变量间的运算,避免将长度较长的变量赋值到较短的变量中,统一变量长度可以解决这个问题。简单的对于32位转移到64位可以将所有long定义转换为int定义。

2.      指针存储:

x86平台下,习惯使用int来存储指针,并将指针直接参与到int型的运算中,而64位平台下指针大小为64bits,无法存储到int中。对指针的运算也可能会因为数据长度的不一致导致异常。

处理方法:修改用于存储传递指针的变量为intptr_t 类型定义,以保证平台兼容性

3.      不规范的结构:

未命名或为指定数量的成员可能会出现异常

处理方法:命名未命名的成员,声明类型符号,将long型定义转为int型。

4.      代码中的直接数

直接使用16进制数字进行赋值时(一般会隐含假设该变量为32位变量的前提) 可能出现异常。使用数字定义数据大小,进行移位运算时会出现异常。

处理方法:检查代码中的直接数字是否有表示32位有关的各种形式,如4,32, 0x7fffffff等,替换为宏定义。

 

编写代码时注意可移植化:

1.    32位与64位下使用同样的头文件

2.      使用严禁的格式定义,如:用off_t定义偏移量,用fpos_t定义文件位置, intptr_t定义指针

3.      使用<intypes.h>中定义的整数类型,不使用int,long,long long的传统定义方式。使用带有整形标示符参数的printf函数,不使用%d,%ld的格式化方式。

4.      使用固定宽度或宏定义的整数类型,数字,掩码

5.      对整形变量作边界检查。

6.      32位与64位进程共享内存操作时,使用64位作为操作字长。

[转]typeof、instanceof、constructor与对象类型

转自:http://www.cnblogs.com/aming/archive/2008/10/27/1320277.html

1、typeof 运算符把类型信息以字符串形式返回。typeof 返回六种可能的值:“数字类型”、“字符串类型”、“布尔型”、“对象类型”、“函数类型”和“未定义类型”。
  但对所有的对象和数组类型返回的都是"object",所以它只在区别对象和原始类型的时候才有用。要区一种对象类型和另一种对象类型,必须使用其他的方法。如:instanceof运算符和constructor属性。


2、instanceof运算符。如果 objectclass 或构造函数的实例,则 instanceof 运算符返回 true。如果 object 不是指定类或函数的实例,或者 objectnull,则返回 false


3、constructor属 性。对象的constructor属性引用了此其构造函数,常用于判断求知对象的类型。如给定一个求知的值通过typeof运算符来判断它是原始的值还是 对象。如果是对象,就可以使用constructor属性来判断其类型。但在客户端javascript对象中Object.toString()方法的 默认实现提供了另一种判断求知对象类型的方法。

2009年12月12日星期六

[精品][原创]Java ClassLoader总结

Classloader是Java语言用于载入.class文件。.class文件是包含了java字节码。
Classloader通过loadClass方法载入class文件,得到Class类。再用newInstance();方法获得类的实例。

委托模式:
Java1.2之后,Classloader添加了委托模式,当载入一个class文件时,
  • 首先查找是否已经载入了此类;
  • 如果没有则会先调用它的getParent()方法得父类的Classloader,然后让父类载入class文件;
  • 如果父类载入失败的话那么会自己本身载入class,如果再载入失败则抛出ClassNotFoundException。
这是为了安全考虑,如果我们建了一个包名都一样的String类,那么不使用委托模式会导致不安全。
另外委托模式对自定义的Classloader不起作用。除非自己实现。

使用ClassLoader方法:
  • 方法一:
MyClassLoader cl = new MyClassLoader();
cl.loadClass("test.classloader.A");方法直接使用MyClassLoader载入类。
  • 方法二:
Class.forName("test.classloader.A",true,new MyClassLoader());方法使用MyClassLoader载入类。
  • 方法三:
Class.forName("test.classloader.A");方法使用CallerClassLoader载入类。
new test.classloader.A();方法使用CallerClassLoader载入类。

Caller Classloader指的是当前所在的类装载时使用的Classloader,它可能是System Classloader,也可能是一个自定义的Classloader,这里,我们都称之为Caller Classloader。我们可以通过getClass().getClassLoader()来得到Caller Classloader。例如,存在A类,是被AClassLoader所加载,A.class.getClassLoader()为AClassLoader的实例,它就是A.class的Caller Classloader。

如果在A类中使用new关键字,或者Class.forName(String className)和Class.getResource(String resourceName)方法,那么这时也是使用Caller Classloader来装载类和资源。比如在A类中初始化B类:
/**
* A.java
*/
public void foo() {
B b = new B();
b.setName("b");
}

那么,B类由当前Classloader,也就是AClassloader装载。同样的,修改上述的foo方法,其实现改为:

Class clazz = Class.forName("foo.B");

最终获取到的clazz,也是由AClassLoader所装载。

  • 方法四:
使用Thread的ClassLoader来载入类。
Thread.currentThread().setContextClassLoader(new MyClassLoader());
Thread.currentThread().getContextClassLoader().loadClass("test.classloader.A");

系统默认的三种ClassLoader

(Bootstrap) 装载器:该装载器没有父装载器,它是 JVM 实现的一部分,从 sun.boot.class.path 装载运行时库的核心代码。

扩展 (Extension) 装载器:继承的父装载器为根装载器,不像根装载器可能与运行时的操作系统有关,这个类装载器是用纯 Java 代码实现的,它从 java.ext.dirs ( 扩展目录 ) 中装载代码。

系统 (System or Application) 装载器:装载器为扩展装载器,我们都知道在安装 JDK 的时候要设置环境变量 (CLASSPATH ) ,这个类装载器就是从 java.class.path(CLASSPATH 环境变量 ) 中装载代码的,它也是用纯 Java 代码实现的,同时还是用户自定义类装载器的缺省父装载器。


ClassLoader中重要方法:
  1. loadCass方法:loadClass(String name ,boolean resolve) 其中 name 参数指定了 JVM 需要的类的名称 , 该名称以类的全限定名表示,如 Java.lang.Object ; resolve 参数告诉方法是否需要解析类,在初始化类之前,应考虑类解析,并不是所有的类都需要解析,如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。这个方法是 ClassLoader 的入口点。
  2. defineClass方法:这个方法接受类文件的字节数组并把它转换成 Class 对象。字节数组可以是从本地文件系统或网络装入的数据。它把字节码分析成运行时数据结构、校验有效性等等。
  3. findSystemClass方法:findSystemClass 方法从本地文件系统装入 Java 字节码。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将字节数组转换成 Class 对象。当运行 Java 应用程序时 , 这是 JVM 正常装入类的缺省机制。
  4. resolveClass方法resolveClass(Class c):方法解析装入的类,如果该类已经被解析过那么将不做处理。当调用 loadClass 方法时 , 通过它的 resolve 参数决定是否要进行解析。
  5. findLoadedClass方法:当调用 loadClass 方法装入类时 , 调用 findLoadedClass 方法来查看 ClassLoader 是否已装入这个类 , 如果已装入 , 那么返回 Class 对象 , 否则返回 NULL 。如果强行装载已存在的类 , 将会抛出链接错误。

2009年12月7日星期一

[转]使用负边距创建自适应宽度的流体布局

文章来源:http://www.osmn00.com/?p=24

合理使用负边距技术,可以帮助我们创建很多有意思的布局,比如自适应浏览器宽度的流体布局。国外关于使用负边距创建这类布局的技术文档,我看到的最早是04年 Ryan Brill 发表在 A List Apart 上的 《Creating Liquid Layouts with Negative Margins》 (04年 - -!国内刚小部分人开始关注WEB标准化),本文亦可看做是对其的中文讲解版。

随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。目前国内门户很多都进行了改版,采用目前的主流———960px左右的宽度。

我认为,对于不太复杂的网站,采用百分比进行架构是个不错的主意。自适应布局的应用面还是蛮多的,比如论坛页面、博客页面等。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。

这里需要的用到的技术关键点就一则:负边距。

【简单的布局】

OK。我们现在开始。假设现在需要给自己的博客重新制作前台,界面的设计已经完成,就差代码架构。我们希望博客的界面可以做到:左侧的主要部分是博客文章 内容,这部分需要在不同分辨率浏览器下自适应宽度;而右侧是侧边栏,这部分我们想做成一个固定250px宽,预期效果图如下:



这是个典型的两栏布局,我们来做一个初步的架构
引用:
<div id=”header”>顶部区域</div>
<div id=”mainer”>
<h1>使用负边距创建自适应宽度的流体布局</h1>
<p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
</div>
<div id=”sideBar”>
<h2>最新文章</h2>
<ul>
<li>最新文章一</li>
<li>最新文章二</li>
<li>最新文章三</li>
</ul>
</div>
<div id=”footer”>底部区域</div>
查看测试页面一,这是在没有样式表的情况下页面的显示效果。下面我们给它加上基本的样式表
引用:
body,p,h1,h2,ul {
margin:0;padding:0;
}
#header {
background-color: #A8A754;
}
#footer {
background-color: #A8A754;
clear: both;
}
#mainer {
width: 100%;
margin-right: -250px;
float: left;
}
#sideBar {
float: right;
width: 250px;
}
定义mainer右边距为-250px,使得右边得以空出侧边栏的宽度,放置侧边栏。加上样式表页面请查看测试页面二。OK,现在我们看到左侧的内容区域已经为侧边栏空出了相应的空间,使得侧边栏放置在了它应该出现的位置。

【去除重叠部分】

我们这时会发现,左侧的文字内容部分却和侧边栏有重叠。这时候我们需要另外的一个DIV层,来将左侧文字部分设置一个足够大的右边距,使其不与侧边栏重叠。并将左侧内容部分与侧边栏部分设置不同背景色,以示区分。
引用:
<div id=”mainer”>
<div id=”main”>
<h1>使用负边距创建自适应宽度的流体布局</h1>
<p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
</div>
</div>
增加的CSS
引用:
#sideBar {
color: #FFF;
background-color: #36361A;
}
#main {
margin-right: 250px;
background-color: #616030;
}
查看测试页面三

【自适应高度】

这时候我们又发现了一个问题:如果左侧长度继续加长,那么,右侧侧边栏的下部会出现空白。我们希望可以实现视觉上的左右两栏自适应等高。

这里我们可以给外层的mainer DIV设置一个右对齐纵向重复的背静图片,图片的宽度我们设置为250px,即同侧边栏宽度相同。



CSS部分我们加上
引用:
#mainer {
background: url(bj1.jpg) repeat-y right bottom;
}
多数时候我们并不想设置全部宽度为浏览器的宽度,多数时候我们会设置小于屏幕宽度的百分比。这种情况下,我们可以在左侧内容及右侧侧边栏外再套一个DIV层,并在侧边栏下加上一个清除浮动的层,来达到我们的目的。这个时候我们的XHTML会是下面这个样子。
引用:
    <div id=”header”>顶部区域</div>

    <div id=”wrapper” class=”mid”>
    <div id=”mainer”>
    <div id=”main”>
    <h1>使用负边距创建自适应宽度的流体布局</h1>
    <p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
    <p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
    <p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
    <p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
    </div>
    </div>

    <div id=”sideBar”>
    <h2>最新文章</h2>
    <ul>
    <li>最新文章一</li>
    <li>最新文章二</li>
    <li>最新文章三</li>
    </ul>
    </div>
    <div class=”clear”></div>
    </div>

    <div id=”footer”>底部区域</div>
相应的CSS
引用:
#wrapper {width: 92%;}
.clearing {clear: both;}
.mid {margin:0 auto;}
查看测试页面四

Ryan Brill 在他的文章里说,外层的wrapper与里面的mainer两个DIV都应该设置背景 background,以便解决IE里的一个BUG。但我这里只设置了mainer DIV的背景,在IE6、IE7、FF里并未发现错误。可能他指的是IE5.x,这里忽略。

【进阶,三栏布局】

掌握了以上的方法,我们会发现制作一个三栏的布局也是很简单的!OK。下面我们把上面的例子变下,我们需要一个三栏的布局,2侧为固定宽度,中部为自适应宽度。这仅需要增加一点DIV。
引用:
    <div id=”header” class=”mid”>顶部区域</div>

    <div id=”wrapper” class=”mid”>
    <div id=”mainer”>
    <div id=”main”>

    <div id=”leftBar”>
    <h2>栏目标题</h2>
    <ul>
    <li>文章标题</li>
    <li>文章标题</li>
    <li>文章标题</li>
    </ul>
    </div>

    <div id=”inmain”>
    <h1>使用负边距创建自适应宽度的流体布局</h1>
    <p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
    <p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
    <p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
    <p>随着越来越大的浏览器的出现及普及,网站界面如何能满足不同分辨率浏览器使用者的浏览需求,逐渐成为前端开发工程师必须面对的问题。采 用百分比进行架构是个不错的主意。以往我们进行这类架构都是使用table表格。但,其实使用很小的技术就可以创建出符合WEB标准化的自适应布局。 </p>
    </div>

    </div>
    </div>

    <div id=”sideBar”>
    <h2>最新文章</h2>
    <ul>
    <li>最新文章一</li>
    <li>最新文章二</li>
    <li>最新文章三</li>
    </ul>
    </div>
    <div class=”clear”> </div>
    </div>

    <div id=”footer” class=”mid”>底部区域</div>
以及另外一个背景图片



CSS部分增加:
引用:
#main {
margin-right: 250px;
background: url(bj2.jpg) #616030 repeat-y left bottom;
}
#leftBar {
float: left;
width: 150px;
}
#inmain {
margin-left: 150px;
}
现在来看看我们的页面,测试页面五

2009年12月3日星期四

[note]隐藏滚动栏(scrollBar)二三事

问题一:如何隐藏滚动栏?

1、完全隐藏
在<boby>里加入scroll="no",可隐藏滚动条;

<boby scroll="no">


2、在不需要时隐藏
指当浏览器窗口宽度或高度大于页面的宽或高时,不显示滚动条;反之,则显示;

<boby scroll="auto">

3、样式表方法
在<boby>里加入style="overflow-x:hidden",可隐藏水平滚动条;加入style="overflow-y:hidden",可隐藏垂直滚动条。
这种方法在页面头部为:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">这种兼容模式下是不行的,其它的没试过,最后终于找到了正确的方法:
被包含页面里加入

<style>
html { overflow-x:hidden; }
</style>

有一段解释是这样说的:body{ overflow-x:hidden; }在标准 DTD 下是不可以的。

4、另一种方法

<style type="text/css">
body {
overflow-x:hidden; /*隐藏水平滚动条*/
overflow-y:hidden; /*隐藏水平滚动条*/
}
</style>

此方法在页面头部为:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">这种兼容模式下也是不行的。

<style type="text/css">
html {
overflow-x:hidden; /*隐藏水平滚动条*/
overflow-y:hidden; /*隐藏水平滚动条*/
}
</style>

问题二:如何使firefox始终显示滚动条?
firefox在页面高度小于一个屏幕的高度时,是不会显示滚动条的。但是在某些特殊情况下,我们需要这个滚动条。在网上找了些方法:

html {
    overflow:-moz-scrollbars-vertical;
}
以上方法使用firefox私有属性,但在firefox 3.5之下无法使用

body, html {
    min-height:101%;
}
以上方法一切正常,且支持多浏览器

[note]如何限制得到整数的大小

int indexFor(int h, int length)
{
return h & length;
}


只要length是2的次方,那么不论h多到,得到的返回值一定会比length小。

2009年12月2日星期三

[转]理解 Java 的 GC 与 幽灵引用

转自:http://www.javaeye.com/topic/401478

理解 Java 的 GC 与 幽灵引用
 
         Java 中一共有 4 种类型的引用 : StrongReference、 SoftReference、 WeakReference 以及 PhantomReference (传说中的幽灵引用 呵呵),
这 4 种类型的引用与 GC 有着密切的关系,  让我们逐一来看它们的定义和使用场景 :

        1. Strong Reference
       
        StrongReference 是 Java 的默认引用实现,  它会尽可能长时间的存活于 JVM 内, 当没有任何对象指向它时 GC 执行后将会被回收

Java代码
  1. @Test  
  2. public void strongReference() {  
  3.     Object referent = new Object();  
  4.       
  5.     /** 
  6.      * 通过赋值创建 StrongReference  
  7.      */  
  8.     Object strongReference = referent;  
  9.       
  10.     assertSame(referent, strongReference);  
  11.       
  12.     referent = null;  
  13.     System.gc();  
  14.       
  15.     /** 
  16.      * StrongReference 在 GC 后不会被回收 
  17.      */  
  18.     assertNotNull(strongReference);  
  19. }  





        2. WeakReference & WeakHashMap

WeakReference, 顾名思义,  是一个弱引用,  当所引用的对象在 JVM 内不再有强引用时, GC 后 weak reference 将会被自动回收

Java代码
  1. @Test  
  2. public void weakReference() {  
  3.     Object referent = new Object();  
  4.     WeakReference<Object> weakRerference = new WeakReference<Object>(referent);  
  5.   
  6.     assertSame(referent, weakRerference.get());  
  7.       
  8.     referent = null;  
  9.     System.gc();  
  10.       
  11.     /** 
  12.      * 一旦没有指向 referent 的强引用, weak reference 在 GC 后会被自动回收 
  13.      */  
  14.     assertNull(weakRerference.get());  
  15. }  




WeakHashMap 使用 WeakReference 作为 key, 一旦没有指向 key 的强引用, WeakHashMap 在 GC 后将自动删除相关的 entry

Java代码
  1. @Test  
  2. public void weakHashMap() throws InterruptedException {  
  3.     Map<Object, Object> weakHashMap = new WeakHashMap<Object, Object>();  
  4.     Object key = new Object();  
  5.     Object value = new Object();  
  6.     weakHashMap.put(key, value);  
  7.   
  8.     assertTrue(weakHashMap.containsValue(value));  
  9.       
  10.     key = null;  
  11.     System.gc();  
  12.       
  13.     /** 
  14.      * 等待无效 entries 进入 ReferenceQueue 以便下一次调用 getTable 时被清理 
  15.      */  
  16.     Thread.sleep(1000);  
  17.       
  18.     /** 
  19.      * 一旦没有指向 key 的强引用, WeakHashMap 在 GC 后将自动删除相关的 entry 
  20.      */  
  21.     assertFalse(weakHashMap.containsValue(value));  
  22. }  




        3. SoftReference

SoftReference 于 WeakReference 的特性基本一致, 最大的区别在于 SoftReference 会尽可能长的保留引用直到 JVM 内存不足时才会被回收(虚拟机保证), 这一特性使得 SoftReference 非常适合缓存应用

Java代码
  1. @Test  
  2. public void softReference() {  
  3.     Object referent = new Object();  
  4.     SoftReference<Object> softRerference = new SoftReference<Object>(referent);  
  5.   
  6.     assertNotNull(softRerference.get());  
  7.       
  8.     referent = null;  
  9.     System.gc();  
  10.       
  11.     /** 
  12.      *  soft references 只有在 jvm OutOfMemory 之前才会被回收, 所以它非常适合缓存应用 
  13.      */  
  14.     assertNotNull(softRerference.get());  
  15. }  




        4. PhantomReference

        作为本文主角, Phantom Reference(幽灵引用) 与 WeakReference 和 SoftReference 有很大的不同,  因为它的 get() 方法永远返回 null, 这也正是它名字的由来

Java代码
  1. @Test  
  2. public void phantomReferenceAlwaysNull() {  
  3.     Object referent = new Object();  
  4.     PhantomReference<Object> phantomReference = new PhantomReference<Object>(referent, new ReferenceQueue<Object>());  
  5.       
  6.     /** 
  7.      * phantom reference 的 get 方法永远返回 null  
  8.      */  
  9.     assertNull(phantomReference.get());  
  10. }  



         诸位可能要问, 一个永远返回 null 的 reference 要来何用,  请注意构造 PhantomReference 时的第二个参数 ReferenceQueue(事实上 WeakReference & SoftReference 也可以有这个参数),
PhantomReference 唯一的用处就是跟踪 referent  何时被 enqueue 到 ReferenceQueue 中.

     5. RererenceQueue

当一个 WeakReference 开始返回 null 时, 它所指向的对象已经准备被回收, 这时可以做一些合适的清理工作.   将一个 ReferenceQueue 传给一个 Reference 的构造函数, 当对象被回收时, 虚拟机会自动将这个对象插入到 ReferenceQueue 中, WeakHashMap 就是利用 ReferenceQueue 来清除 key 已经没有强引用的 entries.

Java代码
  1. @Test  
  2. public void referenceQueue() throws InterruptedException {  
  3.     Object referent = new Object();       
  4.     ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();  
  5.     WeakReference<Object> weakReference = new WeakReference<Object>(referent, referenceQueue);  
  6.       
  7.     assertFalse(weakReference.isEnqueued());  
  8.     Reference<? extends Object> polled = referenceQueue.poll();  
  9.     assertNull(polled);  
  10.       
  11.     referent = null;  
  12.     System.gc();  
  13.   
  14.     assertTrue(weakReference.isEnqueued());  
  15.     Reference<? extends Object> removed = referenceQueue.remove();  
  16.     assertNotNull(removed);  
  17. }  



6.  PhantomReference  vs WeakReference

PhantomReference  有两个好处, 其一, 它可以让我们准确地知道对象何时被从内存中删除, 这个特性可以被用于一些特殊的需求中(例如 Distributed GC,  XWork 和 google-guice 中也使用 PhantomReference 做了一些清理性工作).

其二, 它可以避免 finalization 带来的一些根本性问题, 上文提到 PhantomReference 的唯一作用就是跟踪 referent 何时被 enqueue 到 ReferenceQueue 中,  但是 WeakReference 也有对应的功能, 两者的区别到底在哪呢 ?
这就要说到 Object 的 finalize 方法, 此方法将在 gc 执行前被调用, 如果某个对象重载了 finalize 方法并故意在方法内创建本身的强引用,  这将导致这一轮的 GC 无法回收这个对象并有可能
引 起任意次 GC, 最后的结果就是明明 JVM 内有很多 Garbage 却 OutOfMemory, 使用 PhantomReference 就可以避免这个问题, 因为 PhantomReference 是在 finalize 方法执行后回收的,也就意味着此时已经不可能拿到原来的引用,  也就不会出现上述问题,  当然这是一个很极端的例子, 一般不会出现.

7. 对比

taken from http://mindprod.com/jgloss/phantom.html

Soft vs Weak vs Phantom References
Type Purpose Use When GCed Implementing Class
Strong Reference An ordinary reference. Keeps objects alive as long as they are referenced. normal reference. Any object not pointed to can be reclaimed. default
Soft Reference Keeps objects alive provided there’s enough memory. to keep objects alive even after clients have removed their references (memory-sensitive caches), in case clients start asking for them again by key. After a first gc pass, the JVM decides it still needs to reclaim more space. java.lang.ref.SoftReference
Weak Reference Keeps objects alive only while they’re in use (reachable) by clients. Containers that automatically delete objects no longer in use. After gc determines the object is only weakly reachable java.lang.ref.WeakReference 
java.util.WeakHashMap
Phantom Reference Lets you clean up after finalization but before the space is reclaimed (replaces or augments the use offinalize()) Special clean up processing After finalization. java.lang.ref.PhantomReference


8. 小结
       一般的应用程序不会涉及到 Reference 编程, 但是了解这些知识会对理解 GC 的工作原理以及性能调优有一定帮助,   在实现一些基础性设施比如缓存时也可能会用到, 希望本文能有所帮助.

        王政 于 2009,6,3