HashMap学习笔记

1、HashMap到底是个啥东西?

根据名字也能看出来,HashMap是基于哈希表的 Map 接口的实现。既然是基于哈希表的那么他的数据结构自然也就是由数组和链表组成的喽。数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。所以二者结合就是哈希表了。下面借用一张图片看看它的内部构造。

从图上可以看出左侧是一个长度为16的数组,右侧则是存储数据的链表。那数组的下标是按什么顺序存储的呢?其实存储规则是这样的,当拿到Map的key以后需要调用hashcode()方法计算他的hash值,然后用hash%len(数组长度)得到的结果就是它对应的顺序了。看看上面的12、28、108、140对16做模运算都等于12 所以他们都在12上面。那我们的key value去哪里了呢?HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

以上的讲解对hashMap有个大概了解了,下面来看看他存取数据的方法。

2、存取数据方法

//存储时:
int hash = key.hashCode();// 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;

//取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

以上是简化版的存取方法,便于理解。存取都要计算key的hash值,然后找到他的位置,然后再去链表查值。

上面图片也看到了,多个值可能会产生同样的index,能正确的存储进去吗?那又怎么找到key对应的value呢?

刚才说了存储键值对的是一个linkedlist啊,里面有一个Entry,Entry里面有一个next属性啊,所以当出现hashcode相同的时候他们会通过next连接在一起。这个问题叫做hash的碰撞,解决方法有很多,例如这里用到的链地址法,还有再哈希法、开放定址法等。查找的时候就会调用key.equels()方法去遍历LinkedList中正确的节点。

那怎么才能减少碰撞,提高效率呢?

使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生。使用String,Interger这样的wrapper类作为键是非常好的选择,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。

当然你可能使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。

Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的数组,来重新调整map的大小,并通过映射将原来的对象放入新的数组中。这个过程叫作rehashing,因为它调用hash方法找到新的位置。

重新调整HashMap大小存在什么问题吗?

当多线程的情况下,可能产生条件竞争。也就是在多线程情况下,AB两个线程同时发现需要调整大小了,他们就会同时去调整大小,在调整大小的过程中,存储在LinkedList中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在LinkedList的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。这个时候新的问题出现了,HashMap是非线程安全的,所以这个时候需要用到ConcurrentHashMap了。

ConcurrentHashMap和HashMap有什么不同?

我们知道HashTable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。下面一张图可以看得很清楚。

3、总结

HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。 HashMap在每个LinkedList节点中储存键值对对象。

当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的LinkedList中。键对象的equals()方法用来找到键值对。

以上是对HashMap及其延伸知识的一个学习笔记,以备日后查阅。

参考:http://www.cnblogs.com/xwdreamer/archive/2012/05/14/2499339.html

http://www.admin10000.com/document/3322.html

转载于:https://my.oschina.net/tomcater/blog/707076

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值