Java Map浅谈(jdk1.7)
来源:互联网 发布:塔西陀陷阱 知乎 编辑:程序博客网 时间:2024/06/09 20:51
Map是非常常用的一种数据结构,最常用的一些Map实现如下图所示:
围绕着Map接口,最主要的实现类有HashTable、TreeMap、HashMap、LinkedHashMap和Properties。
首先对于HashTable和HashMap,两者都实现了Map接口,但HashTable的大部分方法做了同步,而HashMap没有,因此,HashMap不是线程安全的;而且,HashTable不允许key或者value使用null值,而HashMap可以,再者,在内部算法上,它们对key的hash算法和hash值内内存索引的映射算法不同。
1.HashMap的实现
HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap底层就是一个数组结构,数组中的每一项又是一个链表。
/** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//数组,就是这个数组
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next;//下一个 int hash; //通过hash算法算出的hash值 ......table就是一个Entry<K,V>类型的数组,当新建一个HashMap的时候,就会初始化一个数组,长度必须是2的倍数,默认为16,而Entry<K,V>则又是一个链表,hash值相同而key不同的键值对就会以链表的形式存放在table的同一位置。
HashMap的存储实现:
public V put(K key, V value) { if (table == EMPTY_TABLE) {//如果数组是空的,长度为0 //初始化数组,threshold=capacity * loadFactor,即数组大小*负载因子(默认折中0.75) //负载因子越大,表示数组的利用率越高,数组中空的位置就越少,虽然这样增加了内存了利用率, //但是,也增加了hash值冲突的几率,也就是增加了链表的长度,增大了取值的难度,所以,要在时间和空间上取一个平衡点,0.75 inflateTable(threshold); } if (key == null) //如果key为空值,则放在数组的第一个位置 return putForNullKey(value); int hash = hash(key);//根据key计算hash值 int i = indexFor(hash, table.length);//通过hash值确定存放在数组中的具体位置--类似于取模运算 for (Entry<K,V> e = table[i]; e != null; e = e.next) {//根据下标i,遍历链表 Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//如果hash值相同且key也相同,则,覆盖原值value,key不变 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++;//修改标记加1,即最新标记,如果此时,其他的线程修改此map,会快速失败抛出异常 addEntry(hash, key, value, i);//增加新的entry return null; }
简单地说,HashMap就是将key做hash算法,然后将hash值映射到内存地址,直接取得key所对应的数据。在HashMap中,底层数据结构使用的是数组,所谓的内存地址即数组的下标索引。HashMap的高性能需要保证以下几点:
a.hash算法必须是高效的;
b.hash值到内存地址(数组索引)的算法是快速的;
c.根据内存地址(数组索引)可以直接取得对应的值。
下面再对以上代码用到的几个方法做个解析:
final int hash(Object k) { int h = hashSeed;//如果hashSeed==0,Hashing将会被禁用 if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode();//获取响应的hashcode,可参照String类中的hashCode()方法 // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). //将二进制中的"1"分布的尽量均匀,使得下面indexFor()得到的下标也相对均匀,减少hash碰撞的几率 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /** * Returns index for hash code h. */ static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1);//按位取与,相当于取模mod或者取余% }从上面可以看到,不管是计算hash值还是取模,都是使用的位运算,还是非常高效的。而HashTable就不是这样了,也许是年久失修吧,有兴趣的同学可以看看。
void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) {//如果实际存储的个数大于可用的数量,扩容 resize(2 * table.length);//扩容为原来的2倍,并重新计算在数组中的位置,有点耗费性能,如果已知大小,最好创建时候指定capacity hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex);//增加一个新entry } //增加一个新entry,如果bucketIndex位置已经有链表则放在头部,如果没有,就新建链表并放头部 void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex];//在bucketIndex位置取出链表 table[bucketIndex] = new Entry<>(hash, key, value, e);//将新entry放在链表头部 size++;//实际大小加1 }HashMap的取值实现:
public V get(Object key) { if (key == null)//如果key为空,在数组第一个位置遍历链表 return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } private V getForNullKey() { if (size == 0) { return null; } for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash &&//如果hash值相同并且key也相同,读取value ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
2.LinkedHashMap------有序的HashMap
LinkedHashMap继承自HashMap,因此,它具备了HashMap的优良特性--高性能。在HashMap的基础上,LinkedHashMap又在内部增加了一个链表,用以存放元素的顺序。因此,LinkedHashMap可以简单地理解为一个维护了元素次序表的HashMap。
LinkedHashMap可以提供两种类型的顺序:一是元素插入时的顺序;二是最近访问的顺序。可以通过以下构造函数类指定排序行为:
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)当accessOrder为true时,按照元素最后访问时间排序;否则按照插入顺序排序。默认为false。
在内部实现中,LinkedHashMap通过继承HashMap.Entry类,实现了LinkedHashMap.Entry,为LinkedHashMap.Entry增加了before和after属性用以记录某一表项的前驱和后继,并构成循环链表。
private static class Entry<K,V> extends HashMap.Entry<K,V> { // These fields comprise the doubly linked list used for iteration. Entry<K,V> before, after;。。。
如果将accessOrder设置为true,那么在迭代的过程中使用get()操作就会抛出java.util.ConcurrentModificationException。因为只要在迭代器中修改被迭代的集合,就会抛出ConcurrentModificationException异常,这个特性适用于所有的集合类,包括HashMap、Vector、ArrayList等。
因为如果LinkedHashMap工作在按照元素访问顺序排序的模式中,get()方法会修改LinkedHashMap中的链表结构,以便将最近访问的元素放置到链表的末尾,所以就有了这个异常错误。
3.TreeMap-----另一种Map的实现
TreeMap实现了SortMap接口,就意味着它可以对元素进行排序,同样的,它的性能也有略微低于HashMap。
与LinkedHashMap不同,LinkedHashMap是根据元素增加或者访问的先后顺序进行排序,而TreeMap则根据元素的key进行排序。为了确定key的排序算法,可以使用两种方式指定:
(1)在TreeMap的构造函数中注入一个Comparator:
public TreeMap(Comparator<? super K> comparator)(2)使用一个 实现了Comparable接口的key。
对于TreeMap而言,排序是一个必须进行的过程,因此,要正常使用TreeMap,一定要通过其中一种方式将排序规则传递给TreeMap。如果既不指定Comparator,又不去实现Comparable接口,那么在put()操作时,就会抛出java.lang.ClassCastException异常。
TreeMap的内部实现是基于红黑树的。红黑树是一种平衡查找树,它的统计性能有优于平衡二叉树。
- Java Map浅谈(jdk1.7)
- Java List浅谈(基于jdk1.7)
- JAVA数据结构Map浅谈!!
- Java Map使用浅谈
- JDK1.8源码学习之Map.java
- 浅谈Java——map
- 切换java-javac jdk1.7-jdk1.6
- java jdk1.7与jdk1.8 区别
- java jdk1.7 search
- 浅谈java Map 和java Bean
- 浅谈java中map遍历算法
- 浅谈java集合类(二)【Map】
- java jdk1.7新特性
- JAVA帮助文档全系列 JDK1.5 JDK1.6 JDK1.7 官方中英完整版下载
- JAVA帮助文档全系列 JDK1.5 JDK1.6 JDK1.7 官方中英完整版下载
- JDK1.5 JDK1.6 JDK1.7 + JAVA帮助文档全系列官方中英完整版下载
- JAVA帮助文档全系列 JDK1.5 JDK1.6 JDK1.7 官方中英完整版下载
- JAVA帮助文档全系列 JDK1.5 JDK1.6 JDK1.7 官方中英完整版下载
- push,pop到任意控制器(一)
- troublehub网站网址
- 定一个属于我自己的"小目标"
- ffmpeg使用一:录屏保存为yuv420p
- 如何彻底修改Eclipse中项目名称
- Java Map浅谈(jdk1.7)
- Linux根据关键字批量杀进程
- [Android设计模式]Android退出应用程序终极方法
- 玩转Android之手摸手教你DIY一个抢红包神器!
- 图解 Maven 安装配置 (win7 64bit)
- 冒泡排序
- 八皇后问题
- 随笔
- android demo(一):跟随手指的小球