• J2EE从入门到入土02.Set及Map集合解析


    目录

    set集合

     HashSet

    元素新增 

     TreeSet

    红黑树

     Map

    HashMap

     ConcurrentHashMap

    TreeMap

    LinkedHashMap

    set集合

     set集合主要看一下两个:HashSet、TreeSet

     HashSet

    特点是:

    特点:无序,不重复
    扩容: 初始容量16,负载因子0.75,扩容增量1倍

    示例:new HashSet<>(20, 0.5f);

    它存储唯一元素并允许空值
    依据对象的hashcode来确定该元素是否存在
    由HashMap支持
    不保持插入顺序
    非线程安全

    HashSet底层就是用HashMap实现的,也就是非线程安全.

    1. private transient HashMap<E,Object> map;
    2. //构造一个初始容量16,加载因子是0.75的HashMap
    3. public HashSet() {
    4. map = new HashMap<>();
    5. }

     既然是用HashMap实现的,那么问题来了,Set存的只是一个对象,而HashMap存的是<K,V>键值对,怎么用HashMap作为Set的底层呢?

    元素新增 

    1. private static final Object PRESENT = new Object();
    2. public boolean add(E e) {
    3. return map.put(e, PRESENT)==null;
    4. }

    可以看到,HashSet只使用了HashMap的key,而值PRESENT是一个对象常量。这也是为什么Set里面的元素都是唯一的。

    返回是map.put(e, PRESENT)==null表达式,HashMap在新增值的时候如果已有则返回旧值,反之,返回null,所以这也是Set判断重复添加值的时候是否添加成功。

     TreeSet

    特点:是一个包含有序的且没有重复元素的集合
    作用是提供有序的Set集合,自然排序或者根据提供的Comparator进行排序,可以在对象出重写比较器,也要重写hashCode方法可以根据自己需求来比较排序,也可以在方法内新建对象来new一个比较器,在对象里面需要重写hashCodee方法

    1. public class Student implements Comparable<Student>{
    2. private Integer sid;
    3. private String sname;
    4. private int age;
    5. public Student(Integer sid, String sname, int age) {
    6. super();
    7. this.sid = sid;
    8. this.sname = sname;
    9. this.age = age;
    10. }
    11. public Integer getSid() {
    12. return sid;
    13. }
    14. public void setSid(Integer sid) {
    15. this.sid = sid;
    16. }
    17. public String getSname() {
    18. return sname;
    19. }
    20. public void setSname(String sname) {
    21. this.sname = sname;
    22. }
    23. public int getAge() {
    24. return age;
    25. }
    26. public void setAge(int age) {
    27. this.age = age;
    28. }
    29. @Override
    30. public int hashCode() {
    31. final int prime = 31;
    32. int result = 1;
    33. result = prime * result + age;
    34. result = prime * result + ((sid == null) ? 0 : sid.hashCode());
    35. result = prime * result + ((sname == null) ? 0 : sname.hashCode());
    36. return result;
    37. }
    38. @Override
    39. public boolean equals(Object obj) {
    40. if (this == obj)
    41. return true;
    42. if (obj == null)
    43. return false;
    44. if (getClass() != obj.getClass())
    45. return false;
    46. Student other = (Student) obj;
    47. if (age != other.age)
    48. return false;
    49. if (sid == null) {
    50. if (other.sid != null)
    51. return false;
    52. } else if (!sid.equals(other.sid))
    53. return false;
    54. if (sname == null) {
    55. if (other.sname != null)
    56. return false;
    57. } else if (!sname.equals(other.sname))
    58. return false;
    59. return true;
    60. }
    61. @Override
    62. public String toString() {
    63. return "Student [sid=" + sid + ", sname=" + sname + ", age=" + age + "]";
    64. }
    65. @Override
    66. public int compareTo(Student o) {
    67. if(this.getAge()-o.getAge()==0) {
    68. return this.getSid()-o.getSid();
    69. }
    70. return this.getAge() - o.getAge();
    71. }

    TreeSet的底层是TreeMap,而TreeMap底层数据结构是红黑树。 

    1. public TreeSet() {
    2. this(new TreeMap<E,Object>());
    3. }

     TreeMap的节点结构如下:

    1. class Entry<K,V> implements Map.Entry<K,V> {
    2. K key;
    3. V value;
    4. Entry<K,V> left;
    5. Entry<K,V> right;
    6. Entry<K,V> parent;
    7. boolean color = BLACK;
    8. }

    红黑树的起源,自然是二叉查找树了,这种树结构从根节点开始,左子节点小于它,右子节点大于它。每个节点都符合这个特性,所以易于查找,是一种很好的数据结构

    红黑树

    注:红黑数是平衡二叉树的一种,插入时遵循二叉树“左右”定律:

    该父节点的左子节点:小于父节点中且子树中最接近父节点值得数。

    该父节点的右子节点:大于父节点中且子树中最接近父节点值得数。

     

     Map

    特点:

    无序,键值对,键不能重复,值可以重复,
    键重复则覆盖,没有继承Collection接口

    遍历方式:

    先获取所有键的Set集合,再遍历(通过键获取值)
    取出保存所有Entry的Set,再遍历此Set即可

    扩容:初始容量16,负载因子0.75,扩容增量1倍

     常用类型

    HashMap  ConcurrentHashMap   TreeMap  LinkedHashMap

    HashMap

     线程不安全,最常用,速度快
        内部采用数组来存放数据

     基本原理

    put执行过程

     Table数组中的的Node的数据结构

    在jdk 1.8之前是这样的一个链表式结构,根据传出的key值来进行哈希值计算,确认放到那个桶内

    ,如果桶内有重复的值则覆盖掉,没有则把原来的元素往下挤

     在jdk 1.8的时候则是改成红黑树结构

    流程图中绿色标出的部分为JDK8新增的处理逻辑,目的是在Table[i]中的Node节点数量大于8时,通过红黑树提升查找速度。

    根据传出的key值来进行哈希值计算,确认放到那个桶内,如果桶内有重复的值则覆盖掉,则根据根节点来判断值的大小,来分配

     ConcurrentHashMap

     线程安全,性能高用的比较多,还有一个HashTable这个类方法则是加了一把锁给锁锁住了,运行进程会慢。

    源码:读写时会进行上锁,每一个桶也就是每一个节点会上一把锁分段锁,默认16把锁

    1. final V putVal(K key, V value, boolean onlyIfAbsent) {
    2. if (key == null || value == null) throw new NullPointerException();
    3. int hash = spread(key.hashCode());
    4. int binCount = 0;
    5. for (Node<K,V>[] tab = table;;) {
    6. Node<K,V> f; int n, i, fh; K fk; V fv;
    7. if (tab == null || (n = tab.length) == 0)
    8. tab = initTable();
    9. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    10. if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
    11. break; // no lock when adding to empty bin
    12. }
    13. else if ((fh = f.hash) == MOVED)
    14. tab = helpTransfer(tab, f);
    15. else if (onlyIfAbsent // check first node without acquiring lock
    16. && fh == hash
    17. && ((fk = f.key) == key || (fk != null && key.equals(fk)))
    18. && (fv = f.val) != null)
    19. return fv;
    20. else {
    21. V oldVal = null;
    22. synchronized (f) {
    23. if (tabAt(tab, i) == f) {
    24. if (fh >= 0) {
    25. binCount = 1;
    26. for (Node<K,V> e = f;; ++binCount) {
    27. K ek;
    28. if (e.hash == hash &&
    29. ((ek = e.key) == key ||
    30. (ek != null && key.equals(ek)))) {
    31. oldVal = e.val;
    32. if (!onlyIfAbsent)
    33. e.val = value;
    34. break;
    35. }
    36. Node<K,V> pred = e;
    37. if ((e = e.next) == null) {
    38. pred.next = new Node<K,V>(hash, key, value);
    39. break;
    40. }
    41. }
    42. }
    43. else if (f instanceof TreeBin) {
    44. Node<K,V> p;
    45. binCount = 2;
    46. if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
    47. value)) != null) {
    48. oldVal = p.val;
    49. if (!onlyIfAbsent)
    50. p.val = value;
    51. }
    52. }
    53. else if (f instanceof ReservationNode)
    54. throw new IllegalStateException("Recursive update");
    55. }
    56. }
    57. if (binCount != 0) {
    58. if (binCount >= TREEIFY_THRESHOLD)
    59. treeifyBin(tab, i);
    60. if (oldVal != null)
    61. return oldVal;
    62. break;
    63. }
    64. }
    65. }
    66. addCount(1L, binCount);
    67. return null;
    68. }

    TreeMap

    • key值按一定的顺序排序,基于红黑树,容量无限制,非线程安全,比较常用
    • 添加或获取元素时性能较HashMap慢(因为需求维护内部的红黑树,用于保证key值的顺序)
    • 能比较元素的大小,根据key比较(元素的自然顺序,集合中自定义的比较器也可排序)
    1. private TreeMap<String,Student> treeMap;
    2. @Before
    3. public void setup() {
    4. treeMap = new TreeMap<String,Student>(new Comparator<String>() {
    5. @Override
    6. public int compare(String o1, String o2) {
    7. // 负数 0 正数
    8. return o1.compareTo(o2);
    9. }
    10. });
    11. treeMap.put("1", new Student(5, "小白"));
    12. treeMap.put("2", new Student(3, "小黑"));
    13. treeMap.put("3", new Student(2, "小黄"));
    14. treeMap.put("4", new Student(4, "小明"));
    15. treeMap.put("3", new Student(1, "小黑"));
    16. treeMap.put("4", new Student(4, "小明"));
    17. }

    LinkedHashMap

    继承HashMap
    维护了一个双向链表
    LinkedHashMap是有序的,且默认为插入顺序
    默认情况下使用entryset获取的集合顺序是与节点的插入顺序(默认是按照插入的顺序进行排列的,最先插入的节点(即最老的节点)为head,最新插入的节点为tail) 

    注意:有序和无序是描述系统内部状态、客观事物内部各要素以及客观事物之间关系的范畴。有序指系统的组成元素、事物内部诸要素或事物之间的有规则的排列、组合、运动和转化,含结构有序与运动有序;无序则相反,指事物内部诸要素或事物之间、系统内部组成元素之间混乱而无规则的组合、运动和转化,含结构无序和运动无序。有序和无序是世界上存在的两种情形,二者的差异是相对的,世间没有绝对的有序和无序,在有序的事物中存在着破坏其有规则的排列或运动过程的因素,无序的事物中总是包含有有序的因素

  • 相关阅读:
    通过股票量化交易券商接口如何减少发生亏损的风险?
    作业 把dic.txt 导入数据库里
    proguard 混淆jar内容
    【Selenium】Selenium4 Grid
    大家都在用MySQL count(*)统计总数,到底有什么问题?
    图像处理之理想高通滤波器、巴特沃斯高通滤波器和高斯高通滤波器的matlab简单实现
    计算机组成原理历年考研真题对应知识点(计算机的性能指标)
    在window10下python:ocr实战
    论文精读:Swin Transformer V2: Scaling Up Capacity and Resolution
    【发布】Photoshop ICO 文件格式插件 3.0
  • 原文地址:https://blog.csdn.net/WZJ278/article/details/125513728