HashMap 和 TreeMap
最近可能要出去面试,所以复习了一下java 集合类HashMap和TreeMap。
HasMap中的key是无序排列的,更适合用于向map中添加、删除和定位元素。TreeMap中key是可以按照自然顺序和自定义顺序排列,这样就可以按顺序遍历,所以TreeMap更适合按顺序遍历。
在创建TreeMap时给构造函数传递一个实现Comparator接口类的实例或者key对象实现了Comparable接口就可以实现集合内的元素自定义排序(注意是按照key排序的)。
HashMap事例:
TreeMap事例:
public static void myHasMap(){
Map<String,String> hashMap = new HashMap<String, String>();
for(int i=0;i<10;i++)
hashMap.put(i+"", i+"v");
for(String key:hashMap.keySet())
System.out.println("key:"+key+" value:"+hashMap.get(key));
}
/*
输出结果中可以看出HashMap中key的排列时无序的。
key:3 value:3v
key:2 value:2v
key:1 value:1v
key:0 value:0v
key:7 value:7v
key:6 value:6v
key:5 value:5v
key:4 value:4v
key:9 value:9v
key:8 value:8v
*/
TreeMap事例:
public static void myHasMap(){
//降序排序在创建TreeMap时传递Collections.reverseOrder()即可按照降序排列
//Map<Integer,String> treeMap = new TreeMap<Integer, String>(Collections.reverseOrder());
Map<Integer,String> treeMap = new TreeMap<Integer, String>();
for(int i=0;i<10;i++)
treeMap.put(i, i+"v");
for(Integer key:treeMap.keySet())
System.out.println("key:"+key+" value:"+treeMap.get(key));
}
/*
输出结果中可以看出TreeMap中key的排列是降序排列的。
key:0 value:0v
key:1 value:1v
key:2 value:2v
key:3 value:3v
key:4 value:4v
key:5 value:5v
key:6 value:6v
key:7 value:7v
key:8 value:8v
key:9 value:9v
*/
TreeMap事例使用Integer作为key,Integer类实现了Comparable接口,集合中的key会按照升序排列。如果使用一个没有实现Comparable的类做为key,那么在运行时会抛出一个
Exception in thread "main" java.lang.ClassCastException: fly.zxy.CollectionJava.DoubleLink cannot be cast to java.lang.Comparable的异常。也就说添加到集合中的key必须实现Comparable接口。
HashMap的key对象为什么要同时实现hasCode()和equals()方法?
先说一下Map中的key为什么需要覆盖Object中的hashCode()和equals()方法。
我们先来看一段代码:
package fly.zxy.CollectionJava;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class HashMapTest {
public static void main(String [] args){
hashMapExample();
}
public static void hashMapExample(){
Map<Element, Figureout> map = new HashMap<Element, Figureout>();
for(int i=0;i<5;i++)
map.put(new Element(i), new Figureout());
Element el = new Element(3);
if(map.containsKey(el))
System.out.println("key:new Element(3) => value:"+map.get(el));
else
System.out.println("not fount new Element(3)");
}
}
class Element{
int number;
public Element(int number){
this.number = number;
}
/*
@Override
public int hashCode() {
return number;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof Element) && (number == ((Element)obj).number);
}
*/
}
class Figureout{
Random r=new Random();
boolean b = r.nextDouble()<0.5;
public String toString(){
if(b)
return "OK!";
else
return "Impossible!";
}
}
上面代码中将Element对象作为key,Figureout作为value值。打印结果是not fount new Element(3)而非not fount new Element(3)对应的value值。这与我们预期的结果不符合,没有正确工作。
是因为最为key的Element类没有覆盖Object中的hasCode()和equals()方法,Object中的这两个方法是根据内存地址计算出hashCode的值和用内存地址做的比较。当i=3是创建了一个new Element(3),
查询时创建了一个Element el = new Element(3)对象,这两个对象的hashCode和作比较是肯定不相等。
所有我们要覆盖hashCode()和equals()方法。java中的很多类也覆盖了这两个方法(比如String,Integer)。取消Element中的注释,结果会输出 key:new Element(3) => value:OK!或者是key:new Element(3) => value:Impossible!,这取决于随机的浮点数。
我们再来回答为什么要同时实现hashCode()和equals()方法?
我们查看HashMap的源码可以知道,主要是看get()方法。要查找的key和现有Map中的key作比较时,只有key的hashCode相等并且值相等(见红色字),才会返回key映射的value。这也就不能理解为什么要同时实现这两个方法了。HashMap中的set()和containsKey()方法也是这样的。
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (<span style="color:#ff6666;">e.hash == hash && ((k = e.key) == key || key.equals(k))</span>)
return e.value;
}
return null;
}