• java基础 集合(3) Map接口、Collections工具类、集合总结


    目录

    一、Map接口

    1.1 、Map接口的常用方法

    1.2 、Map接口6种遍历方式

    3 HashMap的底层源码

    4、Hashtable(t是小写)

    5、 Properties

    6、TreeSet类(TreeMap等同)

    7、开发中如何选择集合实现类

    二、Collections工具类

    排序操作 (均为static)

    查找、替换

    面试题: HashSet和TreeSet的去重机制

    面试题2

    集合总结


    一、Map接口

    1. Map与Collection并列存在的。用于保存具有映射关系的数据 key-value

    2. Map中的key和value可以是任何引用数据类型,会封装到HahsMap$Node对象中

    3. Map中的key不允许重复

    4. Map中的value可以重复

    5. Map的key可以为null value也可以为null,注意key为null,只能有一个;value为null,可以有多个

    6. 常用String类作为Map的key

    7. key和value之间存在单向一对一关系,即通过指定的key总能找到对应value

    8. Map存放数据的 一对k-v 是放在一个Node中的,又因为Node类实现了Entry接口,所以也可说是k-v就是一个Entry

      为了方便遍历Map的实现类都有EntrySet、KeySet 、Values三个内部类

      (他们三个其实不存放数据,真正存放数据的地方还是在数组中,只不过他们是使用了迭代器,实现了指向数组table中key value )

    1. public class MapSource {
    2.    public static void main(String[]args){
    3.        Map map = new HashMap<>();
    4.        map.put("no1","张三");
    5.        map.put("no2","李四");
    6.        /*
    7.          1.k-v 最后是通过 newNode(hash, key, value, null); 存放在Node中的
    8.          2.为了方便程序员的遍历,还会在底层创建EntrySet集合,
    9.            该集合存放元素的类型是Entry,而一个Entry对象就包含了key和value
    10.             transient Set> entrySet;
    11.          3.entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HahsMap$Node
    12.            这是因为Node实现了Map.Entry
    13.            static class Node implements Map.Entry
    14.           4.这样当把 HahsMap$Node 存放在entrySet方便我们的遍历
    15.           因为Map.Entry提供了两个很重要的方法
    16.           getKey() 和   getValue()
    17.         */
    18.        //1、包含key和value的 Entry接口 其实放的是Node类
    19.        //Set>
    20.        Set set = map.entrySet();
    21.        //HashMap$EntrySet
    22.        System.out.println(set.getClass());
    23.        for (Object o : set) {
    24.            Map.Entry entry = (Map.Entry)o;
    25.            System.out.print("key:"+entry.getKey());
    26.            System.out.println(" - value:"+entry.getValue());
    27.       }
    28.        //2、包含key的set集合
    29.        Set set1 = map.keySet();
    30.        //3、包含value的Collecion即可
    31.        Collection values = map.values();
    32.   }
    33. }

    1.1 、Map接口的常用方法

    1. put 添加

    2. remove 根据键值删除映射关系

    3. get 根据键获取值

    4. size 获取元素个数

    5. isEmpty 判断个数是否为0

    6. clear清除

    7. containsKey查找键是否存在

    1.2 、Map接口6种遍历方式

    1. public class HashMapFor {
    2.    public static void main(String[]args){
    3.        Map map = new HashMap();
    4.        map.put("1","张三");
    5.        map.put("2","李四");
    6.        map.put("3","王五");
    7.        map.put("4","黄六");
    8.        map.put(null,"黄六");
    9.        map.put("科技","信管");
    10.        //第一组:keySet 先取出所有的key,通过key求出对应的value
    11.        Set set = map.keySet();
    12.        System.out.println("======1====");
    13.        //(1)增强for循环
    14.        for (Object o : set) {
    15.            //value
    16.            Object o1 = map.get(o);
    17.            System.out.println(o+"-"+o1);
    18.       }
    19.        //(2)迭代器
    20.        System.out.println("======2====");
    21.        Iterator iterator = set.iterator();
    22.        while (iterator.hasNext()){
    23.            //key
    24.            Object next = iterator.next();
    25.            //value
    26.            Object o = map.get(next);
    27.            System.out.println(next+"-"+o);
    28.       }
    29.       //第二组 取出values
    30.        Collection values = map.values();
    31.        //(3)增强for
    32.        System.out.println("======3====");
    33.        for (Object value : values) {
    34.            System.out.println(value);
    35.       }
    36.        //(4)迭代器
    37.        System.out.println("======4====");
    38.        Iterator iterator1 = values.iterator();
    39.        while (iterator1.hasNext()){
    40.            System.out.println(iterator1.next());
    41.       }
    42.        //第三组 使用entrySet
    43.        //(5)增强for
    44.        System.out.println("======5====");
    45.        Set set1 = map.entrySet();
    46.        for (Object o : set1) {
    47.            Map.Entry entry =(Map.Entry)o;
    48.            Object key = entry.getKey();
    49.            Object value = entry.getValue();
    50.            System.out.println(key+"-"+value);
    51.       }
    52.        //(6)迭代器
    53.        System.out.println("======6====");
    54.        Iterator iterator2 = set1.iterator();
    55.        while (iterator2.hasNext()) {
    56.            Map.Entry entry =(Map.Entry) iterator2.next();
    57.            Object key = entry.getKey();
    58.            Object value = entry.getValue();
    59.            System.out.println(key+"-"+value);
    60.       }
    61.   }
    62. }
     
    

    总结

    1. HashMap是Map接口中使用频率最高的实现类

    2. HashMap是以k-v键值对的方式来存储数据

    3. key不能重复,但是值可以重复,允许使用null键和null值

    4. 如果添加相同的key,则会覆盖原来的k-val 等同于修改 (key不会替换,vlaue会进行替换,并讲旧得值返回)

       

      if执行完毕后直接执行下面的语句  

           

       5.与HashSet一样,不保证映射的顺序,因为底层是以Hash表的方式存储的

       6.HashMap没有实现同步,因此是线程不安全的

    3 HashMap的底层源码

    1. HashMap底层维护了Node类型的数组table 默认为null

    2. 当创建对象的时候,将加载因子(loadfactor)初始化为0.75

    3. 当添加key-val的时候,通过key的哈希值得到table的索引。然后判断该索引处是否有元素。如果没有元素直接添加。如果该索引处有元素,进行比较,如果equals相等就进行替换value返回旧的值,如果不相等需要判断是否是树结构还是链表结构,如做出响应的处理。如果发现容量超过阈值就进行扩容

    4. 第一次添加 需要扩容为16 临界值(threshold)为12

    5. 以后在扩容,则扩容table容量的二倍,临界值也是原来的二倍

    6. 在jdk8 如果一条链表元素个数大于等于默认8个,并且table数组的大小大于等于64就会转换为红黑树,如果数组长度小于就进行扩容

    4、Hashtable(t是小写)

    1. 存放的元素是键值对

    2. Hashtable的键和值都不能是null

    3. Hashtable使用方法基本与HashMap是一样的

    4. Hashtable是线程安全的,HashMap是线程不安全的

    1. @SuppressWarnings("all")  
    2. public class HashTable_ {
    3.    public static void main(String[]args){
    4.        Map map = new Hashtable();
    5.        map.put("jack",100); //ok
    6.      // map.put(null,100);// 空指针异常
    7.      // map.put("jack",null);//空指针异常
    8.        map.put("tom",22);//ok
    9.        map.put("lic",33);//ok
    10.        map.put("lic",48);//替换
    11.        System.out.println(map);
    12.   /*
    13.     1.底层有数组 Hashtable$Entry[] 初始就是11
    14.         public Hashtable() {
    15.            this(11, 0.75f);
    16.        }
    17.           public Hashtable(int initialCapacity, float loadFactor) {
    18.            this.loadFactor = loadFactor; //0.75
    19.            table = new Entry[initialCapacity];
    20.            threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    21.        }
    22.     2. 临界值   threshold为 11*0.75 = 8 容量为11
    23.     3. value不能为null 否则抛出空指针异常
    24.         key也不能为null 因为调用了key.hashCode()   否则抛出空指针异常
    25.          public synchronized V put(K key, V value) {
    26.            // Make sure the value is not null
    27.            if (value == null) {
    28.                throw new NullPointerException();
    29.            }
    30.            // Makes sure the key is not already in the hashtable.
    31.            Entry tab[] = table;
    32.            int hash = key.hashCode();
    33.            int index = (hash & 0x7FFFFFFF) % tab.length;
    34.            @SuppressWarnings("unchecked")
    35.            Entry entry = (Entry)tab[index];
    36.            for(; entry != null ; entry = entry.next) {
    37.                if ((entry.hash == hash) && entry.key.equals(key)) {
    38.                    V old = entry.value;
    39.                    entry.value = value; //进行替换的代码
    40.                    return old;
    41.                }
    42.            }
    43.            addEntry(hash, key, value, index);
    44.            return null;
    45.         }
    46.       //3.扩容 if (count >= threshold) 就调用 rehash();
    47.          扩容长度原来二倍+1     //(int)临界值为新的容量*0.75
    48.            private void addEntry(int hash, K key, V value, int index) {
    49.            modCount++;
    50.            Entry tab[] = table;
    51.            //如果超过 threshold就进行扩容
    52.            if (count >= threshold) {
    53.                // Rehash the table if the threshold is exceeded
    54.                rehash();
    55.                tab = table;
    56.                hash = key.hashCode();
    57.                index = (hash & 0x7FFFFFFF) % tab.length;
    58.            }
    59.            // Creates the new entry.
    60.            @SuppressWarnings("unchecked")
    61.            Entry e = (Entry) tab[index];
    62.            tab[index] = new Entry<>(hash, key, value, e);
    63.            count++;
    64.       }
    65.        protected void rehash() {
    66.        int oldCapacity = table.length;
    67.        Entry[] oldMap = table;
    68.        // overflow-conscious code
    69.        //扩容原来二倍+1
    70.        int newCapacity = (oldCapacity << 1) + 1;
    71.         //临界值为新的容量*0.75
    72.        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    73.    */
    74.   }
    75. }

    5、 Properties

    1. Properties类继承自Hashtable类并且实现Map接口,也是键值对的形式来保存数据

    2. 它的使用特点和Hashtable类似

    3. Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改

    4. 工作后xxx.properties文件通常作为配置文件

    1. public class Properties_ {
    2.    public static void main(String[]args){
    3.        /*
    4.          1. Properties 继承Hashtable
    5.          2.可以通过k-v 存放数据,当然key和value不能为null
    6.         */
    7.        Properties pro = new Properties();
    8.        pro.put("jack",100);
    9.        // pro.put(null,100);// 空指针异常
    10.        // pro.put("jack",null);//空指针异常
    11.        pro.put("tom",22);//ok
    12.        pro.put("lic",33);//ok
    13.        pro.put("lic",48);//替换
    14.        pro.put("mm","ww");//替换
    15.        System.out.println(pro);
    16.        //通过key,获取对应的值
    17.        System.out.println(pro.get("jack"));
    18.        //删除 remove
    19.        pro.remove("jack");
    20.        System.out.println(pro);
    21.        //注意getProperty 得到如果不是String 就返回null
    22.        //只有是String才能得到结果
    23.        /*
    24.          public String getProperty(String key) {
    25.            Object oval = super.get(key);
    26.       String sval = (oval instanceof String) ? (String)oval : null;
    27.       return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
    28.   }
    29.        */
    30.        System.out.println(pro.getProperty("tom"));//null
    31.        System.out.println(pro.getProperty("mm"));//ww
    32.   }
    33. }
     
    

    6、TreeSet类(TreeMap等同)

    是有序的 底层是红黑树(又名自平衡二叉查找树)

    1. package com.sofwin.controller;
    2. import java.util.Comparator;
    3. import java.util.Set;
    4. import java.util.TreeSet;
    5. /**
    6. * @packageName: com.sofwin.controller
    7. * @author: wentao
    8. * @date: 2022/11/1 22:27
    9. * @version: 1.0
    10. * @email 1660420659@qq.com
    11. * @description: TreeSet
    12. */
    13. public class TreeSet_ {
    14.    public static void main(String[]args){
    15.        //当我们使用无参构造器的时候 默认是升序
    16.        //如果想要自定义比较的 使用TreeSet提供的构造器,可以传入一个比较器(匿名内部类)
    17.        //从大到小
    18.      // Set set = new TreeSet();
    19.        Set set = new TreeSet(new Comparator() {
    20.            @Override
    21.            public int compare(Object o1, Object o2) {
    22.                //调用String的compareTo方法进行比较
    23.                return ((String)o2).compareTo(((String)o1));
    24.           }
    25.       });
    26.        set.add("jack");
    27.        set.add("tom");
    28.        set.add("sp");
    29.        set.add("a");
    30. //       set.add(1);
    31. //       set.add(10);
    32. //       set.add(-1);
    33. //       set.add(3);
    34.        System.out.println(set);
    35.        /*
    36.        1.构造器底层 会将写的比较器对象 给到TreeMap的一个属性comparator
    37.             public TreeSet(Comparator comparator) {
    38.                this(new TreeMap<>(comparator));
    39.            }
    40.            public TreeMap(Comparator comparator) {
    41.                this.comparator = comparator;
    42.            }
    43.         2. 在调用TreeSet的add方法的时候 底层会执行
    44.            //cpr就是我们匿名内部类
    45.             Comparator cpr = comparator;
    46.            if (cpr != null) {
    47.                do {
    48.                    parent = t;
    49.                    // 动态绑定我们匿名内部类中写的compare方法
    50.                    cmp = cpr.compare(key, t.key);
    51.                    if (cmp < 0)
    52.                        t = t.left;
    53.                    else if (cmp > 0)
    54.                        t = t.right;
    55.                    else //如果=0 即相等就设置value 也就是加入不了了(替换)
    56.                        return t.setValue(value);
    57.                } while (t != null);
    58.            }
    59.         */
    60.   }
    61. }

    7、开发中如何选择集合实现类

    1. 先判断存储的类型 是一个对象,还是是一对键值对

    2. 一组对象 Collection接口

      • 允许重复 List

        • 增删多:LinkedList 【底层维护了一个双向链表】

        • 改查多:ArrayList 【底层维护了Object类型的可变数组】

      • 不允许重复 Set

        • 无序 HashSet 【底层HashMap 维护了一个哈希表 数组+链表+红黑树】

        • 排序 TreeSet

        • 插入和取出顺序一致 LinkedHashSet 数组+双向链表+红黑树

    3. 一组键值对 Map接口

      • 键无序:HashMap 【底层是哈希表 jdk7 数组+链表 jdk8 数组+链表+红黑树】

      • 键排序 TreeMap

      • 键插入和取出顺序一致 LinkedHashMap

      • 读取文件 Properties

    二、Collections工具类

    1. Collections是一个操作Set、List、Map等集合的工具类

    2. Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

    排序操作 (均为static)

    1. reverse: 反转List中元素的顺序

    2. shuffle :对List集合元素进行随机排序

    3. sort :根据元素的自然排序对指定的List集合按照升序排序

    4. sort(List,Comparator)根据指定的Comparator比较器,进行对List集合的排序

    5. swap (List,i,j) 将指定List集合中i处元素和j处元素进行交换

    1. public class Collections_ {
    2.    public static void main(String[]args){
    3.        //创建ArrayList用于测试
    4.            List list = new ArrayList();
    5.            list.add("to");
    6.            list.add("jack");
    7.            list.add("swich");
    8.            list.add("swicz");
    9.            list.add("mki");
    10.            System.out.println("反转前元素:"+list);
    11. //       1. reverse: 反转List中元素的顺序
    12.            Collections.reverse(list);
    13.            System.out.println("反转后元素:"+list);
    14. //       2. shuffle :对List集合元素进行随机排序 每次调用都会进行打乱
    15.           /* Collections.shuffle(list);
    16.            System.out.println("第一次打乱过后元素:"+list);
    17.            Collections.shuffle(list);
    18.            System.out.println("第二次打乱过后元素:"+list);*/
    19. //       3. sort :根据元素的自然排序对指定的List集合按照升序排序
    20.            Collections.sort(list);
    21.            System.out.println("自然排序后"+list);
    22. //       4. sort(List,Comparator)根据指定的Comparator比较器,进行对List集合的排序
    23.            //希望按照字符串长度进行排序
    24.            Collections.sort(list, new Comparator() {
    25.                @Override
    26.                public int compare(Object o1, Object o2) {
    27.                    return ((String)o1).length()-((String)o2).length();
    28.               }
    29.           });
    30.             System.out.println("自定义排序规则:"+list);
    31. //       5. swap (List,i,j) 将指定List集合中i处元素和j处元素进行交换
    32.            Collections.swap(list,0,2);  //越界会报错
    33.            System.out.println("将第一个元素和第三个元素交换后:"+list);
    34.   }
    35. }

    查找、替换

    1. max(Colleciton) 根据元素的自然顺序,返回给定集合中最大的元素

    2. max (Collection,Compartor) 根据比较器规则,返回给定集合中的最大元素

    3. min 跟最大相同

    4. int frequency(Collection,Object) 返回指定集合中指定元素的出现次数

    5. copy(List dest ,List src) 将src中内容赋值到dest中

    6. replaceAll(List,Object oldVal,Object newVal) 使用新值替换集合中的旧值

    1. //创建ArrayList用于测试
    2.            List list = new ArrayList();
    3.            list.add("to");
    4.            list.add("jack");
    5.            list.add("swich");
    6.            list.add("swicz");
    7.            list.add("mki");
    8.            System.out.println("反转前元素:"+list);
    9. //       1. max(Colleciton) 根据元素的自然顺序,返回给定集合中最大的元素
    10.        Comparable max = Collections.max(list);
    11.        System.out.println("自然顺序的最大元素"+max);
    12. //       2. max (Collection,Compartor) 根据比较器规则,返回给定集合中的最大元素
    13.        Object max1 = Collections.max(list, new Comparator() {
    14.            @Override
    15.            public int compare(Object o1, Object o2) {
    16.                return ((String) o1).length() - ((String) o2).length();
    17.           }
    18.       });
    19.        System.out.println("长度最大的元素:"+max1);
    20. //       3. min 跟max相同
    21. //       4. int frequency(Collection,Object) 返回指定集合中指定元素的出现次数
    22.        int to = Collections.frequency(list, "to");
    23.        System.out.println("to出现的次数"+to);
    24.        list.add("to");
    25.        int to2 = Collections.frequency(list, "to");
    26.        System.out.println("to出现的次数"+to2);
    27. //       5. copy(List dest ,List src) 将src中内容赋值到dest中
    28.        ArrayList dest = new ArrayList<>();
    29.        System.out.println("拷贝前dest元素"+dest);
    30.        //会抛出 IndexOutOfBoundsException
    31.        /* 如果源数组list集合的长度 大于填充数组的长度dest就抛出异常
    32.            int srcSize = src.size();
    33.            if (srcSize > dest.size())
    34.                throw new IndexOutOfBoundsException("Source does not fit in dest");
    35.         */
    36.        //因此为了完成拷贝,我们要先给dest进行赋值
    37.        for (int i =0; i
    38.            dest.add(null);
    39.       }
    40.        Collections.copy(dest,list);
    41.        System.out.println("拷贝后desc元素"+dest);
    42. //       6. replaceAll(List,Object oldVal,Object newVal) 使用新值替换集合中的旧值
    43.        System.out.println("替换前:"+list);
    44.        Collections.replaceAll(list,"to","王五");
    45.        System.out.println("替换后"+list);

    面试题: HashSet和TreeSet的去重机制

    1.HashSet的底层是HashMap 数组+链表+红黑树 set的元素存储在key中,value是set的常量PRESENT 
    通过hashCode+equals,底层先通过存入对象进行运算,调用hashCode得到hash值,通过hash值计算出在数组table中索引位置,如果发现该索引位置没有元素就加入,如果发现该索引位置存在元素了或者有链表了,就逐个通过equals进行比较,如果都不相同就加入到尾部(尾插法) 如果过程中有一个相等就不加入,只是将value进行替换,但是value都一样也就相当于不改变
    
    2.TreeSet的底层是TreeMap  是数组+红黑树,如果在TreeSet的时候传入了Compartor匿名对象,就使用实现了compare方法去重,如果该方法返回0就进行value值的替换,也就是去重。如果创建TreeSet的时候没有传入Compartor匿名对象,就使用元素本身实现Compareable接口的compareTo方法去重

    面试题2

    1. package com.sofwin.controller;
    2. import java.util.TreeSet;
    3. /**
    4. * @author: wentao
    5. * @date: 2022/11/2 13:53
    6. * @version: 1.0
    7. */
    8. public class Work1 {
    9.    public static void main(String[]args){
    10.        TreeSet set = new TreeSet();
    11.        set.add(new Person());  //此处会出现异常 ClassCastException 类型转换异常
    12.        //原因: 当调用无参构造器的时候,没有传入Compartor匿名内部类
    13.        //TreeSet底层会使用数据转换成Comparable 使用它的compareTo方法
    14.        // put中的这一行 Comparable k = (Comparable) key;
    15.        //但是由于Person没有实现Comparable接口 因此会抛出类型准换异常
    16.   }
    17. }
    18. class  Person {
    19. }

    集合总结

    List集合底层结构线程安全扩容机制
    ArrayList可变数组线程不安全无参: 第一次扩容容量为10 第二次按照1.5倍扩容
    有参: 容量是指定大小 扩容为1.5倍
    Vector可变数组线程安全无参: 默认10 每次扩容2倍
    注意:Vector创建对象的时候就进行初始化数组有参: 容量是指定大小 每次扩容2倍
    LinkedList双向链表线程不安全

    Set集合底层结构线程安全扩容机制阈值
    HashSetHashMap 数组+链表+红黑树线程不安全有参: 第一次 容量按照指定大小的最接近2^n次有参:第一次 容量的0.75倍
    有参:第二次 按照原来的二倍有参:第二次 按照原来的二倍
    无参: 第一次默认是16无参:阈值是 容量的0.75倍 12
    无参: 第二次 按照原来的二倍无参: 第二次 按照原来的二倍
    LinkedHashSet(HashSet的子类)LinkedHashMap 数组+双向链表+红黑树线程不安全的跟HashSet一样跟HashSet一样
    TreeSetTreeMap 红黑树线程不安全的

    Map集合底层结构线程安全扩容机制阈值
    HashMap数组+链表+红黑树线程不安全有参: 第一次 容量按照指定大小的最接近2^n次有参:第一次 容量的0.75倍
    有参:第二次 按照原来的二倍有参:第二次 按照原来的二倍
    无参: 第一次默认是16无参:阈值是 容量的0.75倍 12
    无参: 第二次 按照原来的二倍无参: 第二次 按照原来的二倍
    LinkedHashMap(HashMap的子类)数组+双向链表+红黑树线程不安全的跟HashSet一样跟HashSet一样
    Hashtable (t小写)数组+双向链表+红黑树线程安全的有参: 直接是指定的大小有参: //(int)临界值为新的容量*0.75
    注意:Hashtable创建对象的时候就进行初始化数组无参: 扩容长度原来二倍+1无参: //(int)临界值为新的容量*0.75
    TreeSetTreeMap 红黑树线程不安全的

      

  • 相关阅读:
    Spring Boot面试题(总结最全面的面试题!!!)
    京东小程序:无代码开发实现API集成,连接电商平台、CRM和客服系统
    Spring 02: 基于xml的Spring接管下的三层项目架构
    C# 继承
    Arcgis小技巧【14】——拓扑(Topology)的方方面面
    Stable Diffuse 之 安装文件夹、以及操作界面 UI 、Prompt相关说明
    LINUX | hexdump以16进制查看文件内容
    数据结构专题 | 先序非递归遍历二叉树
    《治安疏》——海瑞
    MySQL 最朴素的监控方式
  • 原文地址:https://blog.csdn.net/weixin_52574640/article/details/127652654