获取元素
有了前面的分析,获取元素的逻辑就非常清晰。首先,调用者传递key,从key的hashCode方法获得值后,调用hash函数做一些低位置换,保证hash值的均匀分布,之后和size-1按位与后得到数组的位置。然后取出对应位置的链表,遍历该链表,查找hash值相等,并且key的引用或者值相等的对象,然后返回。代码见下面:
- 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 (e.hash == hash && ((k = e.key) == key || key.equals(k)))
- return e.value;
- }
- return null;
- }
算法时间复杂度平均是O(1),如果hash code很糟糕,让其退化成链表,则是O(N).即便是O(1),也要注意,实际上计算hash用了好几步,绝对比直接从数组中获取某个元素的O(1)时间要长的多。
内存消耗
有一个很好的工具,可以帮助我们检查Java对象内存的消耗。从这里下载jar包:http://sizeof.sourceforge.net/
解压后将SizeOf.jar复制到某个目录,比如我的/home/chenshu,在项目中加入这个jar包,并且设置JVM参数:-javaagent:/home/chenshu/SizeOf.jar。
这个类库提供了一些静态函数,利用java.lang.Instrument的Instrumentation.getObjectSize(),能够计算Java对象真正在虚拟机里面占用的内存大小。下面的代码创建了一个只保存一个对象的HashMap,并计算内存占用。
- public static void main(String[] args) {
- // TODO code application logic here
- HashMap<String,String> map = new HashMap<String,String>();
- String put = map.put("a", "b");
- String size = SizeOf.humanReadable(SizeOf.deepSizeOf(map));
- System.out.println(size);
- }
结果是304字节,64bitJVM。真的很浪费内存,比我估计的要大多了!可见HashMap不是用来存放少量数据的。而且考虑到计算hash那么的复杂,如果只是喜欢Map这种Key,Value形式的接口,但并不保存较大数据量,应该考虑别的Map了。 Java其实提供了很多种Map,滥用HashMap的结果是只能开发“企业级“的应用,并且被我这种老程序员笑为富二代。:)
因此,在大数据量(个人认为超过1万),并且需要快速查找和插入的时候,HashMap是非常好的选择。但是如果数据量不大的情况下,以tree实现的Map也是一个不错的选择,毕竟节省很多内存。而且tree还可以实现set这样的数据结构,有时候比Map更符合我们的需求。
如果你现在拿起来就不假思索的使用HashMap(我知道这样的程序员太多了),请慎重。因为让你变得平凡的并不是项目进度紧或者工资低,而是对自己的要求不够高。