Java集合(六)--TreeMap简析

本篇分析TreeMap。

TreeMap的定义及说明

定义如下:

public class TreeMap
    extends AbstractMap
    implements NavigableMap, Cloneable, java.io.Serializable{}p

1、继承了AbstractMap及实现了NavigableMap接口。我们从集合框架那一篇文章中已经知道NavigableMap是一个扩展了SortedMap接口的接口。而SortedMap接口主要提供有序的Map实现。所以TreeMap是一个有序的Map。

2、实现了Cloneable,即支持clone。

3、实现了java.io.Serializable,即支持序列化和反序列化。

TreeMap是基于红黑树的实现的。根据其键的可比较的自然顺序,或者创建时提供的Comparator进行排序,具体取决于使用的构造函数。

接下来看构造方法:

//比较器,用于维护TreeMap中映射的顺序,如果它使用其键的自然顺序,则为null。
private final Comparator comparator;

//构造一个根据键的自然顺序排序的TreeMap
public TreeMap() {
        //比较器为null
        comparator = null;
    }
    
//构造空的,给定比较器的TreeMap
public TreeMap(Comparator comparator) {
        //使用指定的比较器
        this.comparator = comparator;
    }

//构造一个与给定映射相同的TreeMap,根据其键的自然顺序进行排序
public TreeMap(Map m) {
        comparator = null;
        //将指定映射中的所有映射复制到此TreeMap。这些映射将替换TreeMap中对当前位于指定映射中的任何键的任何映射
        putAll(m);
    }

//构造一个与m具有相同元素和元素顺序的TreeMap
public TreeMap(SortedMap m) {
        //返回用于对m中的键进行排序的比较器
        comparator = m.comparator();
        try {
           
             //使用线性时间算法来自排序数据
             buildFromSorted(m.size(),m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

从构造函数中我们知道,如果我们没有指定比较器,则使用键的自然顺序排序。

TreeMap源码简析

按照惯例,去看给TreeMap赋值的put()方法:

//比较器
private final Comparator comparator;

//红黑树的根节点
private transient TreeMapEntry root;

//TreeMap的大小
private transient int size = 0;

//TreeMap修改次数
private transient int modCount = 0;

public V put(K key, V value) {
        //将根节点的值赋予t
        TreeMapEntry t = root;
        //判断根节点是否为null
        if (t == null) {
            //根节点为null,判断比较器是否为null
            if (comparator != null) {
                //比较器为null,判断key是否为null
                if (key == null) {
                    //检查输入null值的副作用
                    comparator.compare(key, key);
                }
            } else {
                //如果比较器不为null,判断key是否为null
                if (key == null) {
                    //key为null,抛出异常
                    throw new NullPointerException("key == null");
                } else if (!(key instanceof Comparable)) {
                //comparator为null,key不为null,但是key对象没有实现Comparable接口,则抛出异常
                    throw new ClassCastException(
                            "Cannot cast" + key.getClass().getName() + " to Comparable.");
                }
            }
            //将此元素作为根节点
            root = new TreeMapEntry<>(key, value, null);
            
            size = 1;
            modCount++;
            return null;
        }
        
        //如果存在根节点,执行以下操作
        //key与某个节点的key所比较的值
        int cmp;
        //与key相比较的某个节点
        TreeMapEntry parent;
        //获取比较器将其赋值与cpr 
        Comparator cpr = comparator;
        //判断比较器是否为null
        if (cpr != null) {
            //比较器不为null,执行循环
            do {
                //将t的值赋予parent(第一次循环为root)
                parent = t;
                //使用比较器比较要插入的key跟当前节点的key
                cmp = cpr.compare(key, t.key);
                //如果key小则将t的值置为当前节点左孩子的值
                if (cmp < 0)
                    t = t.left;
                //如果key大则将t的值置为当前节点右孩子的值
                else if (cmp > 0)
                    t = t.right;
                else
                    //如果key值相同,则覆盖当前节点的值
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            //如果比较器为null,判断key是否为null
            if (key == null)
                //key为null,则抛出异常
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
            //此处注意,key需为实现了Comparable的对象,不然会抛出ClassCastException。
                Comparable k = (Comparable) key;
            //执行循环    
            do {
                //将t的值赋予parent(第一次循环为root)
                parent = t;
                //将key与当前节点的key做比较
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                //如果key小则将t的值置为当前节点左孩子的值
                    t = t.left;
                else if (cmp > 0)
                //如果key大则将t的值置为当前节点右孩子的值
                    t = t.right;
                else
                //如果key值相同,则覆盖当前节点的值
                    return t.setValue(value);
            } while (t != null);
        }
        //设置一个新的节点
        TreeMapEntry e = new TreeMapEntry<>(key, value, parent);
        //如果key比当前节点的key值小
        if (cmp < 0)
            //将新元素作为当前节点的左孩子
            parent.left = e;
        else
            //否则,将新元素作为当前节点的右孩子
            parent.right = e;
        //插入修正,以保证插入新元素后还是红黑树结构    
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

private static final boolean RED   = false;
private static final boolean BLACK = true;

//红黑树结构
static final class TreeMapEntry implements Map.Entry {
       
        //key值
        K key;
        
        //value值
        V value;
        
        //左孩子
        TreeMapEntry left;
        
        //右孩子
        TreeMapEntry right;
        
        //父节点
        TreeMapEntry parent;
        
        //节点颜色
        boolean color = BLACK;

        //初始化
        TreeMapEntry(K key, V value, TreeMapEntry parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        //其他方法
        ..........
    }

通过上面的分析,我们发现TreeMap其实是通过操作TreeMapEntry类型的root属性,以实现对数据的插入。也就是说,对TreeMap的操作其实是通过对红黑树的操作来完成的。

接下来,我们再分析一些其他的方法:

对TreeMapEntry的操作

对TreeMap的操作的方法有firstEntry()、lastEntry()、pollFirstEntry()、pollLastEntry()、lowerEntry()、floorEntry()、ceilingEntry()、higherEntry()。它们的都是通过对树的遍历找到并操作指定的节点。比如firstEntry():

//获取第一个节点的值
public Map.Entry firstEntry() {
        return exportEntry(getFirstEntry());
    }

//获取第一个节点的值     
final TreeMapEntry getFirstEntry() {
        //遍历树的左节点,获取第一个节点的值
        TreeMapEntry p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }

//返回一个AbstractMap.SimpleImmutableEntry
static  Map.Entry exportEntry(TreeMapEntry e) {
        return (e == null) ? null :
            new AbstractMap.SimpleImmutableEntry<>(e);
    }

//给key和value赋值
public SimpleImmutableEntry(Entry entry) {
            this.key   = entry.getKey();
            this.value = entry.getValue();
        }    

在上面的分析中,我们发现一个陌生的类,AbstractMap.SimpleImmutableEntry。这个类是为了使键和值不可变,该类不支持setValue()方法。这么做是为了防止我们修改返回的Entry。

对key操作的相关方法

对key操作的方法有firstKey()、lastKey()、lowerKey()、floorKey()、ceilingKey()、higherKey(),我们看一下firstKey()方法:

public K firstKey() {
        return key(getFirstEntry());
    }
    
 static  K key(TreeMapEntry e) {
        if (e==null)
            throw new NoSuchElementException();
        return e.key;
    }   

即通过获取节点的TreeMapEntry对象来获取其对应的key。

对value操作的相关方法

对value操作的方法有containsValue()、get()、values()等,我们看一下get()方法:

 public V get(Object key) {
        //获取拥有指定的key的节点
        TreeMapEntry p = getEntry(key); 
         //返回值
        return (p==null ? null : p.value);
    }
    
final TreeMapEntry getEntry(Object key) {
        //判断比较器是否为空
        if (comparator != null)
            return getEntryUsingComparator(key);
        //判断key是否为null    
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        //强制转换获取key的比较器
            Comparable k = (Comparable) key;
        //将root的值赋予t    
        TreeMapEntry p = root;
        //遍历树并获取拥有指定key的节点,没有返回null
        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;
    }    

我们可以看到,其实还是通过对树的遍历获取指定的节点,再获取指定的值。

与树相关的操作

与操作树相关的方法有左旋(rotateLeft())、右旋(rotateRight())、插入修正(fixAfterInsertion())、删除修正(fixAfterDeletion())等等。我们看一下左旋:

private void rotateLeft(TreeMapEntry p) {
        if (p != null) {
            TreeMapEntry r = p.right;
            p.right = r.left;
            if (r.left != null)
                r.left.parent = p;
            r.parent = p.parent;
            if (p.parent == null)
                root = r;
            else if (p.parent.left == p)
                p.parent.left = r;
            else
                p.parent.right = r;
            r.left = p;
            p.parent = r;
        }
    }

好了,本篇文章就分析到这,后期看看是否有补充。

你可能感兴趣的:(Java集合(六)--TreeMap简析)