• Java笔记(八)TreeMap & TreeSet & LinkedHashMap


    TreeMap & TreeSet & LinkedHashMap

    一、TreeMap

    HashMap缺陷:键值对之间没有特定的顺序。在TreeMap中,

    键值对之间按键有序,TreeMap的实现基础是排序二叉树。

    一)基本用法

    构造方法:

    //无参构造方法要求Map中的键实现Compareble接口
    public TreeMap()
    //如果comparator不为null,在TreeMap内部进行比较时会调用compare方法
    public TreeMap(Comparator<? super K> comparator)

    TreeMap按键的比较结果对键进行重排,即使键实际上不同,但只要比较

    结果相同,它们就会被认为相同,键只会保留一份。

            TreeMap<String, String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            map.put("t", "try");
            map.put("T", "order");
            for (Map.Entry<String, String> kv : map.entrySet()) {
                System.out.println(kv.getKey() + " = " + kv.getValue()); //t = order
            }

    二)实现原理

    TreeMap内部是用红黑树实现的,红黑树是一种大致平衡的排序二叉树。

    1)内部组成 

    内部主要成员:

    private final Comparator<? super K> comparator;
    private transient Entry<K,V> root = null; //指向二叉树根节点
    private transient int size = 0;

    内部类Entry:

        static final class Entry<K,V> implements Map.Entry<K,V> {
            K key;
            V value;
            Entry<K,V> left = null;
            Entry<K,V> right = null;
            Entry<K,V> parent;
            boolean color = BLACK; //表示节点颜色,非黑即红。
            Entry(K key, V value, Entry<K,V> parent) {
                this.key = key;
                this.value = value;
                this.parent = parent;
            }
        }

    2)保存键值对 

    put方法代码,添加第一个节点的情况:

        public V put(K key, V value) {
            Entry<K,V> t = root;
            if(t == null) {
                compare(key, key); // type (and possibly null) check
                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
         //

    如果不是第一次添加,寻找父节点,寻找父节点根据是否设置了comparator分为两种情况:

        int cmp;
        Entry<K,V> parent;
        //split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if(cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if(cmp < 0)
                    t = t.left;
                else if(cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if(key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if(cmp < 0)
                    t = t.left;
                else if(cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while(t != null);
        }

    基本思路:循环比较找到父节点,并插入作为其左孩子或者右孩子,然后调整保持树的大致平衡。

    3)根据键获取值 

        public V get(Object key) {
            Entry<K,V> p = getEntry(key);
            return(p==null ? null : p.value);
        }
            final Entry<K,V> getEntry(Object key) {
            // Offload comparator-based version for sake of performance
            if(comparator != null)
                return getEntryUsingComparator(key);
            if(key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            Entry<K,V> p = root;
            while(p != null) {
                int cmp = k.compareTo(p.key);
                if(cmp < 0)
                    p = p.left;
                else if(cmp > 0)
                    p = p.right;
                else
                    return p;
            }
            return null;
        }

    4)查看是否包含某个值 

    按值查找需要遍历

    public boolean containsValue(Object value) {
        for(Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
        if(valEquals(value, e.value))
        return true;
        return false;
    }
    final Entry<K,V> getFirstEntry() {
        Entry<K,V> p = root;
        if(p != null)
        while (p.left != null)
        p = p.left;
        return p;
    }
        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 {
                Entry<K,V> p = t.parent;
                Entry<K,V> ch = t;
                while(p != null && ch == p.right) {
                    ch = p;
                    p = p.parent;
                }
                return p;
            }
        }

    三)小结

    ThreeMap根据键保存、查找、删除的效率比较高,为O(h),h为树的高度。

    不要求排序优先考虑HashMap。

    二、TreeSet

    一)基本用法

    TreeSet实现了两点:排重和有序

    构造函数:

    public TreeSet()
    public TreeSet(Comparator<? super E> comparator)

    二)实现原理

    TreeSet是基于TreeMap实现的:

    private transient NavigableMap<E,Object> m; //背后的TreeMap
    private static final Object PRESENT = new Object(); //固定值
        TreeSet(NavigableMap<E,Object> m) {
            this.m = m;
        }
        public TreeSet() {
            this(new TreeMap<E,Object>());
        }

    三)小结

    没有重复元素,通过TreeMap实现。

    三、LinkedHashMap

    LinkedHashMap是HashMap的子类,可以保持元素按插入或者访问有序。 

    一)基本用法

    该类内部有一个双向链表维护键值对顺序,每个键值对既位于哈希表中,也位于双向链表中。

    该类支持两种顺序:

    1)插入顺序:先添加的在前面,后添加的在后面,修改不影响顺序。

    2)访问顺序:所谓访问就是get/put操作,对一个键执行get/put操作后,

    其对应的键值会移到链表末尾。

    LinkedHashMap有5个构造方法,其中4个都是按插入顺序,只有一个构造

    方法可以指定按访问顺序:

    public LinkedHashMap(int initialCapacity, float loadFactor,
    boolean accessOrder) //accessOrder为ture就是按顺序访问

    默认情况下LinkedHashMap是按插入有序的:

            LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
            map.put("c", 56);
            map.put("d", 22);
            map.put("a", 33);
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                System.out.println(entry.getKey() + " = " + entry.getValue());
            }
            /*c = 56
            d = 22
            a = 33*/

    插入有序一种常见的使用场景:希望Map按键有序,键在添加前已经排好序,

    此时就没必要使用开销大的TreeMap。

            LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
            map.put("c", 56);
            map.put("d", 22);
            map.put("a", 33);
            map.get("c");
            map.put("d", 66);
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                System.out.println(entry.getKey() + " = " + entry.getValue());
            }
            /*a = 33
            c = 56
            d = 66*/

    二、实现原理

    该类内部实例变量:

    private transient Entry<K,V> header; //双向链表的表头
    private final boolean accessOrder; //是否按访问顺序

    Entry内部类:

        private static class Entry<K,V> extends HashMap.Entry<K,V> {
            Entry<K,V> before, after;
            Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
                super(hash, key, value, next);
            }
            private void remove() {
                before.after = after;
                after.before = before;
            }
            private void addBefore(Entry<K,V> existingEntry) {
                after = existingEntry;
                before = existingEntry.before;
                before.after = this;
                after.before = this;
            }
            void recordAccess(HashMap<K,V> m) {
                LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
                if(lm.accessOrder) {
                    lm.modCount++;
                    remove();
                    addBefore(lm.header);
                }
            }
            void recordRemoval(HashMap<K,V> m) {
                remove();
            }
        }

    init用于初始化链表的头节点:

    void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

    LinkedHashMap中,put方法还会将节点加入到链表中来,如果是

    按访问有序的,还会调整节点到末尾,并根据情况删除掉最久没有被访问的节点。

    HashMap的put实现中,如果是新的键,会调用addEntry方法添加节点,LinkedHashMap重写了该方法:

    void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);
        //Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if(removeEldestEntry(eldest)) {
        removeEntryForKey(eldest.key);
        }
    }

    他先调用父类的addEntry方法,父类的addEntry会调用createEntry创建节点,

    LinkedHashMap重写了createEntry方法:

        void createEntry(int hash, K key, V value, int bucketIndex) {
            HashMap.Entry<K,V> old = table[bucketIndex];
            Entry<K,V> e = new Entry<>(hash, key, value, old);
            table[bucketIndex] = e;
            //新建的节点,加入到链表末尾
            e.addBefore(header);
            size++;
        }

    例如执行:

    Map<String,Integer> countMap = new LinkedHashMap<>();
    countMap.put("hello", 1);

    执行后内存结构:

    在HashMap的put实现中,如果键已经存在,则会调用节点的recordAccess方法。

    LinkedHashMap.Entry重写了该方法,如果是有序访问,则调整该节点到链表末尾。

    Simple is important!
  • 相关阅读:
    通过连接池和字段索引,提升单点登录cas的性能
    crc16.c
    modbus.c
    sciencesoftware科学软件
    C++ ASSERT() 断言机制
    sessionKey
    main函数中argc理解
    compile,build和execute的区别
    Linux vi 中移动光标 命令
    OCP读书笔记(2)
  • 原文地址:https://www.cnblogs.com/Shadowplay/p/10026487.html
Copyright © 2020-2023  润新知