今日学习Map和Set相关知识,HashMap与TreeMap的优缺点,HashSet与TreeSet的相关知识点

1.HashMap

  • Map的特点:里面存储的是两个元素,<K,V>即:K→Key(键);V→Value(值),成对出现,我们称之为键值对。两个元素是一对一的关系,即一个键对应一个值,而且要保证键的唯一性。Key的作用是识别该数据对的唯一性,Value的作用是保存数据内容

  • HashMap:HashMap是Map中常用的实现类,它的结构是可变数组和链表结合起来的特殊结构,可以理解为数组嵌套链表(可借鉴二维数组,即一维数组中的元素是个数组,组成的二维数组),这个是可变数组中的元素是单链表(只有下节点没有上节点)。在JDK1.8之前是这样,JDK1.8之后就是数组嵌套单链表或者红黑树的结构,在单链表长度为8或更长时,链表转换为红黑树的结构,在红黑树的层数小于6时,转换为链表结构。

  • 早版本中的一些问题:JDK1.8之前的版本中,使用HashMap存储数据,在高并发多线程的任务中会出现环链问题,这会使CPU占用率到达100%

  • 环链问题:怎样出现:
    多线程任务时,当新数据进来时,发现HashMap中数组使用率达到75%,此时需要扩充数组大小,数组大小扩充,会将原先未扩充的HashMap中的数据存入扩容后的HashMap中,链表中数据会倒序装入数组中
    第一线程:判断扩容,将原数据倒序装入扩容后的数组中,此时系统给线程的时间片到了,他要做的就是将数据装入数组中
    第二线程:判断扩容,将原数据倒序装入扩容后的数组中,线程二在系统给它的时间片内做完了将数据放入扩容后的数组。
    此时时间片轮换,到了第一线程,它会完成上次未完成的任务,将数据存入扩容后的数组中,因为线程二已经将数据存好了,数据以单链表形式存到数组中,所以会出现线程一将线程二已经存好的数据再次存入扩容后的数组;因为扩容后再存入数据是倒序,所以就会出现两个数据的next节点中存的是对方的地址。这样就形成一个环链
    如果此时要遍历寻找这个环链之后的数据,那么就会卡到环链中,一直循环,就会造成找不到数据,此时cpu就会在这里一直执行,使用率会达到100%
    若不明白可以看下:https://blog.csdn.net/qq_21993785/article/details/80384250 中大神写的,非常详细并且还有图解。

  • HashMap的存储方式:第一层数据结构是数组,比如HashMap中初始化一个长度为10的数组,当进行添加操作时,键值对会通过重写object类或者重写后的hashCode方法得到hash值,然后在将hash值与HashMap中数组长度做位运算,可以理解为将int型的hash值对数组长度再次哈希运算得到int的数据尽量均匀的存储到数组中,那么每个键值对都能针对当前数组计算出一个符合他自身的下标,这个操作保留了数组的快速寻址特性,因为int型压缩到数组下标会产生大量重复数据,那么将重复下标的数据以链表形式保存,HashMap的数据保存的就是节点,数组中保存的是单节点的对象,对象中有key和value属性,这样就保留了链表顶点删除快的优势。那么现在一个数组下标位置可以保存多个数据,存到链表中。因为key有唯一性,那么链表在存储数据时要验证数据是否存在过。这时调用object类或重写后的equals方法,判断两个对象是否相同,相同认为key保存过,将新的value替换,不相同就比较相同下标中的链表中是否有相同,当所有节点都访问完,并且没有相同的key时,把要插入的节点添加到当前链表末端,一个添加动作完成
    每次数组扩充,相同下标的链表元素顺序颠倒(头变尾,尾变头)

  • HashMap中常用的方法:
    HashMap在java.util包下,使用时记得引包

    HashMap hp=new HashMap();
    //1.增(添加数据)
    hp.put(1,"haha");//存入键值对<1,"haha">
    //2.删(删除数据)
    hp.remove(1,"haha");//删除指定键值对,删除成功返回true,否则返回false
    hp.remove(1);//删除指定的键所对应的键值对
    hp.clear();//清空HashMap中所有数据
    //3.改(修改数据)
    hp.put(1,"hhhhhaha");//存入键值对<1,"hhhhhaha">因为一个键对应一个值,所以这个方法也是修改方法
    hp.replace(1,"6666");//查询到有一致的key时,替换新的value将原先的value输出,若没查询到返回null
    hp.replace(1,"hhhhhaha","66666");//将新的value替换原先的value,成功返回true,否则返回false
    //4.查(查询数据)
    hp.size();//查询HashMap中存有多少数据
    hp.containsKey​(1);//如果查询到有key为1的键值对,则返回 true ,否则为false
    hp.containsValue​("6666") 如果查询到有value为字符串666的键值对,则返回 true  ,否则返回false
    hp.isEmpty();//查询HashMap是否为空
    hp.get(1);//查询key为1的键值对,有的话返回value值,没有返回null
  • 使用HashMap可能出现的问题:内存泄漏(存的进去取不出来)
    Java中没有内存泄漏,Java程序的数据不会写道系统分配给Java程序的内存区域外,因为Java程序没有指针概念,Java程序员无法控制数据在内存的保存位置,数据保存位置的控制即内存指针操作,是由Java虚拟机完成;但是存在另一种意义上的内存泄露,存储的数据无法被找到,而且不认为是垃圾,不会被垃圾回收机制清理,永远占用内存

    1. 第一种内存泄漏
      如果hashmap保存的key数据类型是自定义的类,比如people类,并且这个类没有重写equals方法,hashcode方法,那么在存储时put计算到的hash值是int的默认值0,equals获得的值是Boolean的默认值false,那么存储时都会加入到数组的第一节点中,在第一链表中所有节点的比较永远是false,即没有这个key,put 操作时,永远能存储进去,而get或者containsKey(key)时永远是没有结果,这样数据只能存储不能获取,称为内存泄漏.
    2. 第二种内存泄漏
      在put时hashmap会计算key的hash值,第一次计算是对象的hashcode的结果,第二次是根据第一次计算结果计算数组下标,一个key保存到map后,如果修改其中参与hashcode的值,再次计算的hashcode就可能不同,不同数据在计算数组下标,更加会发生变化,无法计算到存储时的下标,就无法找到准确的链表,那么根据对象寻找key所在位置的方式就会失效,当重新put这个旧的key时,因为hash值是新的,指向的地址是新的,那么新的链表中一定没有原先存过的数据,所以能put进数据,但是如果经常这样内存泄漏,可能会在一个链表中遇到原先存放的旧key。但是这种情况已经是可以控制的了,所以自定义类作为key时要重写equals方法和hashcode方法,并且保证hashcode的值不可被修改.

2.TreeMap

  • TreeMap:TreeMap的结构是二叉树,它有自己比较器,但是也可以自己重写这个比较器的方法,因此他是有序的,这个顺序是程序员自己设计的,一般来说,比较key值,第一个为根节点,比它小的放左边,比它大的放右边,如果下面还有节点就继续比较,直到没有节点可以比较,就是自己的位置。
  • TreeMap的一些特点:TreeMap有自己比较器,他是有大小序的,因此可以进行大小查找。
  • TreeMap的一些常用方法:
TreeMap<Integer, String> map = new TreeMap<Integer, String>();
        map.put(1, "fff");// 向TreeMap中添加数据
        map.firstEntry();// 找最小的节点数据,返回键值对
        map.firstKey();// 找最小的节点的KEY,返回键值对
        map.lastEntry();// 找最大的节点数据,返回键值对
        map.lastKey();// 找最大的节点的KEY,返回键值对
        map.pollFirstEntry();//删除最小的节点数据
        map.pollLastEntry();//删除最大的节点数据
        map.headMap(120);// 找比他小的,没有就返回空map,有就返回一个map
        map.subMap(1, 1);// 找在两个之间的节点,没有返回空map,有就返回一个map
        map.tailMap(120);// 找比他大的,没有就返回空map,有就返回一个map
        map.floorEntry(9);//找<=他的,没有就返回null,返回键值对
        map.floorKey(9);//找<=他的,没有就返回null,返回key值
        map.ceilingEntry(9);//找>=他的,没有就返回null,返回键值对
        map.ceilingKey(9);//找>=他的,没有就返回null,返回key值

3.LinkedHashMap

  • LinkedHashMap:它的结构基本上和HashMap差不多,但是在HashMap的基础上添加了两个节点,即上节点和下节点,(原先的HashMap结构不变)用来存放数据的顺序。

4. ConcurrentHashMap

  • ConcurrentHashMap:它的结构基本上和HashMap差不多,它是在存放数据的单链表处添加了资源锁,这样在多线程高并发的情况下,只允许数组个数个线程访问ConcurrentHashMap,比自己在多线程工作时给HashMap添加资源锁效率更高效。

5. Set

  • Set:Set中有两个常用的实现类,HashSet,TreeSet,用来存放数据,但是要注意Set中存放的不是键值对,只能存储一个。HashSet和HashMap相似,TreeSet和TreeMap相似,LinkedHashSet和LinkedHashMap相似,但是存的是单个的值,一般用来取出map中的键值对,将map中的某个值存放到Set中,取出map中的值