Map浅析
Map类型,持有 key-value,个人理解像个小型数据库。
Map是一种依照键值对的形式进行存储的数据结构。键值相当于list和数组中的下标。键值可以是任何类型的对象同时在图中不能存在重复的键值并且每个键值必须与一个相应的值对应存储。HashMap也用到了哈希码的算法,以便快速查找一个键,TreeMap则是对键按序存放,有一些扩展的方法,firstKey(),lastKey()等,可以从TreeMap中指定一个范围以取得其子Map。

HashMap 散裂图
LinkedHashMap 链式散裂图
TreeMap 树形图
LinkedHashMap是排序类,其中排序的顺序是按照键值对插入的先后顺序进行。他的构造方法分为两种,一种是无参的构造方法,另一种是以一个hashMap对象作为参数构的构造方法。
TreeMap中的所有的键值对都已经进行了排序进行数据的遍历是最快的。
HashMap通常会用一个指针数组(假设为table[])来做分散所有的key,当一个key被加入时,会通过Hash算法通过key算出这个数组的下标i,然后就把这个<key, value>插到table[i]中。如果有两个不同的key被算在了同一个i,那么就叫冲突,又叫碰撞,这样会在table[i]上形成一个链表。
缺陷:如果table[]的尺寸很小,比如只有2个,如果要放进10个keys的话,那么碰撞非常频繁,于是一个O(1)的查找算法,就变成了链表遍历,性能变成了O(n),这是Hash表的缺陷。
Rehash:Hash表的尺寸和容量非常的重要。一般来说,Hash表这个容器当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大Hash表的尺寸,但是这样一来,整个Hash表里的无素都需要被重算一遍。这叫rehash,这个成本相当的大。
HashMap是基于hashing的原理,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。使用put()方法传递键和值时,先对键调用hashCode()方法,返回的hashCode用于找到对应桶位置来储存Entry对象。如果两个对象的hashcode相同,但也不一定两个对象就相等,因为有equals()和hashCode()方法。因为hashcode相同,所以他们储存的桶位置也相同就会发生碰撞。而HashMap使用链表存储对象,这个对象会被存储在链表之中。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 如果两个键的hashCode相同,HashMap会通过键对象的hashcode找到存储位置,调用key.equals()找到链表中正确的节点,并且获取值对象。所以,使用不可变或者声明为final的对象,采用合适的equals()和hashCode(),可以减少碰撞,不可变性能够缓存不同键的hashcode。
HashMap 的实例有两个参数影响其性能:初始容量 和负载因子。容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量,初始容量 默认大小为16。负载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了负载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍,新数组是原数组的old*2倍,重新计算每个元素在数组中的位置,这个操作对性能消耗较大,顺便带下ArrayList扩充倍数是原数组长度*3/2+1大小, Vector为2倍大小。
HashMap的负载因子问题:默认负载因子大小为0.75,这么理解,当map存储大小超过默认大小75%时,将会创建一个新的数组,,来重新调整大小,还是用过对象的hash方法找到新的存储位置。而这时,请注意,弟兄们,七贱们,出现问题了,重新调整HashMap时存在条件竞争,如果两个线程发现HashMap都需要重新调整大小,就会试着同时去调整,在调整大小时,存储在链表中的元素次序会反过来,移动到新位置时候,HashMap不会把元素放在尾部,而是放在头部,百度到据说这么做是为了避免尾部遍历,(这个尾部遍历我也不懂)。如果竞争发生,就会出现死循环。这就是多线程环境下使用HashMap会出现的线程安全涉及到的小部分问题。
此段为复制到新数组源码,注释我自己加的
do {
Entry<K,V> next = e.next; // <--假设线程一执行到这里被调度挂起了
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);//而线程二执行完成了。
仔细看这段代码
我是这样理解的
线程一而next指向了下一个,而其是在线程二rehash后,指向了线程二重组后的链表,线程二把它next到的值放在了新链表第一个,
线程一被调度回来再执行,先是执行 newTalbe[i] = e;
然后是e = next,指向下一个key,
线程一接着工作把key放到newTable[i]的第一个
,而此时的第二个值为线程二放在了新链表第一个,已经被线程一移位到第二个,然后把e和next往下移。此时就出现环形链接出现。出现死循环。
这个只是copy时出现的多线程,还有在put时据说也会出现多线程问题,还没去研究。看网上说是把这个问题报给了Sun,但是Sun不认为这个是一个问题。因为HashMap本来就不支持并发。要并发就用ConcurrentHashmap。
我目前做的项目里面大量用到HashMap,因为是CS开发,界面和后台交互时,数据都保存在map里面,界面段把map传到服务端去做处理,之前在做一个功能时,我使用了 LinkedHashMap,而项目经理看了我提交的代码,建议我还是使用HashMap,因为那时他在北京,RTX上没能细说为什么,我想还是出于性能考虑,LinkedHashMap支持线程的同步,即任一时刻只有一个线程能写Hashtable,因导致了Hashtable在写入时会比较慢,而HashMap性能方面比Hashtable高出很多。

浙公网安备 33010602011771号