java核心数据结构(二)——Map类族



Map类族的类图关系:

 跟List 和Set 不同的是,Map接口并不继承于Collection接口,它有一套自己的实现。

抽象类AbstractMap实现了Map接口,同时有EnumMap、HashMap、WeakHashMap三个实现类,而LinkedHashMap又继承于HashMap.

首先关注一下hashTble和hashMap的区别。两者同样实现了Map接口,但HashTable的大部分方法都做了线程同步控制,而HashMap则没有

 public synchronized int size() {
        return count;
    }
    public synchronized boolean isEmpty() {
        return count == 0;
    }
    public synchronized Enumeration<K> keys() {
        return this.<K>getEnumeration(KEYS);
    }
       另外,HashTable不允许key、value任意一值为null;从内部算法上而言,HashTable和HashMap对key-value的映射算法不同。以HashMap为例,总结一下它的实现机理。

一、HashMap

1、HashMap的实现原理

   将key做hashCode(hash算法),将得到的hash值映射到内存地址中,直接取得key所对应的value。在HashMap中底层数据结构基于数组,内存地址也就是数组的下标索引。JDK1.8对HashMap相关的hash算法如下:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }  
           取得key的hash值后,根据hash值找到对应的数组下标,获取对应的value。  

2、Hash冲突

    HashMap性能影响最值得关注的就是Hash冲突问题。简单来说就是两个对象通过hash计算后,指向同一个内存地址。针对hash冲突,就得说到HashMap的数组内元素-Entry类对象。Entry类有key、value、next、hash四个属性,HashMap中的put方法里的Node类就继承于Entry,当发生hash冲突时,产生的新的Entry对象会被安放到对应的内存地址中,替换原有值,为保证旧数据不丢失,将新entry对象的next指向旧值。所以总的来说,HashMap还是一个基于数组-数组中又基于链表的数据结果。JDK1.8中主要是对Node对象的操作,但是大同小异,不管怎么写,HashMap的源码还是让人很吐血的。

    所以,一般只要对象的hashCode方法写的足够好,减少hash冲突的发生,HashMap的性能还是可以保证。但如果有大量的hash冲突,使得不得不遍历多次链表,这个对于链表的遍历在上一篇博客中已经提到了-使用随机遍历的时间是打杯水上个厕所也等不到结果的。呵呵哒。

3、容量参数

   对于ArrayList和Vector内部使用数组结构实现,当内存空间不足需要扩容时,开销是比较大的。在HashMap上做了一定的优化。主要体现在HashMap的多个构造参数

 public HashMap(int initialCapacity) {//初始容量==数组大小
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    } 
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }  
  public HashMap(int initialCapacity, float loadFactor) {负载因子=元素个数/内部数组总大小
        。。。。。
    }  
public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }  
         当负载因子>1时,也就意味着,需要放入的元素个数>容量大小,僧多粥少,产生hash冲突的可能性就会提高。所以通常建议负载因子在0~1之间,默认HashMap的容量是16,负载因子为0.75。合理的设置容量和负载因子大小,调用构造函数初始化HashMap,可以减少冲突,提高访问速度。

二 、LinkedHashMap

    HashMap性能已经不错,不过它的缺点之一在于无序,存入HashMap中的元素在遍历时都是无序的。LinkedHashMap就是在顺序行这方面对HashMap做了优化。

    LinkedHashMap继承于HashMap, 增加了首尾次序的参数:也提供了相应的在node前、后\插入移除等方法实现(after、before)。

transient LinkedHashMap.Entry<K,V> head;
   transient LinkedHashMap.Entry<K,V> tail;  
   void afterNodeInsertion(boolean evict) { // possibly remove eldest 
   ……
  } 
   void afterNodeAccess(Node<K,V> e) { // move node to last 
   ……
  } 

三、TreeMap

    TreeMap继承于AbstractMap抽象类,主要也是应用于对元素进行排序,但排序方式有别于LinkedHashMap,TreeMap基于一种平衡查找树结构,可根据一定的条件对元素进行排序。具体应用笔者并没有实践研究,就不在此献丑了。

 四、总结

  对于Map类族的主要实现类和应用特点本文都做了简单的总结,这些源码跟原理上的知识可能稍微有些晦涩,但源码慢慢看,先看能看懂的,看多了就会发现对于java核心特性例如可变参数、反射、泛型应用是非常广泛的。(特性内容在上个月的博文中有系列介绍)知识都是相通的,知道的多了看的也就流畅了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值