Day23.Map集合、HashMap、Properties、TreeMap|Comparable

目录

Map集合

常用方法

遍历Map集合

HasMap(重点)

LinkedHashMap

TreeMap---->SortedMap、TreeSet

TreeSet、TreeMap实现自定义对象的排序

传入Comparable接口

编写Comparator(比较器)

 Properties 属性集 (了解)

自动平衡二叉树数据结构


Map集合

在创建一个Set集合时,底层实际上创建了一个Map集合。向Set集合内添加元素,实际上添加到Map集合的key部分。

常用方法

default voidreplaceAll(BiFunction<? super K,? super V,? extends V> function)

将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。

 Vput(K key, V value) 返回值为旧的 value
          向Map集合中添加键值对。
 Vget(Object key)
          通过key获取value。
 Vremove(Object key)
          通过key删除键值对。
 voidputAll(Map<? extends K,? extends V> m)
          从指定映射中将所有映射关系复制到此映射中(可选操作)。
 booleancontainsKey(Object key)
          判断Map中是否包含某个key。
 booleancontainsValue(Object value)
          判断Map中是否包含某个value。
 Set<K>

keySet()
          返回此映射中包含的键的 Set 视图。

                                        (HashSet集合的元素存储在HashMap中的key部分)

 Collection<V>values()        //value可重复 
          获取Map集合中所有的value,返回一个collection集合
Set<Map.Entry<K,V>>entrySet()
          将Map集合转换成Set集合
voidclear()
          从此映射中移除所有映射关系。 
 intsize()
          返回此映射中的键-值映射关系数。
 booleanisEmpty()
          判断集合元素个数是否为0。
  • 代码测试  
        //创建Map集合对象
        Map<Integer,String> map = new HashMap<>();

        //向Map集合添加键值对
        map.put(1,"张三");
        map.put(2,"李四");
        map.put(3,"王五");

        //通过key获取value
        System.out.println(map.get(2));     //李四

        //获取键值对数量
        System.out.println("键值对数量:"+map.size());    //键值对数量:3

        //获取所有values
        Collection<String> values = map.values();
        for(String s : values){
            System.out.println(s);  //张三 李四 王五
        }

        //通过key删除value
        map.remove(2);
        System.out.println(map.get(2));     //null
        System.out.println("键值对数量:"+map.size());    //键值对数量:2

        //判断是否包含某个key、value
        // (contains方法底层调用的是equals,所以自定义类型需要重写equals方法)
        System.out.println(map.containsKey(2));     //false
        System.out.println(map.containsValue("李四"));    //false

        //清空map集合
        map.clear();
        //判断是否为空
        System.out.println(map.isEmpty());  //true

遍历Map集合

  • 方式1:获取key的set集合,通过遍历获取value(效率较低)
        // 第一种方式:获取所有的key,通过遍历key,通过key获取value
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"张三");
        map.put(2,"王五");
        map.put(3,"赵六");
        //先获取所有的key,所有的key是一个set集合
        Set<Integer> keys = map.keySet();
        //遍历key,通过key获取value
        //迭代器 或 foreach
        Iterator<Integer> it = keys.iterator();
        while(it.hasNext()){
            Integer key = it.next();
            String value = map.get(key);
            System.out.println(value);
        }
  • 方式2:通过entrySet()转换为Set集合,取出每个node(节点)的key、value值。(效率较高)
        // 第二种方式:Set<Map.Entry<K,V>>  entrySet() 效率较高
        //以上方法是将Map集合直接全部转换成Set集合
        //Set集合中的元素类型是:Map.Entry
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        //遍历Set集合,每一次取出一个Node,获取key value
        //(1)迭代器的方式遍历
        Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
        while(it2.hasNext()){
            Map.Entry<Integer,String> node =  it2.next();
            Integer key = node.getKey();
            String value = node.getValue();
            System.out.println(key + "=" + value);  //1=张三2=王五3=赵六
        }
        //(2)foreach方式遍历
        for(Map.Entry<Integer,String> node : set){
            System.out.println(node.getKey() + "=" + node.getValue());
        }

HasMap(重点)

链表的时间复杂度是On,红黑树是Logn

  1. HashMap集合底层是哈希表(散列表)的数据结构,无序不可重复。

  2. 哈希表是一个数组和单向列表的结合体

    • 数组:在查询方面效率很高随机增删效率较低

    • 单向链表:在随机增删方面效率很高在查询方面效率很低

    • 哈希表将以上的两种数据融合在一起,充分发挥他们各自的优点。

  3. 通过构造器初始化对象时,初始容量是多少?

    • 空参构造器:开始未初始化底层数组,在添加第一个元素时,默认初始化容量为16。

    • 有参构造器:指定初始化容量,但未初始化数组,在第一次添加元素时,初始化容量为比指定容量大的 最小的2的次幂。

  4. 哈希算法:哈希表添加元素时,如何得到元素要添加的位置?

    • 计算key的哈希值 -- 做到不同的元素哈希值尽量不同。

    • 二次哈希 : 将哈希值的高低16位进行异或运算。 -- 减少哈希冲突。

    • 二次哈希的值再与容量进行取模运算。 (如容量为16, n%16 = n&15与运算)。

  5. 链表长度达到 8、容量达到64时 转为红黑树,以进一步提升查询效率(很少出现)
    当红黑树节点减少到 6 时退化回链表。

  6. 关于HashMap集合的扩容: 何时扩容?如何扩容?

    • 第一次添加元素时默认初始化容量是16默认加载因子是0,75(数组容量达到75%时扩容),扩容为原容量的2倍,这是为了重新分布效率。(0.75,时间与空间折中,泊松分布   <0.75浪费空间  >0.75冲突增加,浪费时间)。

    • 扩容后重新计算哈希值,均衡分布。(重新分布后原红黑树链表节点可能不足6,退化回链表)。重新分布后其位置保持不动 或 换为当前位置的二倍。

  7. JDK8: 引入红黑树,链表的头插法改为尾插法。

  8. HashMap集合底层源代码:

    publi class HashMap{
        Node<K,V>[] table;          //HashMap底层实际上就是一个一维数组
        static class Node<K,V>{     //静态的内部类HashMap.Node
            final int hash; //哈希值(哈希值是key的hashCode()方法的执行结果,哈希值通过算法可以转换为数组下标)
            final K ker;    //存储到Map集合中的key元素
            V value;        //存储到Map集合中的value元素
            Node<K,V> next; //下一个节点的内存地址。}}
    
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默认初始容量
    
    static final int MAXIMUM_CAPACITY = 1 << 30;       //最大容量
    
    static final float DEFAULT_LOAD_FACTOR = 0.75f;    //默认加载因子
    
    static final int TREEIFY_THRESHOLD = 8;            //链表转红黑树临界值
    
    static final int UNTREEIFY_THRESHOLD = 6;          //红黑树退化链表临界值
    //链表转为红黑树的另一个条件
    static final int MIN_TREEIFY_CAPACITY = 64;        //链表转红黑树链表容量
    //--------------------------------------------
    transient Node<K,V>[] table;     //底层的数组 Node节点
    
    transient Set<Map.Entry<K,V>> entrySet;
    
    transient int size;              //元素个数
    
    transient int modCount;          //修改次数
    
    int threshold;             //扩容阈值 容量*加载因子
    
    final float loadFactor;    //实际加载因子
    
    
    
    
  • 哈希表(散列表)数据结构

哈希表(散列表):一维数组,数组中的每一个元素是一个单向链表。(数组和链表的结合体)  

 HashMap集合中ker部分特点:无序、不可重复

        为什么无序?        因为不知道会挂到哪个单向链表上。(哈希值取余长度得出结论)

        为什么不可重复? queals方法保证HashMap集合的key不可重复,

                                      如果key重复了,value会被覆盖 

重点:放在HashMap集合Key部分HashSet集合中的元素需要同时重写hashCode和equals方法

为什么要重写重写hashCode和equals方法?

HashMap使用不当时,将无法发挥其性能!

      假设所有的hashCode()方法返回值都设定为相同的值会导致底层哈希表变成了单向链表

      假设将所有的hashCode()方法返回值都设定为不同的值会导致底层变成一维数组

      以上情况我们称为散列分布不均匀。散列分布均匀:假设有100个元素,10个单向链表,每个链表上有10个节点,这便是散列分布均匀的。需要重写hashCode()方法时有一定技巧。

  • 如何重写hashCode() 效率更高?
    @Override    //IDEA提供的方法 (默认版)
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;    //(质数冲突概率及低,31底层效率高,)
        return result;
    }

//让hashCode值与元素的内容相关
//相同内容的元素哈希值一定不同
//不同内容的哈希值尽量不同
  • HashMap 无序不可重复,遍历出来却变的有序?

HashMap的Key部分如果存储是为Integer类型,其hashCode便是其本身,那么每一个桶中将只有一个元素,结构会变成一个一维数组,因此取出时会变得有序。

Integer.hashCode 源码: 

Returns a hash code for this Integer.
Returns:  a hash code value for this object, equal to the primitive int value represented by this Integer object.

返回此整数的哈希码。  

返回:   该对象的哈希码值,等于由这个Integer对象表示的原始int值。

  • 常用方法(JDK1.8新增)
Vcompute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)

将BIFunction的结果值赋予key关联的映射值(如果没有当前映射为 null)。

VcomputeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)

如果指定的键尚未与值相关联(或映射到 null ),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非 null

voidforEach(BiConsumer<? super K,? super V> action)

对此映射中的每个条目执行给定的操作,直到所有条目都被处理或操作引发异常。

Vmerge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)

如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联。

voidputAll(Map<? extends K,? extends V> m)

将指定地图的所有映射复制到此地图。

LinkedHashMap

  1. 特点:kay 有序不可重复
  2. 底层结构:哈希表基础上维护一个链表,用于维护元素的迭代顺序
  3. HashMap 的子类 效率略低于 HashMap

TreeMap---->SortedMap、TreeSet

  1. TreeMap集合底层是一个 二叉树   (相对平衡)

  2. 可排序集合:TreeSet集合中的元素无序不可重复,但是可以按照元素的大小自动排序

  3. TreeSet 对引用类型排序,引用类型需要自定义类实现Comparable接口,重写compareTo方法。或在参数传入Comparator比较器,自定义比较方法
    Comparable 与 Comparator同时存在,底层优先调用 Comparator

  4. 放到TreeSet集合的元素,等同于放到TreeMap集合Key部分了HashMap的使用和HashSet的自定义对象比较方法是一样的,这里不再赘述。(HashMap对key部分排序。)

TreeMap()
          使用键的自然顺序构造一个新的、空的树映射。
TreeMap(Comparator<? super K> comparator)
          构造一个新的、空的树映射,该映射根据给定比较器进行排序。
TreeSet()
          构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
TreeSet(Comparator<? super E> comparator)
          构造一个新的空 TreeSet,它根据指定比较器进行排序。
        //创建一个TreeSet集合
        TreeSet<String> ts = new TreeSet<>();
        //添加String
        ts.add("zhangsan");
        ts.add("lisi");
        ts.add("wangwu");
        //遍历
        for (String s : ts){
            System.out.println(s); //lisi wangwu zhangsan
        }

        TreeSet<Integer> ts2 = new TreeSet<>();
        ts2.add(300);
        ts2.add(200);
        ts2.add(100);
        for (Integer s : ts2){
            System.out.println(s); //100 200 300
        }

TreeSet、TreeMap实现自定义对象的排序

放到TreeSet集合或TreeMap集合key部分的元素要想进行排序,可使用两种方式

  1. 放在集合中的元素实现java.lang.Comparable接口

  2. 在构造TreeSet或者TreeMap集合时传入一个比较器(Comparator)对象

Comparable和Comparator如何选择?

        当比较规则不会发生改变 比较规则只有一个时,建议使用Comparable接口

        当比较规则有多个,并且需要多个比较规则频繁切换,建议Comparator接口

Comparator符合OCP原则

注:Comparable是java.lang包下的,Comparator是java.util包下的

传入Comparable接口

  • compareTo(Object o)方法是java.lang.Comparable接口中的方法,当需要对某个类的对象进行排序时该类需要实现Comparable接口,并且重写public int compareTo(T o)方法

  • compareTo方法的返回值很重要,返回0表示相同,value会覆盖;返回>0,会继续在又子数上找;返回<0,会继续在左子树上找。

  • 自定义类型实现Comparable接口,重写compareTo方法范例

    class Customer implements Comparable<Customer>{
        int age;
        public Customer(int age){
            this.age = age;
        }
        //需要在这个方法中编写比较的逻辑、规则,按什么进行比较?
        //k.compareTo(t.key)
        //用参数k和集合中每一个k进行比较,返回值可能是>0 <0 =0
        @Override
        public int compareTo(Customer c) { // c1.compareTo(c2)
            return this.age - c.age;    //age - this.age则为降序
            //c1和c2比较时,就是this和c比较
        }
        //重写toString
        public String toString(){
            return"Customer[age="+age+"]";
        }
    }
    
    public class TreeSetTest01 {
        public static void main(String[] args) {
            Customer c1 = new Customer(32);
            Customer c2 = new Customer(20);
            Customer c3 = new Customer(30);
            //创建TreeSet集合
            TreeSet<Customer> cus = new TreeSet<>();
            //添加元素
            cus.add(c1);      //Customer cannot be cast to Comparable
            cus.add(c2);
            cus.add(c3);
            //遍历
            for(Customer c : cus){
                System.out.println(c);      //成功升序排序
            }
        }
    }
    • 例二
    //先按照年龄升序,如果年龄一样再按照姓名升序。
    class Vip implements Comparable<Vip>{
        String name;
        int age;
        
        @Override  //写排序规则,按照什么排序
        public int compareTo(Vip v) {
            if(this.age == v.age){
                //年龄不同按照姓名排序  姓名是String类型,调用compareTo方法比较
                return this.name.compareTo(v.name);
            }else{
                //年龄相同按照年龄排序
                return this.age - v.age;
            }
        }
        //构造器
        public Vip(String name, int age) {
            this.name = name;
            this.age = age;
        }
        //重写toString
        @Override
        public String toString() {
            return "Vip{" + "name='" + name + '\'' + ", age=" + age + '}';
        }
    }
            Vip v1 = new Vip("zhangSan",30);
            Vip v2 = new Vip("zhangSan",25);
            TreeSet<Vip> vip = new TreeSet<>();
            vip.add(v1);
            vip.add(v2);
            for(Vip v : vip){
                System.out.println(v);      //成功升序排序
            }

编写Comparator(比较器)

  • TreeSet集合中元素可排序的第二种方式:编写一个比较器,传入TreeSet集合构造方法

  • 注:Comparable是java.lang包下的,Comparator是java.util包下的

//乌龟  第一种方式:impements Comparable 实现接口重写compareTo();
class WuGui {
    int  age;
    public WuGui(int age){
        this.age = age;
    }
    @Override
    public String toString() {
        return "小乌龟[" + "age=" + age + ']';
    }
}
//单独编写一个比较器
//比较器实现java.tuil.Comparator接口(Comparable是java.lang包下的,Comparator是java.util包下的)
class WuGuiComparator implements Comparator<WuGui>{
    @Override
    public int compare(WuGui o1, WuGui o2) {
        //指定比较规则,按照年龄排序
        return o1.age - o2.age;
    }
}
    public static void main(String[] args) {
        //创建TreeSet集合的时候,需要使用这个比较器
        //TreeSet<WuGui> wugui = new TreeSet<>(); 这样没有通过构造方法传递比较器
                                         //↓给构造方法传递一个比较器↓
        TreeSet<WuGui> wugui = new TreeSet<>(new WuGuiComparator());
        WuGui g1 = new WuGui(300);
        WuGui g2 = new WuGui(500);
        WuGui g3 = new WuGui(100);
        wugui.add(g1);
        wugui.add(g2);
        wugui.add(g3);
        for(WuGui wg :wugui){ 
            System.out.println(wg);          //成功排序
        }
        //getMap集合 key部分排序
        TreeMap<WuGui,Integer> wgMap = new TreeMap(new WuGuiComparator());
        wgMap.put(g1,300);
        wgMap.put(g2,500);
        wgMap.put(g3,100);
        //遍历map数组
        Set<Map.Entry<WuGui,Integer>> s = wgMap.entrySet();
        for(Map.Entry<WuGui,Integer> n: s){
            System.out.println(n.getKey() ); //成功排序
        }
    }
  •  使用匿名内部类:
        //匿名内部类的方式,直接new接口,省略创建WuGuiComparator类的步骤
        TreeSet<WuGui> wugui = new TreeSet<>(new Comparator<WuGui>() {
            @Override
            public int compare(WuGui o1, WuGui o2) {
                return o1.age - o2.age;
            }
        });

 Properties 属性集 (了解)

详情请看:Properties通常用于编写配置文件

Properties类的用法总结_源码复兴号的博客-CSDN博客_properties

  • Properties 表示了一个持久的属性集,可保存在流中或从流中加载Properties的key和value都是String类型并不允许为null

  • Properties是一个Map集合,继承 Hashtable。

  • Properties被称为属性类对象,主要用来存储配置信息。

  • Properties是线程安全的

  • 常用方法

 ObjectsetProperty(String key, String value)
          调用 Hashtable 的方法 put。                存
 StringgetProperty(String key)
          用指定的键在此属性列表中搜索属性。 取
 voidstore(OutputStream out, String comments)
          以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。
 voidstore(Writer writer, String comments)
          以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。
 voidload(InputStream inStream)
          从输入流中读取属性列表(键和元素对)。
 voidload(Reader reader)
          按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
 Set<String>stringPropertyNames()
          返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。
 Enumeration<?>propertyNames()
          返回属性列表中所有键的枚举,如果在主属性列表中未找到同名的键,则包括默认属性列表中不同的键。
        //创建一个Properties集合
        Properties pro = new Properties();
        //添加元素
        pro.setProperty("username","code");
        pro.setProperty("password","123abc");
        //根据key获取value
        System.out.println(pro.getProperty("username"));    //code
        System.out.println(pro.getProperty("password"));    //123abc
  •  System 系统类方法
static StringgetProperty(String key)
          获取指定键指示的系统属性。
static PropertiesgetProperties()  
          确定当前的系统属性。
        //获取属性集
        Properties prop = System.getProperties();
        //获取keySet,遍历
        Set<Object> keySet = prop.keySet();
        for (Object o : keySet) {
            System.out.println(prop.get(o));
        }

自动平衡二叉树数据结构

底层结构:TreeSet 里面维护了一个TreeMap,都是基于红黑树实现的

红黑树是一种相对平衡的二叉树,查询效率高于链表。(平衡二叉树追求绝对平衡,左右子树差值不能超过1)

二叉树的遍历方式有:

* 前序遍历:中-左-右
* 中序遍历:左-中-右
* 后序遍历:左-右-中

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值