Map接口

Map接口

1 概述

Map接口中都存储的是key(键)和value(值)。每一个key都是不重复的,值是可以重复的。每一个key只能对应一个value,称之为键值对

一个Map由多个键值对组成。

将每一个键值对都看作一个对象,抽取出一个代表键值对的类,Map.Entry

2 Map常用方法

public static void main(String[] args) {
        // Map接口常用的方法
        Map<String,String> map = new HashMap<>();
        // 添加数据
        map.put("郭德纲","于谦");
        map.put("舒克","贝塔");
        map.put("李晨","范冰冰");
        map.put("贾乃亮","李小璐");
        map.put("丁真","王源");
        map.put("丁真","雪豹");
        map.put("王宝强","马蓉");
        map.put("pgone","李小璐");
        // 清空映射
//        map.clear();
        // 判断是否包含某个key
        System.out.println(map.containsKey("舒克"));
        // 判断是否包含某个value
        System.out.println(map.containsValue("王源"));
        // 获取键值对的集合
        Set<Map.Entry<String, String>> entries = map.entrySet();
        System.out.println(entries);
        // 快速遍历
        map.forEach((k,v) -> System.out.println(k + v));
        // 根据key获取value
        String s = map.get("pgone");
        System.out.println(s);
        // 判断映射是否为空
        System.out.println(map.isEmpty());
        // 获取所有的key
        Set<String> strings = map.keySet();
        System.out.println(strings);
        map.remove("贾乃亮");
        // 键值对的个数
        System.out.println(map.size());
        // 获取所有value的集合
        Collection<String> values = map.values();
        System.out.println(values);
        System.out.println(map);
    }

课堂练习:统计字符串中字符出现的次数 abfgafaba

public static void test(){
        // 统计字符串中字符出现的次数
        String str = new Scanner(System.in).next();
        // 创建Map
        Map<Character,Integer> map = new HashMap<>();
        for (int i = 0; i < str.length(); i++) {
            // 获取字符
            char c = str.charAt(i);
            // 判断Map中是否有对应的字符的key
            if (map.containsKey(c)){
                // 把出现次数+1
                Integer value = map.get(c);
                value += 1;
                map.put(c,value);
            }else {
                map.put(c,1);
            }
        }
        map.forEach((k,v) -> System.out.println(k + "出现的次数是" + v));
    }

3 HashMap类

HashMap是哈希表的一个实现,是无序的,key是唯一的。并且key和value可以是null,是线程不安全的,而且其中的数据的位置可能会发生改变。

底层的数据结构是数组+单向链表+红黑树

3.1 构造方法

// 无参构造  把默认的0.75赋值给加载因子  第一次添加数据的时候会创建长度为16的数组
HashMap()
// 指定初始容量  默认的加载因子0.75
HashMap(int initialCapacity)
// 可以指定加载因子和初始容量  初始容量一定是2的n次方  第一次添加数据的时候会创建长度为2的n次方的数组
HashMap(int initialCapacity, float loadFactor)

3.2 底层源码

  • 成员变量
// 默认加载因子
float DEFAULT_LOAD_FACTOR = 0.75f;
// 加载因子
float loadFactor;
// 底层的数组
Node<K,V>[] table;
// 扩容的阈值
int threshold;
// 默认初始容量
int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最小扭转红黑树的容量
int MIN_TREEIFY_CAPACITY = 64;
// 红黑树扭转链表
int UNTREEIFY_THRESHOLD = 6
// 链表扭转红黑树
int TREEIFY_THRESHOLD = 8
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // tab 底层数组
        Node<K,V>[] tab; 
    	// 数组中的一个结点
        Node<K,V> p; 
        // n 数组的长度
        // i 索引
        int n, i;
        // 判断是否是首次添加数据
        if ((tab = table) == null || (n = tab.length) == 0)
            // 首次添加数据 resize方法中会创建数组
            n = (tab = resize()).length;
    	// 计算数据要放的索引,然后判断索引处是否有内容  如果没有内容就放在索引处
        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))))
                // key相同 用新值覆盖旧值
                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);
                        // 判断链表的长度,如果>8 就扭转成红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // key相同 用新值覆盖旧值
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                // 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;
    }
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        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;
    }

4 Hashtable

Hashtable的key和value不能是null,是同步线程安全的映射。

默认的初始容量是11,默认的加载因子是0.75

指定的初始容量给多少就是多少

扩容是容量扩大一倍再加1

实现类 底层数据结构 key/value能否为null 线程是否安全
HashMap 数组+单向链表+红黑树 都可以
LinkedHashMap 数组+双向链表+红黑树 都可以
TreeMap 红黑树 key不能为null,value可以是null
Hashtable 哈希表 都不能为null 同步线程安全
ConcurrentHashMap 哈希表 都不能为null 异步线程安全

总结:

  • 当创建HashMap时,会把加载因子赋值为0.75
  • 当第一次put时,会创建一个长度为16的Node类型的数组,默认的阈值是12,然后根据计算把数组中的某一个索引处添加一个Node对象
  • 如果key相同,hash值肯定相同,会把旧值替换成新值,并且返回旧值
  • 如果key不相同,但是hash相同,会对对应的桶上的位置进行一个个的逐位比较,直到链表结点的next是null,然后连接在这个链表的最后一个。如果链表长度大于8,并且数组长度大于等于64,这时会将链表转换成红黑树,从而提高查询效率
  • 如果链表长度大于8,数组长度小于64,会进行一次扩容。数组长度翻倍,并且重新进行进行高低位分配
  • 初始容量一定是2的n次方,保证(n - 1) & hash 的结果是 0到数组长度-1的范围内
  • 扩容时容量翻倍,扩容后结点要么放在原来位置,要么放在原来位置+原来的数组长度处
  • 扩容后如果桶上是一个红黑树,经过运算发现桶上的长度小于等于6,会把红黑树转换成链表

注意:使用HashMap时的key一般不要使用自定义类型。如果要使用,建议重写hashCode和equals方法

posted @ 2025-07-09 17:37  小胡coding  阅读(10)  评论(0)    收藏  举报