JAVA基础之HashMap与TreeMap

首页

看之前建议先看这一篇
首页:JAVA基础之容器汇总

HashMap

HashMap实现了Map接口,使用哈希算法进行实现,键不能重复,如果重复,新的值会替换旧的值并返回旧的值。HashMap集合了数组和链表的优点。

代码实现

public class HashMapTest {
    public static void main(String[] args) {
        //实例化HashMap
        Map<String ,String> map = new HashMap<>();

        //添加元素
        map.put("a","AA");
        //相同的键会替换原来的值并返回原来的值
        String value = map.put("a","A");
        System.out.println("value = " + value);

        map.put("b","B");
        map.put("c","C");
        map.put("z","z");

        //三种获取map值的方式
        //第一种,直接get
        System.out.println("1.-----------------------------------------");
        value = map.get("a");
        System.out.println("value = " + value);
        //第二种,通过keySet()获取,如果只需要取键不要值,建议使用这一种方法
        Set<String> set = map.keySet();
        System.out.println("2.-----------------------------------------");
        for (String key : set){
            String v = map.get(key);
            System.out.println("key = " + key + " value = " + v);
        }

        //第三种,通过Map.Entry与entrySet()获取,如果是要取键值对的话建议使用这一种方法
        Set<Map.Entry<String ,String> > entries = map.entrySet();
        System.out.println("3.-----------------------------------------");
        for(Map.Entry<String ,String> entry : entries){
            String key = entry.getKey();
            String v = entry.getValue();
            System.out.println("key = " + key + " value = " + v);
        }

        //并集操作,相同的后者会覆盖者
        Map<String ,String> map2 = new HashMap<>();
        map2.put("d","D");
        map2.put("e","E");
        map2.put("a","AA");
        map.putAll(map2);

        Set<Map.Entry<String ,String> > entries1 = map.entrySet();
        System.out.println("4.-----------------------------------------");
        for(Map.Entry<String ,String> entry : entries1){
            String key = entry.getKey();
            String v = entry.getValue();
            System.out.println("key = " + key + " value = " + v);
        }

        //删除元素
        String val = map.remove("z");
        System.out.println("remove: val = " + val);
        System.out.println("4.-----------------------------------------");
        for(Map.Entry<String ,String> entry : map.entrySet()){
            String key = entry.getKey();
            String v = entry.getValue();
            System.out.println("key = " + key + " value = " + v);
        }

        //判断key是否存在
        System.out.println("map.containsKey(\"a\") = " + map.containsKey("a"));
        System.out.println("map.containsKey(\"z\") = " + map.containsKey("z"));
        //判断value是否存在
        System.out.println("map.containsValue(\"A\") = " + map.containsValue("A"));
        System.out.println("map.containsValue(\"AA\") = " + map.containsValue("AA"));
    }
}

结果:

"C:\Program Files\Java\jdk1.8.0_144\bin\java.exe"
value = AA
1.-----------------------------------------
value = A
2.-----------------------------------------
key = a value = A
key = b value = B
key = c value = C
key = z value = z
3.-----------------------------------------
key = a value = A
key = b value = B
key = c value = C
key = z value = z
4.-----------------------------------------
key = a value = AA
key = b value = B
key = c value = C
key = d value = D
key = e value = E
key = z value = z
remove: val = z
4.-----------------------------------------
key = a value = AA
key = b value = B
key = c value = C
key = d value = D
key = e value = E
map.containsKey("a") = true
map.containsKey("z") = false
map.containsValue("A") = false
map.containsValue("AA") = true

Process finished with exit code 0

底层实现

我们知道

  1. 数组:占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。
  2. 链表:占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。
    而哈希表则可以结合两者的特点,而HashMap底层就是使用哈希表实现的,所以HashMap具有数组和链表两者的优点。
    存储结构如下:
    在这里插入图片描述

这里也有jdk版本问题,在jdk1.7及之前,是没有红黑树的,而且链表采用的是头插法,而jdk1.8及之后出现了红黑树这个概念,链表采用的是尾插法,那什么时候采用链表什么时候转换成红黑树呢?需要满足以下两个条件会将链表转换成红黑树:

  1. 当链表长度大于8时
  2. 当数组长度大于64时

而什么时候将红黑树转换回链表呢?满足一个条件即可

  1. 当红黑树的节点个数小于6时
底层源码:
成员变量:
  /**
     * The default initial capacity - MUST be a power of two.
     */
     //一开始数组默认长度为16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
     //最大容量为2的30次方,为啥不是31位,我觉得最高位应该是符号位,所以就取31位了
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    //负载因子,当数组使用量超过3/4时,会进行数组扩容
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     */
     //转换成红黑树时的链表长度阈值
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     */
     //红黑树转换成链表的节点个数阈值
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
    //转换成红黑树时需要数组的最小长度,阈值
    static final int MIN_TREEIFY_CAPACITY = 64;

节点类型

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

HashMap也是采用延迟加载方式进行加载时,等到使用时会使用resize方法进行数组初始化和扩容处理。

/**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
     //无参构造方法只是对负载因子进行了赋值,其他的没有处理
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
resize()方法

看得懂就看,看不到就下次再看了

/**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    final Node<K,V>[] resize() {
    	//这里一开始table是空的
        Node<K,V>[] oldTab = table;
        //所以oldCap是0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //负载因子拿过来
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

计算Hash值

put方法

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
  1. 获取key对象的hashcode
    计算方法是取key的hashcode()的高16位与低16位进行异或操作得到
    看了下,hashCode()最终是使用native关键字来标识,所以hashCode的计算并不是通过java语言来实现,那我们就不用管这个hashCode怎么算出来了。
public native int hashCode();
 /**
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
    static final int hash(Object key) {
        int h;
        //>>>是无符号数的右移操作
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  1. 根据 hashcode 计算出 hash 值(要求在[0, 数组长度-1]区间)
    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //看这里,看到没,这里将计算得到的hash值与数组长度减一进行与运算,然后再存值
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

前面看懂了吗,看不到就别看了,再看我也晕了,举个例子
假设我们拿到的hash值是98765,数组还没有扩容前是16,将其转换成0-15之间的hash值为:
在这里插入图片描述
所以98765的hash值是4.

就这样吧,hashMap源码还不是完全能看懂。

TreeMap

TreeMap 和 HashMap 同样实现了 Map 接口,所以,对于 API 的用法来说是没有区别的。HashMap 效率高于 TreeMap;TreeMap 是可以对键进行排序的一种容器,在需要对键排序时可选用 TreeMap。TreeMap 底层是基于红黑树实现的。
在使用 TreeMap 时需要给定排序规则:

  1. 元素自身实现比较规则
  2. 通过比较器实现比较规则

代码实现

实现方式跟TreeSet基本一样

public class TreeMapTest {
    public static void main(String[] args) {
        Map<Student,String > map1 = new TreeMap<>();
        map1.put(new Student("yql",18),"yql");
        map1.put(new Student("yql2",20),"yql2");
        map1.put(new Student("yql3",20),"yql3");

        for(Map.Entry<Student,String> entry : map1.entrySet()){
            System.out.println("key = " + entry.getKey() + " value = " + entry.getValue());
        }

        System.out.println("-----------------------------------------------");
        Map<Users,String > map2 = new TreeMap<>(new UserComparator());
        map2.put(new Users("yql",18),"yql");
        map2.put(new Users("yql2",20),"yql2");
        map2.put(new Users("yql3",20),"yql3");

        for(Map.Entry<Users,String> entry : map2.entrySet()){
            System.out.println("key = " + entry.getKey() + " value = " + entry.getValue());
        }
    }
}

结果:

key = Student{name='yql', age=18} value = yql
key = Student{name='yql2', age=20} value = yql2
key = Student{name='yql3', age=20} value = yql3
-----------------------------------------------
key = Users{username='yql', userage=18} value = yql
key = Users{username='yql2', userage=20} value = yql2
key = Users{username='yql3', userage=20} value = yql3

Process finished with exit code 0

嗯,就先这样吧

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值