Map集合的使用

Map集合类型

  • Map
    特点:存储的是键值对映射关系,根据key可以找到value。
  • HashMap
    1.采用HashTable存储结构
    2.添加快、删除快、查询快
    3.缺点:key无序
  • LinkedHashMap
    1.采用HashTable存储结构,同时采用链表维护次序
    2.key有序(添加顺序)
  • TreeMap
    1.采用红黑树的存储结构
    2.优点:key有序,查询速度比List快(按照内容查询)
    3.缺点:查询速度没有HashSet快

使用Map存储国家简称-国家名称映射

package com.bjsxt.map;

import java.util.*;

/*
HashMap
        key:唯一、无序  HashSet
        value:不唯一、无序   Collection
 */
public class TestMap {
    
    
    public static void main(String[] args) {
    
    
        //创建一个Map集合对象
        Map<String, String> map = new HashMap<String, String>();
        Map<String, String> map1 = new LinkedHashMap<String,String>();
        Map<String, String> map2 = new TreeMap<String, String>();
        //向Map中添加元素
        map.put("CN", "China");
        map.put("jp", "Japan");
        map.put("us", "the United States");
        map.put("uk", "England");
        map.put("en", "England");
        //从Map中根据key获取value
        System.out.println(map.size());
        System.out.println(map);
        System.out.println(map.containsKey("CN"));
        System.out.println(map.isEmpty());
        System.out.println(map.get("CN"));
        System.out.println(map.get("if"));
        System.out.println(map.keySet());//Set 得到所有的key
        System.out.println(map.values());//Collection 得到所有的value
        System.out.println("==============================================");
        //Map的遍历
        //思路一:先得到所有的key,再根据key找到value
        Set<String> keySet=map.keySet();
        for (String key:keySet
             ) {
    
    
            System.out.println(key+"---->"+map.get(key));
        }

        System.out.println("==============================================");

        //思路二:先得到所有的key-value组成的Set,然后输出每个key-value
        /*
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        Iterator<Map.Entry<String, String>> it = entrySet.iterator();
        while (it.hasNext()) {
            //取出一个entry
            Map.Entry<String, String> entry = it.next();
            //输出一个entry
            System.out.println(entry.getKey()+"------>"+entry.getValue());
        }

         */

        //推荐使用,和思路二一样
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        for (Map.Entry<String,String> entry:
             entrySet) {
    
    
//            System.out.println(entry);
            System.out.println(entry.getKey()+"--->"+entry.getValue());
        }
    }
}

使用各种Map存储学号-学生映射

package com.bjsxt.map;

import com.bjsxt.set.Student;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/*
使用Map存储学号和学生的映射
 */
public class TestMap2 {
    
    
    public static void main(String[] args) {
    
    
        //创建一个Map对象存储key-value
        Map<Integer, Student> map = new HashMap<Integer, Student>();
        //向Map对象中添加多个键值对
        Student s1 = new Student(1, "张永昌", 21, 380);
        Student s2 = new Student(2, "zhang", 21, 370);
        Student s3 = new Student(3, "yong", 21, 360);
        Student s4 = new Student(4, "chang", 21, 390);
        Student s5 = new Student(5, "hhhhh", 21, 375);
        Student s6 = new Student(2, "zhang", 21, 370);
        map.put(s1.getSno(),s1);
        map.put(s2.getSno(),s2);
        map.put(s3.getSno(),s3);
        map.put(s4.getSno(),s4);
        map.put(s5.getSno(),s5);
        map.put(s6.getSno(),s6);

        //其他方法
//        map.clear();
        map.remove(2);
        
        //遍历输出
        System.out.println(map.size());
        System.out.println(map.get(3));

        System.out.println("===========================================");

        Set<Map.Entry<Integer, Student>> entrySet = map.entrySet();
        for (Map.Entry<Integer,Student> entry:entrySet
             ) {
    
    
            System.out.println(entry.getValue());
        }
    }
}

理解HashMap的源码

  • 在JDK1.7及以前,HashMap底层就是一个table数组+链表实现的哈希表存储结构。
    在这里插入图片描述
  • 链表的每一个节点就是一个Entry,其中包括键key、值value、键的哈希码hash、执行下一个节点的引用next四部分。
static class Entry<K, V> implements Map.Entry<K, V> {
    
    
    final K key; //key
    V value;//value
    Entry<K, V> next; //指向下一个节点的指针
    int hash;//哈希码
}
  • JDK1.7中HashMap的主要成员变量及其含义:
public class HashMap<K, V> implements Map<K, V> {
    
    
//哈希表主数组的默认长度
    static final int DEFAULT_INITIAL_CAPACITY = 16; 
//默认的装填因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f; 
//主数组的引用!!!!
    transient Entry<K, V>[] table; 
    int threshold;//界限值  阈值
    final float loadFactor;//装填因子
    public HashMap() {
    
    
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    public HashMap(int initialCapacity, float loadFactor) {
    
    
        this.loadFactor = loadFactor;//0.75
        threshold = (int) Math.min(capacity * loadFactor, 
MAXIMUM_CAPACITY + 1);//16*0.75=12
        table = new Entry[capacity];
      ....
    }
}
  • 调用put方法添加键值对。哈希表三步添加数据原理的具体实现;是计算key的哈希码,和value无关。特别注意:
    1.第一步计算哈希码时,不仅调用了key的hashCode(),还进行了更复杂处理,目的是尽量保证不同的key尽量得到不同的哈希码。
    2.第二步根据哈希码计算存储位置时,使用了位运算提高效率。
    3.第三步添加Entry时添加到链表的第一个位置,而不是链表末尾。
    4.第三步添加Entry是发现了相同的key已经存在,就使用新的value替代旧的value,并且返回旧的value。
public class HashMap {
    
    
    public V put(K key, V value) {
    
    
       //如果key是null,特殊处理
        if (key == null)
            return putForNullKey(value);
        //1.计算key的哈希码hash 
        int hash = hash(key);
        //2.将哈希码代入函数,计算出存储位置  y= x%16;
        int i = indexFor(hash, table.length);
        //如果已经存在链表,判断是否存在该key,需要用到equals()
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    
    
            Object k;
            //如找到了,使用新value覆盖旧的value,返回旧value
    if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    
     
                V oldValue = e.value;// the United States
                e.value = value;//America
                e.recordAccess(this);
                return oldValue;
            }
        }
        //添加一个结点
        addEntry(hash, key, value, i);
        return null;
    }
final int hash(Object k) {
    
    
    int h = 0;
    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
    
    
//作用就相当于y = x%16,采用了位运算,效率更高
    return h & (length-1);
 }
}
  • 调用get方法根据key获取value。
    1.哈希表三步查询数据原理的具体实现。
    2.其实是根据key找Entry,再从Entry中获取value即可。
public V get(Object key) {
    
    
    //根据key找到Entry(Entry中有key和value)
    Entry<K,V> entry = getEntry(key);
    //如果entry== null,返回null,否则返回value
    return null == entry ? null : entry.getValue();
}
  • 添加元素时达到了阈值,需要扩容,每次扩为原来主数组容量的2倍。
void addEntry(int hash, K key, V value, int bucketIndex) {
    
    
    //如果达到了门槛值,就扩容,容量为原来容量的2位 16---32
    if ((size >= threshold) && (null != table[bucketIndex])) {
    
    
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    //添加节点
    createEntry(hash, key, value, bucketIndex);
}
  • 在JDK1.8中有了一些变化,当链表的存储的数据个数大于等于8的时候,不再采用链表存储,而采用了红黑树存储结构。
  • 这么做主要是查询的时间复杂度上,链表为O(n),而红黑树一直是O(logn)。如果冲突多,并且超过8,采用红黑树来提高效率。
    在这里插入图片描述

理解TreeMap的源码

  • 基本特征:二叉树、二叉查找树、二叉平衡树、红黑树。
    在这里插入图片描述
  • 每个节点的结构
    在这里插入图片描述
static final class Entry<K,V> implements Map.Entry<K,V> {
    
    
    K key;
    V value;
    Entry<K,V> left;
    Entry<K,V> right;
    Entry<K,V> parent;
    boolean color = BLACK;
}
  • TreeMap主要成员变量及其含义
public class TreeMap<K, V> implements NavigableMap<K, V> {
    
    
    private final Comparator<? super K> comparator;//外部比较器
    private transient Entry<K, V> root = null; //红黑树根节点的引用
    private transient int size = 0;//红黑树中节点的个数
    public TreeMap() {
    
    
        comparator = null;//没有指定外部比较器
    }
    public TreeMap(Comparator<? super K> comparator) {
    
    
        this.comparator = comparator;//指定外部比较器
    }
}
  • 添加原理
    1.从根节点开始比较 。
    2.添加过程就是构造二叉平衡树的过程,会自动平衡 。
    3.平衡离不开比较:外部比较器优先,然后是内部比较器。如果两个比较器都没有,就抛出异常。
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;
        return null;
    }
    //如果是添加非第一个节点,就这么处理
    int cmp;
    Entry<K,V> parent; 
    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
               //找到了对应的key,使用新的value覆盖旧的value                 
                return t.setValue(value);
        } while (t != null);
    }
    else {
    
    
        //如果外部比较器没有,就使用内部比较器
       ....
    }
    //找到了要添加的位置,创建一个新的节点,加入到树中
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)  
        parent.left = e;
    else
        parent.right = e;       
    size++;
    return null;
}
  • 查询原理基本同添加
     public V get(Object key) {
    
    
    //根据key(cn)找Entry(cn--China)
    Entry<K,V> p = getEntry(key);
    //如果Entry存在,返回value:China
    return (p==null ? null : p.value);
}

final Entry<K, V> getEntry(Object key) {
    
    
    //如果外部比较器存在,就使用外部比较器
    if (comparator != null)
        return getEntryUsingComparator(key);
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
    //如果外部比较器不存在,就使用内部比较器
    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
            //如果找到了,就返回Entry
            return p;
    }
    //如果没有找到,就返回null
    return null;
}

猜你喜欢

转载自blog.csdn.net/qq_40836864/article/details/114446331