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 )

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

1.1 、Map接口的常用方法

  1. put 添加

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

  3. get 根据键获取值

  4. size 获取元素个数

  5. isEmpty 判断个数是否为0

  6. clear清除

  7. containsKey查找键是否存在

1.2 、Map接口6种遍历方式

public class HashMapFor {
    public static void main(String[]args){
        Map map = new HashMap();
        map.put("1","张三");
        map.put("2","李四");
        map.put("3","王五");
        map.put("4","黄六");
        map.put(null,"黄六");
        map.put("科技","信管");
​
        //第一组:keySet 先取出所有的key,通过key求出对应的value
        Set set = map.keySet();
        System.out.println("======1====");
        //(1)增强for循环
        for (Object o : set) {
            //value
            Object o1 = map.get(o);
            System.out.println(o+"-"+o1);
        }
​
        //(2)迭代器
        System.out.println("======2====");
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            //key
            Object next = iterator.next();
            //value
            Object o = map.get(next);
            System.out.println(next+"-"+o);
        }
       //第二组  取出values
        Collection values = map.values();
        //(3)增强for
        System.out.println("======3====");
        for (Object value : values) {
            System.out.println(value);
        }
        //(4)迭代器
        System.out.println("======4====");
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()){
            System.out.println(iterator1.next());
        }
​
        //第三组 使用entrySet
        //(5)增强for
        System.out.println("======5====");
        Set set1 = map.entrySet();
        for (Object o : set1) {
            Map.Entry entry =(Map.Entry)o;
            Object key = entry.getKey();
            Object value = entry.getValue();
            System.out.println(key+"-"+value);
        }
        //(6)迭代器
        System.out.println("======6====");
        Iterator iterator2 = set1.iterator();
        while (iterator2.hasNext()) {
            Map.Entry entry =(Map.Entry) iterator2.next();
            Object key = entry.getKey();
            Object value = entry.getValue();
            System.out.println(key+"-"+value);
        }
​
    }
}
 

总结

  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是线程不安全的

@SuppressWarnings("all")  
public class HashTable_ {
    public static void main(String[]args){
        Map map = new Hashtable();
        map.put("jack",100); //ok
      //  map.put(null,100);// 空指针异常
      //  map.put("jack",null);//空指针异常
        map.put("tom",22);//ok
        map.put("lic",33);//ok
        map.put("lic",48);//替换
        System.out.println(map);
   /*
     1.底层有数组 Hashtable$Entry[] 初始就是11
         public Hashtable() {
            this(11, 0.75f);
        }
​
           public Hashtable(int initialCapacity, float loadFactor) {
            this.loadFactor = loadFactor;  //0.75
            table = new Entry<?,?>[initialCapacity];
            threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        }
     2. 临界值   threshold为 11*0.75 = 8  容量为11
     3. value不能为null 否则抛出空指针异常
         key也不能为null  因为调用了key.hashCode()   否则抛出空指针异常
          public synchronized V put(K key, V value) {
            // Make sure the value is not null
            if (value == null) {
                throw new NullPointerException();
            }
​
            // Makes sure the key is not already in the hashtable.
            Entry<?,?> tab[] = table;
            int hash = key.hashCode();
            int index = (hash & 0x7FFFFFFF) % tab.length;
            @SuppressWarnings("unchecked")
            Entry<K,V> entry = (Entry<K,V>)tab[index];
            for(; entry != null ; entry = entry.next) {
                if ((entry.hash == hash) && entry.key.equals(key)) {
                    V old = entry.value;
                    entry.value = value; //进行替换的代码
                    return old;
                }
            }
​
            addEntry(hash, key, value, index);
            return null;
         }
​
       //3.扩容 if (count >= threshold) 就调用 rehash();
          扩容长度原来二倍+1     //(int)临界值为新的容量*0.75 
            private void addEntry(int hash, K key, V value, int index) {
            modCount++;
​
            Entry<?,?> tab[] = table;
            //如果超过 threshold就进行扩容
            if (count >= threshold) {
                // Rehash the table if the threshold is exceeded
                rehash();
​
                tab = table;
                hash = key.hashCode();
                index = (hash & 0x7FFFFFFF) % tab.length;
            }
​
            // Creates the new entry.
            @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>) tab[index];
            tab[index] = new Entry<>(hash, key, value, e);
            count++;
       }
​
        protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;
​
        // overflow-conscious code
        //扩容原来二倍+1
        int newCapacity = (oldCapacity << 1) + 1;
         //临界值为新的容量*0.75
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    */
    }
}

5、 Properties

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

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

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

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

public class Properties_ {
    public static void main(String[]args){
        /*
          1. Properties 继承Hashtable
          2.可以通过k-v 存放数据,当然key和value不能为null
​
         */
        Properties pro = new Properties();
        pro.put("jack",100);
        //  pro.put(null,100);// 空指针异常
        //  pro.put("jack",null);//空指针异常
        pro.put("tom",22);//ok
        pro.put("lic",33);//ok
        pro.put("lic",48);//替换
        pro.put("mm","ww");//替换
        System.out.println(pro);
​
        //通过key,获取对应的值
        System.out.println(pro.get("jack"));
​
        //删除 remove
        pro.remove("jack");
        System.out.println(pro);
        //注意getProperty 得到如果不是String 就返回null
        //只有是String才能得到结果
        /*
          public String getProperty(String key) {
            Object oval = super.get(key);
            String sval = (oval instanceof String) ? (String)oval : null;
            return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) :             sval;
            }
        */
        System.out.println(pro.getProperty("tom"));//null
        System.out.println(pro.getProperty("mm"));//ww
    }
}
 

6、TreeSet类(TreeMap等同)

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

package com.sofwin.controller;
​
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
​
/**
 * @packageName: com.sofwin.controller
 * @author: wentao
 * @date: 2022/11/1 22:27
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: TreeSet
 */
public class TreeSet_ {
    public static void main(String[]args){
        //当我们使用无参构造器的时候  默认是升序
        //如果想要自定义比较的 使用TreeSet提供的构造器,可以传入一个比较器(匿名内部类)
        //从大到小
​
      //  Set set = new TreeSet();
        Set set = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //调用String的compareTo方法进行比较
                return ((String)o2).compareTo(((String)o1));
            }
        });
        set.add("jack");
        set.add("tom");
        set.add("sp");
        set.add("a");
//        set.add(1);
//        set.add(10);
//        set.add(-1);
//        set.add(3);
        System.out.println(set);
        /*
        1.构造器底层 会将写的比较器对象  给到TreeMap的一个属性comparator
             public TreeSet(Comparator<? super E> comparator) {
                this(new TreeMap<>(comparator));
            }
​
            public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
​
         2. 在调用TreeSet的add方法的时候 底层会执行
            //cpr就是我们匿名内部类
             Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                do {
                    parent = t;
                    //  动态绑定我们匿名内部类中写的compare方法
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else //如果=0 即相等就设置value  也就是加入不了了(替换)
​
                        return t.setValue(value);
                } while (t != null);
            }
         */
    }
}
​

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处元素进行交换

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

查找、替换

  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) 使用新值替换集合中的旧值

 //创建ArrayList用于测试
            List list = new ArrayList();
            list.add("to");
            list.add("jack");
            list.add("swich");
            list.add("swicz");
            list.add("mki");
            System.out.println("反转前元素:"+list);
​
//        1. max(Colleciton)  根据元素的自然顺序,返回给定集合中最大的元素
        Comparable max = Collections.max(list);
        System.out.println("自然顺序的最大元素"+max);
//        2. max (Collection,Compartor) 根据比较器规则,返回给定集合中的最大元素
        Object max1 = Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String) o1).length() - ((String) o2).length();
            }
        });
        System.out.println("长度最大的元素:"+max1);
//        3. min 跟max相同
//        4. int  frequency(Collection,Object) 返回指定集合中指定元素的出现次数
        int to = Collections.frequency(list, "to");
        System.out.println("to出现的次数"+to);
        list.add("to");
        int to2 = Collections.frequency(list, "to");
        System.out.println("to出现的次数"+to2);
//        5. copy(List dest ,List src) 将src中内容赋值到dest中
        ArrayList dest = new ArrayList<>();
        System.out.println("拷贝前dest元素"+dest);
        //会抛出 IndexOutOfBoundsException
        /*  如果源数组list集合的长度  大于填充数组的长度dest就抛出异常
            int srcSize = src.size();
            if (srcSize > dest.size())
                throw new IndexOutOfBoundsException("Source does not fit in dest");
         */
        //因此为了完成拷贝,我们要先给dest进行赋值
        for (int i =0; i<list.size(); i++){
            dest.add(null);
        }
        Collections.copy(dest,list);
        System.out.println("拷贝后desc元素"+dest);
//        6. replaceAll(List,Object oldVal,Object newVal) 使用新值替换集合中的旧值
        System.out.println("替换前:"+list);
        Collections.replaceAll(list,"to","王五");
        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

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

集合总结

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 红黑树线程不安全的

  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值