zoukankan      html  css  js  c++  java
  • Map接口的实现类

    HashMap

    HashMap源码阅读

    LinkedHashMap

    LinkedHashMap是HashMap的子类,实际上它连HashMap的putVal等方法都没有重写,因为HashMap就调用了预留给子类的函数,在HashMap中是空实现,在LinkedHashMap中重写,用作建立双向链表

    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    

    这是LinkedHashMap中的一个类,结合上方信息可看出,LinkedHashMap并没有修改HashMap的数据结构,只是在HashMap的基础上添加了两个元素

    newNode方法是putVal中调用的,生成节点的方法,在linkedHashMap也被重写了

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }
    
    

    可以看到,一旦有新元素,那么它就会被放在链表的结尾,那么,如果有修改元素的情况出现呢

    在putVal中有这样一段
    其中afterNodeAccess在HashMap是空实现,在LinkedHashMap中被重写

    if (e != null) { // existing mapping for key
        V oldValue = e.value;
        if (!onlyIfAbsent || oldValue == null)
        e.value = value;
        afterNodeAccess(e);
        return oldValue;
    }
    

    实际上,afterNodeAccess这个方法的就是,将被修改的类置于链表末尾

    void afterNodeAccess(HashMap.Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                    (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }
    

    Hashtable

    Hashtable确实老了,从jdk1.0开始,Hashtable就已经出现了.它甚至都没有按照驼峰命名法来命名

    一般说来,大家都知道Hashtable和HashMap的区别就在于,Hashtable线程安全,而HashMap线程不安全,但是差距肯定不止于此
    由于Iterate和Map出现得比较晚,所以Hashtable没有使用这两个东西,看他独特的继承树,就知道它是版本弃子.
    如果jdk开发人员没有放弃这个类,完全可以完全重写它.
    即使Hashtable线程安全,但是在使用多线程时,还是建议使用Map的同步代理类,说明Hashtable可以完全被遗弃了.
    在功能方面,Hashtable不允许空值

    if (value == null) {
         throw new NullPointerException();
    }
    

    这是Hashtable的put中第一句

    在这里有杜绝了key为null的可能

    int hash = key.hashCode();
    

    那么HashMap是怎么处理的呢?
    首先HashMap没有特别判断value为空的情况
    而hashMap的key的hash值是这么计算的:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

    我看了一下,Hashtable既没有扰乱hash值的操作,也没有红黑树,也没有位运算优化...

    还有一些方法功能上的差别,不细说了

    TreeMap

    要使用这个类,不需要重写hashcode方法与equals方法,但是Key一定要实现Compareble接口或者在创建TreeMap时,传入一个Compartor的实现类
    TreeMap会优先使用Comparator
    TreeMap内部直接是一个红黑树,所以,内部的Key是有序的,要想查找Key的最大最小值都比较容易
    但是,如何进行遍历呢?
    当然想都不用想,用递归实现树的前序遍历可以做到,但是,用户调用Iterate的next方法的时候,树里面的值可不是全部直接出来了
    显然这里不能使用递归

    首先要知道,在调用Iterate的构造器时,指针就已经指向了数中的最小值

    //这个方法用于确定t的下一个元素
    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    
        if (t == null)
            return null;
        右侧不为空,往右侧走一步,接着找到右子树的最小值
        else if (t.right != null) {
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {
        //parent这个属性就是去掉递归的核心
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }
    

    当然相关方法和HashMap也有区别,能写出红黑树的话就差不多能看懂了

    ConcurrentHashMap


    这个类是线程同步的,不支持null
    实际上它的做法就是在table数组上每一个散列位置上加锁,而不是像Hashtable那样吧整张表给锁了
    显而易见,如果要用到多线程,一定要深入了解这个类
    但是目前我对sun.misc.Unsafe的了解还不够,而这个类中用到了它,所以无法对源码进行跟深入的分析

    以后再回头单开一个博客介绍一下这个类吧,同学们一定要搞清楚这个类呀

  • 相关阅读:
    直播报名| Kylin on Parquet 介绍及快速上手
    直播 | Apache Kylin & Apache Hudi Meetup
    1. MySQL体系结构和存储引擎——MySQL体系结构、存储引擎、连接MySQL
    深入理解Java虚拟机(第三版)-13.Java内存模型与线程
    Redis 字典实现
    JVM 判断对象已死亡?
    堆内存常见的分配策略、 经典的垃圾收集器、CMS与G1收集器及二者的比较
    String.intern() 和常量池
    Java 对象的创建过程(五步)、对象的内存布局、对象的访问定位
    Java内存区域(运行时数据区域)详解、JDK1.8与JDK1.7的区别
  • 原文地址:https://www.cnblogs.com/ZGQblogs/p/12531631.html
Copyright © 2011-2022 走看看