一、HashMap和TreeMap
在数组中,通过数组下标来对其内容索引;在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value。HashMap实现Map接口,提供所有映射操作的实现,并且允许null的键和值。HashMap中插入和查询key/value的开销是一个固定常量。HashMap不保证映射后的其内容的顺序在一定的时间内不会变化,也就是说,HashMap中元素的排列顺序是无序的(TreeMap是有序的)如例1:
例1:
import
java.util.
*
;
class HashMaps
{
public static void main(String[] args)
{
HashMap map = new HashMap();
map.put( " 1 " , " 111 " );
map.put( " 4 " , " 444 " );
map.put( " 2 " , " 222 " );
map.put( " 5 " , " 555 " );
Iterator it = map.keySet().iterator();
while (it.hasNext())
{
Object mapKey = it.next();
System.out.println( " map.get(key) is : " + map.get(mapKey));
}
TreeMap tree = new TreeMap();
tree.put( " 1 " , " 111 " );
tree.put( " 4 " , " 444 " );
tree.put( " 2 " , " 222 " );
tree.put( " 5 " , " 555 " );
Iterator it_2 = tree.keySet().iterator();
while (it_2.hasNext())
{
Object treeKey = it_2.next();
System.out.println( " tree.get(key) is : " + tree.get(treeKey));
}
}
}
class HashMaps
{
public static void main(String[] args)
{
HashMap map = new HashMap();
map.put( " 1 " , " 111 " );
map.put( " 4 " , " 444 " );
map.put( " 2 " , " 222 " );
map.put( " 5 " , " 555 " );
Iterator it = map.keySet().iterator();
while (it.hasNext())
{
Object mapKey = it.next();
System.out.println( " map.get(key) is : " + map.get(mapKey));
}
TreeMap tree = new TreeMap();
tree.put( " 1 " , " 111 " );
tree.put( " 4 " , " 444 " );
tree.put( " 2 " , " 222 " );
tree.put( " 5 " , " 555 " );
Iterator it_2 = tree.keySet().iterator();
while (it_2.hasNext())
{
Object treeKey = it_2.next();
System.out.println( " tree.get(key) is : " + tree.get(treeKey));
}
}
}
map.get(key) is : 222
map.get(key) is : 111
map.get(key) is : 555
map.get(key) is : 444
tree.get(key) is : 111
tree.get(key) is : 222
tree.get(key) is : 444
tree.get(key) is : 555
因为HashMap通过hashCode对进行查找,所以HashMap中元素的排列顺序是不固定的,而TreeMap在查看key/value的时候,元素会被排序,其次序由Comparable或Comparator决定,因此查询得到的结果是有序地。如果你需要得到一个有序的结果你就应该使用TreeMap。
二、HashMap的使用
HashMap通过get()/put()方法来查询和插入,还有一个很有用的方法ContainsKey()来check对象是否存在于HashMap中。如例2:
例2:
import
java.util.
*
;
public class HashMaps
{
public static void main(String[] args)
{
HashMap aMap = new HashMap();
Random ran = new Random();
for ( int i = 0 ; i < 100 ; i ++ )
{
Integer iVal = new Integer(ran.nextInt( 10 ));
if (aMap.containsKey(iVal))
{
((CCount)aMap.get(iVal)).count ++ ;
}
else
{
aMap.put(iVal, new CCount());
}
}
System.out.println(aMap);
}
}
class CCount
{
int count = 1 ;
public String toString()
{
return Integer.toString(count);
}
}
public class HashMaps
{
public static void main(String[] args)
{
HashMap aMap = new HashMap();
Random ran = new Random();
for ( int i = 0 ; i < 100 ; i ++ )
{
Integer iVal = new Integer(ran.nextInt( 10 ));
if (aMap.containsKey(iVal))
{
((CCount)aMap.get(iVal)).count ++ ;
}
else
{
aMap.put(iVal, new CCount());
}
}
System.out.println(aMap);
}
}
class CCount
{
int count = 1 ;
public String toString()
{
return Integer.toString(count);
}
}
HashMap是基于HashCode的,在所有对象的超类Object中有一个HashCode()方法。当使用标准库中的类Integer作为HashMap的key时,程序能够正常运行,但是如果使用自定义的类作为HashMap的key时,可能会出现错误。这是因为HashMap通过key查找value时,实际上是计算key对象地址的散列码来确定value的。默认情况下,我们使用超类Object的方法HashCode()来生成散列码。这样,假如我们用同样的初始化参数构造同样内容的对象,由于他们的地址不同,生成的散列码也不同。如下例所示:
例3:
import
java.util.
*
;
public class HashMaps
{
public static void main(String[] args)
{
HashMap aMap = new HashMap();
for ( int i = 0 ; i < 10 ; i ++ )
aMap.put( new Element(i), new Figureout());
System.out.println( " aMap: get result: " );
Element test = new Element( 5 );
if (aMap.containsKey(test))
System.out.println((Figureout)aMap.get(test));
else
System.out.println( " Not found " );
}
}
class Element
{
int num;
public Element( int n)
{
num = n;
}
}
class Figureout
{
Random r = new Random();
boolean possible = r.nextDouble() > 0.5 ;
public String toString()
{
if (possible)
return " OK " ;
else
return " Impossible " ;
}
}
public class HashMaps
{
public static void main(String[] args)
{
HashMap aMap = new HashMap();
for ( int i = 0 ; i < 10 ; i ++ )
aMap.put( new Element(i), new Figureout());
System.out.println( " aMap: get result: " );
Element test = new Element( 5 );
if (aMap.containsKey(test))
System.out.println((Figureout)aMap.get(test));
else
System.out.println( " Not found " );
}
}
class Element
{
int num;
public Element( int n)
{
num = n;
}
}
class Figureout
{
Random r = new Random();
boolean possible = r.nextDouble() > 0.5 ;
public String toString()
{
if (possible)
return " OK " ;
else
return " Impossible " ;
}
}
例3中,先是在for循环中通过new Element(i)生成了一个HashMap,之后,我们希望查找一个test元素,这个test元素是通过new Element(5)生成的。test元素和HashMap中的元素是不同的对象,因此,它们的地址也不同,使用默认的Object超类提供的HashCode方法无法查找。所以,例3中程序执行结果始终是“Not found”。
因此,对于用户自定义的类作key的情况,我们需要覆盖HashCode方法。同时,还需要覆盖equals方法来判断当前的key是否与HashMap中的元素的key相同。修改后的Element类如下:
class
Element
{
int num;
public Element( int n)
{
num = n;
}
public int hashCode()
{
return num;
}
public boolean equals(Object o)
{
return (o instanceof Element) && (num == ((Element)o).num);
}
}
{
int num;
public Element( int n)
{
num = n;
}
public int hashCode()
{
return num;
}
public boolean equals(Object o)
{
return (o instanceof Element) && (num == ((Element)o).num);
}
}
这里Element覆盖了Object中的hashCode()和equals()方法。覆盖hashCode()使其以number的值作为 hashCode返回,这样对于相同内容的对象来说它们的hashCode也就相同了。
三、小结
1,如果要用自己的类作为HashMap的key,必须同时覆盖hashCode和equals方法。
2,重写hashCode方法的关键:
(1)对同一个对象调用hashCode()都应该生成同样的值。
(2)hashCode()方法不要依赖于对象中易变的数据,当数据发生变化时,hashCode()就会生成一个不同的散列码,即产生了一个不同的label。
(3)hashCode()不应依赖于具有唯一性的对象信息,例如对象地址。
(4)散列码应该更关心速度,而不是唯一性,因为散列码不必是唯一的。
(5)好的hashCode()应该产生分步均匀的散列码。
3,正确的equals()方法满足五个条件:
(1)自反性。对于任意的x,x.equals(x)一定返回true。
(2)对称性。对于任意的x和y,如果y.equals(x)返回true,则x.equals(y)也返回true。
(3)传递性。对于任意的x、y、z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true。
(4)一致性。对于任意的x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一直是true,要么一直是false。
(5)对任何不是null的x,x.equals(null)一定返回false。