hashmap, treemap, hashtable, hashcode 的使用和探讨

来源:互联网 发布:马伯庸 三国 知乎 编辑:程序博客网 时间:2024/06/05 10:25

1. 基本使用

a. 在HashMap中通过get()来获取value,通过put()来插入value,ContainsKey()则用来检验对象是否已经存在。可以看出,和ArrayList的操作相比,HashMap除了通过key索引其内容之外,别的方面差异并不大;

b. 可以使用values()方法返回一个Collection物件,如果您需要一次选代Map中所有的物件;

c. HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序;

d. HashMap使用Hash Table,因而它有自己的排序方式,如果您想要在选代所有的物件时,依照插入的顺序来排序,则可以使用LinkedHashMap,它是HashMap 的子类。

        System.out.println("Sequence...");        Map hashmap = new HashMap();        hashmap.put("a", "aa");        hashmap.put("b", "bb");        hashmap.put("c", "cc");        hashmap.put("d", "dd");        Iterator iterator_hashmap = hashmap.keySet().iterator();        while (iterator_hashmap.hasNext()) {            Object key = iterator_hashmap.next();            System.out.println("HashMap:" + hashmap.get(key));        }        Map linkedhashmap = new LinkedHashMap();        linkedhashmap.put("a", "aa");        linkedhashmap.put("b", "bb");        linkedhashmap.put("c", "cc");        linkedhashmap.put("d", "dd");        Collection collection = linkedhashmap.values();        Iterator iterator_linkedhashmap = collection.iterator();        while (iterator_linkedhashmap.hasNext()) {            System.out.println("LinkedHashMap:" + iterator_linkedhashmap.next());        }        Hashtable hashtable = new Hashtable();        hashtable.put("a", "aa");        hashtable.put("b", "bb");        hashtable.put("c", "cc");        hashtable.put("d", "dd");        Iterator iterator_hashtable = hashtable.keySet().iterator();        while (iterator_hashtable.hasNext()) {            Object key = iterator_hashtable.next();            System.out.println("HashTable:" + hashtable.get(key));        }        Map treemap = new TreeMap();        treemap.put("a", "aa");        treemap.put("b", "bb");        treemap.put("c", "cc");        treemap.put("d", "dd");        Iterator iterator_treemap = treemap.keySet().iterator();        while (iterator_treemap.hasNext()) {            Object key = iterator_treemap.next();            System.out.println("TreeMap:" + treemap.get(key));        }

 

2. keySet 和 entrySet 的比较

对于keySet其实是遍历了2次,一次是转为iterator,一次就从hashmap中取出key所对于的value。而entryset只是遍历了第一次,它把key和value都放到了entry中,所以就快了。 HashMap使用很多,比如导入信息时就要用到,因大部分导入的信息要去判断是否有重复的信息,这样就可以利用containsKey来进行处理了,而不用在插入的时候去进行处理。

        System.out.println("efficiency of keySet and entrySet...");        HashMap k_hashmap = new HashMap();        for (int i = 0; i < 1000; i++) {            k_hashmap.put("" + i, "hello");        }        long k_bs = Calendar.getInstance().getTimeInMillis();        Iterator iterator = k_hashmap.keySet().iterator();        while (iterator.hasNext()) {            System.out.println(k_hashmap.get(iterator.next()));        }        System.out.println(Calendar.getInstance().getTimeInMillis() - k_bs);        HashMap e_hashmap = new java.util.HashMap();        for (int i = 0; i < 1000; i++) {            e_hashmap.put("" + i, "hello");        }        long e_bs = Calendar.getInstance().getTimeInMillis();        java.util.Iterator it = e_hashmap.entrySet().iterator();        while (it.hasNext()) {            java.util.Map.Entry entry = (java.util.Map.Entry) it.next();            System.out.println(entry.getValue());        }        System.out.println(Calendar.getInstance().getTimeInMillis() - e_bs);


3. HashCode在其中的探讨

下面的程序无论运行多少次,得到的结果都是"Not found"。也就是说索引KeyElement(5)并不在HashMap中.

KeyElement的HashCode方法继承自Object,而Object中的HashCode方法返回的HashCode对应于当前的地址,也就是说对于不同的对象,即使它们的内容完全相同,用HashCode()返回的值也会不同。这样实际上违背了我们的意图。因为我们在使用HashMap时,希望利用相同内容的对象索引得到相同的目标对象,这就需要HashCode()在此时能够返回相同的值。在下面的例子中,我们期望new KeyElement(i) (i=5)与 KeyElement test=new KeyElement(5)是相同的,而实际上这是两个不同的对象,尽管它们的内容相同,但它们在内存中的地址不同。因此很自然的,下面的程序得不到我们设想的结果。 

public class KeyElement {    int number;    public KeyElement(int n) {        number = n;    }}        HashMap hm = new HashMap();        for(int i=0; i<10; i++)        hm.put(new KeyElement(i), "aa"+1);        KeyElement testKey = new KeyElement(5);        if (hm.containsKey(testKey)) {            System.out.println(hm.get(testKey));        } else {            System.out.println("Not found");        }

 

如果我们把KeyElement类重新修改一下,就可以得到我们要的结果了。在这里KeyElement覆盖了Object中的hashCode()和equals()方法。覆盖hashCode()使其以number的值作为hashcode返回,这样对于相同内容的对象来说它们的hashcode也就相同了。而覆盖equals()是为了在HashMap判断两个key是否相等时使结果有意义

public class KeyElement {    int number;    public KeyElement(int n) {        number = n;    }    public int hashCode() {        return number;    }    public boolean equals(Object o) {        return (o instanceof KeyElement) && (number == ((KeyElement) o).number);    }}

 

延伸:

重写HashCode()的原则:

  1. 不必对每个不同的对象都产生一个唯一的hashcode,只要你的HashCode方法使get()能够得到put()放进去的内容就可以了。即"不为一原则"。
  2. 生成hashcode的算法尽量使hashcode的值分散一些,不要很多hashcode都集中在一个范围内,这样有利于提高HashMap的性能。即"分散原则"。

掌握了这两条原则,你就能够用好HashMap编写自己的程序了。不知道大家注意没有,java.lang.Object中提供的三个方法:clone(),equals()和hashCode()虽然很典型,但在很多情况下都不能够适用,它们只是简单的由对象的地址得出结果。这就需要我们在自己的程序中重写它们,其实java类库中也重写了千千万万个这样的方法。利用面向对象的多态性——覆盖,Java的设计者很优雅的构建了Java的结构,也更加体现了Java是一门纯OOP语言的特性。

 

4. HashMap与HashTable的区别

HashTable的应用非常广泛,HashMap是新框架中用来代替HashTable的类,也就是说建议使用HashMap,不要使用HashTable。可能你觉得HashTable很好用,为什么不用呢?这里简单分析他们的区别。 
a.HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。

b.HashTable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以)。

c.HashTable有一个contains(Object value),功能和containsValue(Object value)功能一样。

d.HashTable使用Enumeration,HashMap使用Iterator。

以上只是表面的不同,它们的实现也有很大的不同。

e.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

f.哈希值的使用不同,HashTable直接使用对象的hashCode,代码是这样的:
int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap重新计算hash值,而且用与代替求模:
int hash = hash(k);int i = indexFor(hash, table.length);static int hash(Object x) {  int h = x.hashCode();  h += ~(h << 9);  h ^= (h >>> 14);  h += (h << 4);  h ^= (h >>> 10);  return h;}static int indexFor(int h, int length) {  return h & (length-1);}
以上只是一些比较突出的区别,当然他们的实现上还是有很多不同的,比如
HashMap对null的操作