HashMap(一)

hashMap(一)

HashMap的数据结构

数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端
数组
数组存储区间是连续的,占用内存严重,故空间复杂很大,但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难.
链表
链表存储区间离散,占用内存比较宽松,故空间复杂度很低,但时间复杂度很大,达(N).链表的特点是:寻址困难,插入和删除容易.
哈希表
寻址容易,插入删除也容易的数据结构,也不会占用太多的内存空间.
1.哈希表是由数组和链表组成的,一个数组中,每个元素存储的是一个链表的头结点,一般情况下是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到的,比如数组长度的len为16,key为12,28,12%16=12,28%16=12,所以key都存储到数组下标为12的位置.
2.hashMap其实是一个线性的数组实现的,所以理解为其存储数据的容器就是一个线性数组,这可能让我们很不解,一个线性的数组怎么实现按键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面.
transient Entry[] table;
总结
1.在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一个hash值得链表都存储在一个链表里,但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低,而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间.
红黑树
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色,在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
1.节点是红色或黑色.
2.根是黑色.
3.所有叶子节点都是黑色(叶子是NIL节点).
4.每个红色节点必须有两个黑色的子节点(从每个叶子到根的路径上不能有两个连续的红色节点).
5.从任一节点到每个叶子节点的所有简单路径都包含相同数目的黑色节点.

HashMap中主要成员变量

  • transient int size:记录了Map中key-value对的个数
  • loadFactor:加载因子,用来衡量 HashMap满的程度。loadFactor的默认值为0.75.
  • int threshold:临界值,当实际key-value个数超过threshold时,HashMap会将容量扩容,threshold= 容量*加载因子
  • 容量:如果不指定,默认容量是16

为什么需要加载因子

加载因子(默认是0.75),因为如果填充比很大,说明利用的空间很多,如果一直不进行扩容的话,链表就会越来越长,这样查找的效率很低,因为链表的长度很大(当然最新版使用了红黑树改进了很多),扩容之后,将原来链表数组的每一个链表分成奇偶两个子链表分别挂在新的链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率.

HashMap的存取实现

既然是线性数组,为什么能随机存取?这里HashMap用了一个小算法,大致是这样实现的:

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

put
1.疑问:如果两个key通过hash % Entry[].length得到的index相同,会不会有覆盖的危险?
答:这里HashMap里面用到链式数据结构的一个概念.上面我们提到Entry里面有一个next属性,作用是指向下一个Entry,打个比方,第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A.一会后又进来一个键值对B,通过计算其index也等于0,HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们会发现index = 0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起,所以疑问不用担心,也就是说数组中存储的是最后插入的元素.
2.当然HashMap里面包含了一些优化方面的实现,比如:Entry[]的长度一定后,随着map里面数据越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因子,随着map的size越来越大,Entry[]会以一定的规则加长长度.
null key
null key总是存放在Entry[]数组的第一个元素中.
解决hash冲突的办法
1.开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
2.再哈希法
3.链地址法
4.建立一个公共溢出区
**注**java中HashMap的解决方法就是采用的链地址法

HashMap和HashTable的区别

HashMap和HashTable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别,主要的区别有:线程安全性,同步,以及速度.
1.HashMap几乎可以等价于HashTable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value)而HashTable则不行).
2.HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确同步的话,多个线程是不能共享HashMap的,Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好.
3.另一个区别就是HashMap的迭代器是fail-fast迭代器,而HashTable的enumerator迭代器不是fail-fast的,所以当有其他线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常,但这并不是一个一定发生的行为,要看JVM.这条同样也是Enumeration和Iterator的区别.
4.由于HashTable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢,如果你不需要同步,只需要单线程,那么使用HashMap性能要好过HashTable.
5.HashMap不能保证随着时间的推移Map中的元素次序是不变的.

猜你喜欢

转载自blog.csdn.net/kbh528202/article/details/80502199