Java学习16(MAP)

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java学习16(MAP)相关的知识,希望对你有一定的参考价值。

Java学习16

就是不怎么想看那个课题,还是看看老杜吧哈哈

泛型(Generic)

JDK5.0之后推出的新特性:泛型  <>

用泛型来指定集合中存储的数据类型
用了泛型之后,集合中的元素类型就更统一了

泛型这种机制,只在程序编译阶段起作用,只是给编译器参考的(运行阶段意义不大)

泛型的好处:
    第一:集合中存储的元素类型统一了
    第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”
泛型的缺点:
    导致集合中存储的元素缺乏多样性!
    在实际业务中,一般一个集合中都是一种类型,所以泛型被认可

JDK8之后引入了:自动类型推断机制(又称位钻石表达式)
List<Animal> myList = new ArrayList<>();
//后面的泛型<>里面可以空着,里面的类型会自动推断,被称为钻石表达式(JDK8之后才允许)

也可以自定义泛型:<E>中的E是一个标识符,随便写,但是一般写E或者T,E是element单词首字母,T是type首字母

foreach 增强for循环

JDK5.0之后的新特性
语法:
    for(元素类型 变量名:数组或集合){     
        System.out.println(变量名);    
    }                                             //变量名代表的就是数组/集合中的每一个元素

foreach有一个缺点,没有下标,在需要使用下标时,不建议使用foreach

Set集合

HashSet集合无序不可重复
TreeSet无序不可重复,但会自动排序

Map集合

1.与Collection没有继承关系
2.以key和Value的方式存储数据:键值对
    key和value都是引用数据类型
    key和value存储的都是内存地址
    key起主导地位,value是key的一个附属品
3.Map接口中常用方法
    void clear()   清空Map
    boolean containsKey(Object key)   判断Map中是否包含某个key
    boolean containsValue(Object value)   判断Map中是否包含某个value
    V get(Object key)    通过key获取value
    Set<K> keySet()      获取Map集合中所有的key,所有的键是一个Set集合
    boolean isEmpty()    判断集合中的元素个数是否为0
    V put(K key, V value)  向Map集合中添加键值对
    V remove(Object key)   通过key删除键值对
    int size()      获取Map集合中键值对的个数
    Collection<V> values()      获取Map集合中所有的value,返回一个Collection
    
    Set<Map.Entry<K,V>> entrySet()   将Map集合转换成Set集合,Set集合中元素的类型是Map.Entry<K,V>,Map.Entry是一个Map中静态内部类
    例如:map1集合对象:
    key             value
    1               zhangsan
    2               lisi
    3               wangwu
    
    Set set = map1.entrySet();
    set集合对象形式如下:
    1=zhangsan
    2=lisi

在这里插入图片描述

Map集合的遍历

第一种方式:先获取所有的key,通过遍历key,来遍历value
第二种方式:把map集合转换成Set集合,再遍历Set集合
    第二种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值,这种方式比较适合大数据量
		//遍历map集合
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"张三");
        map.put(2,"李四");
        map.put(3,"王五");
		//第一种,根据key遍历value
        Set<Integer> keySet = map.keySet();
        Iterator<Integer> it = keySet.iterator();
        while(it.hasNext()){
            Integer key = it.next();
            String value = map.get(key);
            System.out.println(key+"="+value);
        }
        //增强for循环,foreach
        for(Integer key2 : keySet){
            System.out.println(map.get(key2));
        }
        System.out.println("===========================");
        //第二种,转换成Set集合
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
        while(it2.hasNext()) {
            Map.Entry<Integer, String> node = it2.next();
            //也可行
            //System.out.println(node);
            Integer key3 = node.getKey();
            String value3 = node.getValue();
            System.out.println(key3 + "=" + value3);
        }
输出:
1=张三
2=李四
3=王五
张三
李四
王五
===========================
1=张三
2=李四
3=王五

HashMap集合:

1.HashMap集合底层是哈希表/散列表数据结构
2.哈希表是一个怎样的数据结构?
    哈希表是一个数组和单向链表的结合体
    哈希表将两种数据结构融合在一起,充分发挥各自的优点
3.HashMap底层实际上是一个数组(一维数组),一维数组的每一个元素是一个单向链表
    有一个静态的内部类HashMap.Node
    static class Node<K,V> {    
        final int hash;     // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。)    
        final K key; // 存储到Map集合中的那个key    
        V value; // 存储到Map集合中的那个value    
        Node<K,V> next; // 下一个节点的内存地址。
哈希表/散列表数据结构

在这里插入图片描述

1.map.put(k,v)实现原理:
    第一步:先将k,v封装到Node对象中
    第二步:底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组下标,
    下标位置上如果没有任何元素,就把Node加到这个位置上;
    如果下标对应的位置上有链表,此时会拿着k和链表上的每一个节点的k进行equals,如果所有的equals方法都返回false,那么这个新节点将被添加到链表的末尾;如果其中有一个equals返回的是true,那么这个节点的value将会被覆盖
2.v=map.get(k)实现原理:
    底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组下标,
    下标位置上如果没有任何元素,返回null;
    如果这个位置上有单向链表,那会拿着k和单向链表上的每个节点的k进行equals,如果所有equals方法返回false,那么get方法返回null;
    只要其中有一个equals方法返回ture,那么此时这个节点的value就是我们要找的value,get方法最终返回这个value
    
3.为什么哈希表的随机增删,以及查询效率都高?
    增删是在链表上完成
    查询也不需要都扫描,只需要部分扫描
HashMap集合的key,会先后调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都要重写

4.HashMap集合key的特点:
无序不可重复
为什么无序?因为不一定会挂到哪个单向链表上
不可重复?equals方法来保证HashMap集合的key不可重复,如果重复了就覆盖了

5.放在HashMap集合key部分的元素其实就是放到HashSet集合中了。所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。

6.同一个单向链表上所有节点的hash相同(也可以不同,存在哈希碰撞),因为他们的数组下标是一样的
但同一个链表上的k和k的equals方法返回都是false,都不相等

7.哈希表HashMap使用不当时无法发挥性能!    
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了    
纯单向链表。这种情况我们成为:散列分布不均匀。    
什么是散列分布均匀?        
    假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。    
假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?        
    不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。        
    也是散列分布不均匀。散列分布均匀需要你重写hashCode()方法时有一定的技巧。
    
8.重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
9.HashMap集合的默认初始化容量为16,加载因子是0.75,扩容2倍
    这个默认的加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容,扩容之后的容量是原容量的2倍

    重点,记住:HashMap集合初始化容量必须是2的次幂,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
    
10. 在JDK8之后,如果哈希表单向链表中的元素超过8个,单向链表这种数据结构会变成红黑树这种数据结构;红黑树上的元素少于6个,会变回单向链表(这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围,提高效率)

重写hashCode()和equals()方法

1.向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!
equals方法有可能调用,也有可能不调用。    
拿put(k,v)举例,什么时候equals不会调用?        
    k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。    
拿get(k)举例,什么时候equals不会调用?
    k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
    
2.注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。
并且equals方法返回如果是true,hashCode()方法返回的值必须一样。    
    equals方法返回true表示两个对象相同,在同一个单向链表上比较。    
    那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
    所以hashCode()方法的返回值也应该相同。
    
3.终极结论:放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。

4.对于哈希表数据结构来说:
    如果o1和o2的hash值相同,一定是放到同一个单向链表上。    
    当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
    
5.HashMap集合key部分允许null吗?    
    允许    
    但是要注意:HashMap集合的key null值只能有一个。(有可能面试的时候遇到这样的问题)
Hashtable
6.Hashtable的key可以为null吗?    
    Hashtable的key和value都是不能为null的。    
    HashMap集合的key和value都是可以为null的。
7.Hashtable方法都带有synchronized:线程安全的。
线程安全有其它的方案,这个Hashtable对线程的处理导致效率较低,使用较少了。
Hashtable和HashMap一样,底层都是哈希表数据结构。
Hashtable的初始化容量是11,默认加载因子是:0.75f
Hashtable的扩容是:原容量 * 2 + 1

Properties

目前只需要掌握Properties属性类对象的相关方法即可。
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。     
Properties被称为属性类对象。
Properties是线程安全的。

掌握两个方法:存和取
存:setProperty   (调用map的put方法)
取:getProperty  通过键获得值

TreeSet

1、TreeSet集合底层实际上是一个TreeMap
2、TreeMap集合底层是一个二叉树。
3、放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。
称为:可排序集合。

对自定义的类型来说,TreeSet可以排序吗?    
    在没有指定排序规则之前,无法排序,谁大谁小没有说明
运行时会出现java.lang.ClassCastException: Person cannot be cast to class java.lang.Comparable

出现这个异常的原因是:
    自定义的类(Person)没有实现java.lang.Comparable接口
    
因此,放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
并且实现compareTo方法。equals可以不写。
需要在compareTo方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较

compareTo方法的返回值很重要:    
    返回0表示相同,value会覆盖。    
    返回>0,会继续在右子树上找。
    返回<0,会继续在左子树上找。
    
最终的结论:放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:        
    第一种:放在集合中的元素实现java.lang.Comparable接口。        
    第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象Comparator。
    (传入比较器可以改变比较规则)
Comparable和Comparator怎么选择呢?    
    当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。    
    如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。        
Comparator接口的设计符合OCP原则。

自平衡二叉树

1.TreeSet/TreeMap是自平衡二叉树,遵循左小右大原则存放
2.遍历二叉树的时候有三种方式:
    前序遍历:根左右
    中序遍历:左根右
    后序遍历:左右根
    //前中后说的是根的位置
3.TreeSet/TreeMap集合采用的是:中序遍历方式
    Iterator迭代器采用的是中序遍历方式

集合工具类Collections

1.将list集合变成线程安全的:Collections.synchronizedList(list);
2.排序:Collections.sort(list);
    对List集合中元素排序,需要保证List集合中元素实现了Comparable接口
    或者采用这种方法:Collections.sort(list集合, 比较器对象);

以上是关于Java学习16(MAP)的主要内容,如果未能解决你的问题,请参考以下文章

Java学习16(MAP)

elasticsearch代码片段,及工具类SearchEsUtil.java

java SpringRetry学习的代码片段

学号:201521123116 《java程序设计》第八周学习总结

Java 8 新特性总结

源码阅读(16):Java中主要的Map结构——HashMap容器(上)