java集合之Map接口及其实现类

一、Map集合

image-20220218192450901

  • 特点:存储一对数据(Key-Value),无序、无下标,键不可重复,值可重复。
  • 方法:
    • put(K key,V value)//将对象存入到集合中,关联键值。key重复则覆盖原值。
    • get(Object key)//根据键获取对应的值
    • keySet()//返回所有的key的集合
    • values()//返回包含所有值的Colletion集合
    • Map.Entry<K,V>//键值匹配的Set集合

二、Map接口的使用

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

/**
 * Map集合是键值对的集合,无顺序,无下标,键不能重复,值可以重复
 */
public class MapInterfacDemo {
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<String,String>();
        //添加数据
        map.put("CH","中国");
        map.put("FA","法国");
        map.put("USA","美国");
        map.put("USA","meiguo");
        System.out.println(map.size());
        System.out.println(map);
        //删除数据
//        map.remove("USA");//按键删除
//        map.remove("FA","法国");//按键值删除
//        System.out.println(map.size());
//        System.out.println(map);
        //遍历
        //方法一,通过keySet方法获取key的Set集合
        Set<String> set = map.keySet();//获取key的Set集合
        for (String s : set) {
            System.out.println(s+"----"+map.get(s));//get方法通过键获取值
        }
        //方法二,通过entrySet方法获取键值对的封装entry
        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry.getKey()+"---"+entry.getValue());
        }
        //判断
        System.out.println(map.containsKey("USA"));
        System.out.println(map.containsValue("meiguo"));
    }

}

三、hashMap的使用

  • JDK1.2后加入使用,线程不安全,适用于单线程,运行效率快,允许用null作为键或值
import java.util.HashMap;
import java.util.Map;
/**
 * Hashmap存储结构
 * 数组+链表+红黑树
 */
public class HashMapdemo {
    public static void main(String[] args) {
        HashMap<Student,String> hashMap=new HashMap();
        Student s1=new Student("刘备",001);
        Student s2=new Student("关羽",002);
        Student s3=new Student("张飞",003);
        //添加
        hashMap.put(s1,"逐县");
        hashMap.put(s2,"小沛");
        hashMap.put(s3,"巨鹿");
        //hashMap.put(s3,"xiaopei");//键相同,则会覆盖之前的值
        //如果不重写hashcode和equals方法,则这个地方默认会添加一个数据,如果重写hashcode和equals方法就会将s3和
        //new Student("张飞",003)视为同一个对象,此时,因为键相同,所以不会添加,而进行替换
        hashMap.put(new Student("张飞",003),"洛阳");
        System.out.println(hashMap.size());
        System.out.println(hashMap);
        //删除
        //hashMap.remove(s1);//通过键删除
//        hashMap.remove(s1,"逐县");//通过键值删除
//        System.out.println(hashMap.size());
//        System.out.println(hashMap);
        //遍历
        //1.keyset
        for (Student student : hashMap.keySet()) {
            System.out.println(student+"----"+hashMap.get(student));
        }
        //2.entrySet,效率高于keyset
        System.out.println("=============entrySet==============");
        for (Map.Entry<Student, String> studentStringEntry : hashMap.entrySet()) {
            System.out.println(studentStringEntry.getKey()+"---"+studentStringEntry.getValue());
        }
        //判断
        System.out.println(hashMap.containsKey(s1));
        System.out.println(hashMap.containsValue("小沛"));

    }
}


//Student类:
import java.util.Objects;
public class Student {
    private String name;
    private int stuNO;//学号
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getStuNO() {
        return stuNO;
    }
    public void setStuNO(int stuNO) {
        this.stuNO = stuNO;
    }
    public Student() {
    }
    public Student(String name, int stuNO) {
        this.name = name;
        this.stuNO = stuNO;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", stuNO=" + stuNO +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return stuNO == student.stuNO && Objects.equals(name, student.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, stuNO);
    }
}

四、hashMap源码分析

DEFAULT_INITIAL_CAPACITY = 1 << 4	//初始容量=1左移4位=16
MAXIMUM_CAPACITY = 1 << 30			//最大容量=1左移30位=2的30次方
DEFAULT_LOAD_FACTOR = 0.75f			//扩容加载因子为0.75,如有100个数的话,进行扩容,会扩容100*0.75=75
TREEIFY_THRESHOLD = 8				//JDK1.8后HashMap引入红黑树,当链表(单向)的数据个数大于8个时,并且数组的长度大于64,将链表转换为红黑树,这样的目的是提升查找效率
UNTREEIFY_THRESHOLD = 6				//红黑树的数据个数小于6,则转换为链表的存储方式
MIN_TREEIFY_CAPACITY = 64			//进行树化的最小数组长度
Node<K,V>							//链表结节
table								//数组
size								//hashmap的数据长度
  • 重点:(针对数组的扩容)
    • 默认加载因子为0.75,当size大于0.75*当前容量时(默认为16),进行扩容,此时临界值变为之前的2倍(2乘12),容量变为之前的两倍(2乘以16)
//默认无参构造方法
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // loadFactor是加载因子 
}
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子为0.75
//put方法,调用putVal
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; //创建一个Node数组
    	Node<K,V> p; //创建一个Node
    	int n, i;
    	//当没有调用put方法时,table=null,size=0
    	//将table赋值给tab,如果为空,后面短路
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;//调用resize方法,将返回数组的长度赋值给n
        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)//如果在put的过程中,size达到一个临界值,第一次添加变为12
            resize();
        afterNodeInsertion(evict);
        return null;
}
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//若table为null则返回0
        int oldThr = threshold;//threshold默认也是0
        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每次扩容增加2倍
                     oldCap >= DEFAULT_INITIAL_CAPACITY)//DEFAULT_INITIAL_CAPACITY为16
                newThr = oldThr << 1; // 临界值每次扩容是之前的2倍
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;//DEFAULT_INITIAL_CAPACITY=16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//等于默认加载因子0.75*默认容量16=12
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;//newThr为新的临界值
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//创建一个容量为16的Node数组
        table = newTab;//将这个数组赋值给table
        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;//返回值
}

五、HashMap源码分析总结

1、HashMap刚创建时,table是null,为了节省空间;当添加第一个元素时,table容量调整为16
2、当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的2倍。目的是减少调整元素的个数。
3、jdk1.8后:当每个链表长度大于8,并且数组元素个数大于等于64时,会调整为红黑树,目的是提高执行效率。
4、jdk1.8后:当红黑树长度小于6时,调整为链表。
5、jdk1.8以前链表是头插入,jdk1.8之后链表是尾插入。

六、HashSet源码分析

image-20220221152319424

  • hashSet底层使用的就是hashMap,只用了hashmap中的key

image-20220221152528385

  • add方法是调用的HashMap中的put方法,只使用key。

七、HashTable和Propeties

  • Hashtable
    jkd1.0版本,线程安全,运行效率低,现在用的不多。
    Hashtable是同步的。
    不允许null作为key或value。
    无参构造方法的默认初始容量(11)、负载因子(0.75)。
  • Properties
    Hashtable的子类,要求key或value都是String。
    通常用于配置文件的读取。
    常和IO流联系在一起。

八、TreeMap

  • 实现了SortedMap接口(Map的子接口),可以对key自动排序。

  • put时调用compare方法来进行比较,如果没有比较器,则用Comparable接口实现类的compareTo来进行比较image-20220222163537101

  • //例子
    import java.util.Map;
    import java.util.TreeMap;
    public class TreeMapDemo {
        public static void main(String[] args) {
            TreeMap<Person,String> treeMap=new TreeMap<>();
            //添加
            Person p1=new Person("韩信",001);
            Person p2=new Person("项羽",002);
            Person p3=new Person("钟离昧",003);
            treeMap.put(p1,"汉中");//ClassCastException,需要实现Comparable接口并重写CompareTo方法,如果不重写Compare方法,会报以上错误,因为put方法调用后的数据排列规则没有定义
            treeMap.put(p2,"巨鹿");
            treeMap.put(p3,"汉水");
            //treeMap.put(new Person("钟离昧",003),"hanshui");//重写CompareTo方法后,如此添加会失败,会对住址进行覆盖,因为CompareTo的方法比较的是Person的ID
            System.out.println(treeMap.size());
            System.out.println(treeMap);
            //删除
            //treeMap.remove(p3);//与下面的remove效果相同
    //        treeMap.remove(new Person("钟离昧",003));
    //        System.out.println(treeMap.size());
    //        System.out.println(treeMap);
            //遍历
            //1.KeySet
            System.out.println("------------KeySet-------------");
            for (Person person : treeMap.keySet()) {
                System.out.println(person+"-----"+treeMap.get(person));
            }
            //2.EntrySet,效率比KeySet高,因为KeySet的treeMap.get(person)还会进行一次遍历
            System.out.println("------------EntrySet-------------");
            for (Map.Entry<Person,String> entry : treeMap.entrySet()) {
                System.out.println(entry.getKey()+"---"+entry.getValue());
            }
            //判断
            System.out.println(treeMap.containsKey(p1));
            System.out.println(treeMap.containsKey(new Person("钟离昧", 003)));
            System.out.println(treeMap.containsValue("hanshui"));
            System.out.println(treeMap.containsValue("汉水"));
        }
    }
    
    //Person类:
    public class Person implements Comparable{
        private String name;
        private int ID;
        public Person() {
        }
        public Person(String name, int ID) {
            this.name = name;
            this.ID = ID;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getID() {
            return ID;
        }
        public void setID(int ID) {
            this.ID = ID;
        }
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", ID=" + ID +
                    '}';
        }
        @Override
        public int compareTo(Object o) {
            //根据ID的不同来进行比较,以此来进行排序
            int n1=this.getID()-((Person)o).getID();
            return n1;
        }
    }
    
  • 如果Person类不实现Comparable接口,也可以通过匿名内部类为TreeMap指定比较器来实现比较规则的制定

    TreeMap<Person,String> treeMap=new TreeMap<>(new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            int i = o1.getID() - o2.getID();
            return i;
        }
    });
    
  • TreeSet和TreeMap的关系

    • TreeSet的构造方法默认是创建一个TreeMap集合。image-20220222163746221

    • add方法则是调用的TreeMap的put方法,只使用了Key。image-20220222163903774

九、Collections工具类

import java.util.*;

public class CollectionsDemo {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList=new ArrayList<Integer>();
        arrayList.add(20);
        arrayList.add(22);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(100);
        System.out.println(arrayList.size());
        System.out.println(arrayList);
        //自定义比较规则
        Collections.sort(arrayList, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        System.out.println(arrayList);
        //sort,默认为正序
        Collections.sort(arrayList);
        System.out.println(arrayList);
        //binarySearch,必须正序排序后才可使用
        System.out.println(Collections.binarySearch(arrayList, 2));
        //copy,需要目标数组的size大于等于源数组的大小,否则会报数组越界异常IndexOutOfBoundsException,需要通过赋值操作才能改变size的值,仅仅指定
        //初始容量是没用的(容量指定与否不影响,因为会自动扩容)
        ArrayList<Integer> arrayList1 = new ArrayList<>(1);
        for (int i = 0; i < arrayList.size()+1; i++) {
            arrayList1.add(null);//[2, 3, 20, 22, 100, null]
        }
        Collections.copy(arrayList1,arrayList);//第一个参数为目标数组,后一个参数为源数组
        System.out.println(arrayList1);
        //shuffle打乱(洗牌)
        Collections.shuffle(arrayList);
        System.out.println(arrayList);
        //reserve反转
        Collections.reverse(arrayList);
        System.out.println(arrayList);
        System.out.println("===========list转数组无参数toArray==========");
        //补充:1.list转数组
        //1.1.无参数toArray
        Object[] Objects = arrayList.toArray();
        for (Object object : Objects) {
            System.out.println(object);
        }
        System.out.println("==========有参数toArray==========");
        //1.2.有参数toArray
        Integer[] integers = arrayList.toArray(new Integer[0]);//只要容量不超过arraylist的容量就行,超过元素为null(默认值)
        for (Integer integer : integers) {
            System.out.println(integer);
        }
        System.out.println("==========数组转list==========");
        //数组转list,如此得到的集合不能添加删除操作,因为数组的大小是固定的
        Integer []integers1={1,2,3,5,6,41};
        List<Integer> asList = Arrays.asList(integers1);//注意是Arrays工具类的方法
        //asList.add(1);//抛出异常UnsupportedOperationException
        //asList.remove(1);//抛出异常UnsupportedOperationException
        System.out.println(asList);
        //基本类型数组转集合
        int[] nums={1,2,5,3,65};
        List<int[]> ints = Arrays.asList(nums);//这个方法返回的是int[]类型,这样的话List集合里只有一个int数组类型的元素
        for (int[] anInt : ints) {
            System.out.println(anInt);//因为是数组,所以打印出的是地址:[I@2503dbd3
        }
        //要使用基本类型的包装类
        Integer[] nums2={1,2,5,3,65};
        List<Integer> ints1 = Arrays.asList(nums2);
        for (Integer anInt1 : ints1) {
            System.out.println(anInt1);
        }
    }
}

十、集合总结

  • 集合的概念:
    • 对象的容器,和数组类似,定义了对多个对象操作的常用方法。
  • List集合:
    • 有序、有下标、元素可以重复。(ArrayList、LinkedList、Vector)
  • Set集合:
    • 无序、无下标、元素不可重复。(HashSet、TreeSet)
  • Map集合:
    • 存储一对数据,无序、无下标,键不可重复,值可重复。(HashMap、HashTable、TreeMap)
  • Colletions
    • 集合工具类,定义了除了存取以外的集合常用方法。
posted @ 2022-02-25 10:40  是韩信啊  阅读(107)  评论(0编辑  收藏  举报