以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 Java/Eclipse 』  (http://bbs.xml.org.cn/list.asp?boardid=41)
----  Hashmap与Hashtable的选择使用  (http://bbs.xml.org.cn/dispbbs.asp?boardid=41&rootid=&id=68759)


--  作者:菜籽
--  发布时间:10/27/2008 8:51:00 PM

--  Hashmap与Hashtable的选择使用
1.问题
  Hashtable和Hashmap是我们在开发过程中经常用来映射key到value的容器,在这两者之间选择使用的时候,我们经常被有经验者建议用Hashmap,但我们可能对其中的缘由不甚了解。本文通过对原代码的一些简单分析,来解释原理,从而在使用中能够更好地做出选择。

2.分析
  Hashtable和Hashmap实现的功能基本相同,但主要有3点区别。

  2.1
  第一个不同之处在于它们的继承关系有所不同。
  public class Hashtable extends Dictionary
  public class HashMap extends AbstractMap

  由上面的代码可以看出Hashtable是基于陈旧的Dictionary类的。在Java 1.2引入Map借口后,Hashtable也改进为可以实现 Map。HashMap是Map接口的一个实现,继承于较新的AbstractMap类。 Hashmap可以算作是Hashtable的升级版本,整体上Hashmap对Hashtable类优化了代码。比如说, 消除了hardcoding,增加了code reuse等等。当然这点不同,并不会对我们选择使用哪个产生影响。

  2.2
  第二个不同,在Hashmap中,null可以作为key,这样的key只有一个,可以有一个或多个key所对应的value为null。而在Hashtable中,null不可以作为key,也不可以作为value。否则会抛出java.lang.NullPointerException。
  Hashtable的put方法的源代码如下:
  public synchronized Object put(Object key, Object value) {
// Make sure the value is not null
if (value == null) {
  throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index] ; e != null ; e = e.next) {
  if ((e.hash == hash) && e.key.equals(key)) {
  Object old = e.value;
  e.value = value;
  return old;
  }
}
  }

  从这段代码可以看出,在调用Hashtable的put方法时,首先会对put的value是否为空进行判断,如果为空,则会抛出NullPointerException,处理终止。如果对Hashtable中put一个null的key,代码在执行到
  int hash = key.hashCode(); 的时候,因为key对象为空,同样会抛出NullPointerException,处理终止。
  所以在编码时,诸如
  hashtable.put("1", null);//value为null
  或者
  hashtable.put(null, "one");//key为null
  都是错误的。
  当然,平时在编码时,put的key或者value不会那么明显就是个直接的字符串或者其他对象类型。它们可能是从DB或DTO中取得的或者通过一些计算或转化得到的。这个时候要注意put的key或value是否有可能为null,如果有null的可能性,应该先进行判断并处理。以避免异常的产生。

  看看下面这段错误代码。TABLEWORK是个hashtable
  WorkData wd = null;
  wd = rs.getString("HINBAN_NO");// DBから品番を取得する
  TABLEWORK.put(tableName, wd); // テーブルにセットする
  wd对象是从数据库取得的,有可能会为空。所以在向Hashtable中put的时候,要对wd对象是否为空进行判断并对wd对象为空的情况进行处理。

  修改后的代码如下。
  WorkData wd = null;
  wd = rs.getString("HINBAN_NO");// DBから品番を取得する
  if (wd == null) {
// 存在しなければ、新規作成
wd = ComUtils.crtHinaban();
  }
  // テーブルにセットする
  TABLEWORK.put(tableName, wd);

  接下来看看Hashmap的put方法是如何对null处理的。
  public Object put(Object key, Object value) {
    Object k = maskNull(key);
    int hash = hash(k);
    int i = indexFor(hash, table.length);

    for (Entry e = table[i]; e != null; e = e.next) {
        if (e.hash == hash && eq(k, e.key)) {
            Object oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, k, value, i);
    return null;
  }

  在put方法的开始,没有像Hashtable那样对value为空进行判断并抛出异常。另外Object k = maskNull(key); 这句代码中的maskNull方法也对put的key进行如下处理。

  static final Object NULL_KEY = new Object();

  /**
  * Returns internal representation for key. Use NULL_KEY if key is null.
  */
  static Object maskNull(Object key) {
     return (key == null ? NULL_KEY : key);
  }
  由此可以看出,在Hashmap put的时候,maskNull()方法会对put的值是否为空进行判断,如果为空,会产生一个新的对象(Object     NULL_KEY = new Object();)即NULL_KEY。

  另外还要注意一个问题,因为Hashmap可以存入null。所以当get()方法返回null值时,既可以表示 Hashmap中没有该key,也可以表示该key所对应的value为null。因此,在Hashmap中不能由get()方法来判断Hashmap中是否存在某个key, 而应该用containsKey()方法来判断。

  看下面这段代码
public static void main(String[] args) {

  Map map = new HashMap();
  map.put("1", null);
  System.out.println(map.get("1"));
  System.out.println(map.get("2"));
  System.out.println(map.containsKey("1"));
  System.out.println(map.containsKey("2"));
   
}

  输出结果
  null
  null
  true
  false

  从上面这段试验代码的输出结果可以看出,如果通过get方法是无法判断key值1,2是否存在的,因为它们的返回的值都是null。正确的做法应该使用containsKey()方法来判断,从输出结果看,前者为true,后者为false。

  2.3
  第三个不同是Hashtable的方法是同步的,而Hashmap方法不是。Hashtable是synchronized,你可以不用采取任何特殊的行为就可以在一个多线程的 应用程序中用一个Hashtable,而Hashmap的读写是unsynchronized, 在多线程的环境中要注意使用。这两者的不同是通过在读写方法上加synchronized关键字来实现的.有兴趣的话,大家可以看下Hashtable的源代码,在需要线程安全的方法前都加上了synchronized关键字。下面举了最常用的几个方法的定义。
  hashtable
  public synchronized Object get(Object key)
  public synchronized Object put(Object key, Object value)
  public synchronized Object remove(Object key)
  public synchronized void clear()

  从Hashmap的源代码可以看出没有这个关键字
  hashMap
  public V put(K key, V value)
  public V get(Object key)
  有人可能会问, 既然能synchronized,能线程安全好啊。为什么不要呢,这里其实还是一个效率的问题。对于线程安全的方法,系统要进行加锁,减锁操作。性能会有很大的影响。由于很多程序是在单线程或者说是线程安全的情况下工作的, 所以用synchronized就显得有些多余了。
  当既要同步又要可以让null作为键或者值的时候,一个简便的方法就是利用Collections类的静态的 synchronizedMap()方法,
  Map synMap = Collections.synchronizedMap(map);
  它创建一个线程安全的Map对象,并把它作为一个封装的对象来返回。  


3. 总结
  通过上述的内容,我们了解了Hashmap和Hashtable的几个主要的不同点。
  Hashmap可以使用null作为key和value,而Hashtable不行。Hashtable是同步的,Hashmap是异步的。但是,因为在需要时,Hashmap可以利用Collections类的静态的 synchronizedMap()方法来实现同步,其次Hashmap的功 能比Hashtable的功能更多,而且它不是基于一个陈旧的类的,所以才有人认为,在各种情况下,Hashmap都优先于Hashtable。


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
4,550.781ms