1、Java-Map-概述

  1. 1Map是一个接口,也称为双列集合,它的每个元素都包含一个键对象Key和值对象Value,键和值对象之间存在一种对应关系,称为映射。
  2. public interface Map<K,V> {}
  3. 2Map中的key值可看成Set集合,是无序且不能重复的,value值可看成List,可重复,每个元素可根据索引来找,只是这个索引是以另一个对象作为索引的。
  4. 3key-value 可以是任何引用类型的数据,且key不允许重复,value可以重复,插入两个键值相同记录时,那么后插入的记录会覆盖先插入的记录并将先插入的记录返回
  5. /**
  6. * 当插入的key相同时,后插入的记录会覆盖掉先插入的记录,并将先插入的记录返回
  7. */
  8. public class MapDemo {
  9. public static void main(String[] args){
  10. Map<String,String> map = new Hashtable<String,String>();
  11. String str1 = map.put("1","张三");
  12. String str2 = map.put("2","李四");
  13. String str3 = map.put("2","陈汉");
  14. System.out.println(str1);//输出:null
  15. System.out.println(str2);//输出:null
  16. System.out.println(str3);//输出:李四
  17. System.out.println(map);// 输出:{2=陈汉, 1=张三}
  18. }
  19. }

1-1、Java-Map-Map中的常用方法

  1. void clear();//删除该Map对象中所有键值对;
  2. boolean containsKey(Object key);//查询Map中是否包含指定的key值;
  3. boolean containsValue(Object value);//查询Map中是否包含一个或多个value;
  4. Set entrySet();//返回map中包含的键值对所组成的Set集合,每个集合都是Map.Entry对象。
  5. Object get();//返回指定key对应的value,如果不包含key则返回null;
  6. boolean isEmpty();//查询该Map是否为空;
  7. Set keySet();//返回Map中所有key组成的集合;
  8. Collection values();//返回该Map里所有value组成的Collection。
  9. Object put(Object key,Object value);//添加一个键值对,如果集合中的key重复,则覆盖原来的键值对;
  10. void putAll(Map m);//将Map中的键值对复制到本Map中;
  11. Object remove(Object key);//删除指定的key对应的键值对,并返回被删除键值对的value,如果不存在,则返回null;
  12. boolean remove(Object key,Object value);//删除指定键值对,删除成功返回true;
  13. int size();//返回该Map里的键值对个数;

1-2、Java-Map-内部类Entry

  1. 1Map中包括一个内部类Entry,该类封装一个键值对,常用方法:
  2. Object getKey():返回该Entry里包含的key值;
  3. Object getvalue():返回该Entry里包含的value值;
  4. Object setValue(V value):设置该Entry里包含的value值,并设置新的value值。
  5. 2、源码:
  6. /**
  7. * Map内部类Entry,封装了一个键值对
  8. */
  9. Set<Map.Entry<K, V>> entrySet();
  10. /**
  11. * Map有个内部接口:Entry,
  12. */
  13. interface Entry<K,V> {
  14. Object getKey();//返回该Entry里包含的key值;
  15. Object getvalue();//返回该Entry里包含的value值;
  16. Object setValue(V value);//设置该Entry里包含的value值,并设置新的value值。
  17. }
  18. 3Demo
  19. /**
  20. * 例子:
  21. */
  22. public class MapDemo1 {
  23. public static void main(String[] args){
  24. Map<String, Integer> hm = new HashMap<String, Integer>();
  25. //放入元素
  26. hm.put("Harry",23);
  27. hm.put("Jenny",24);
  28. hm.put("XiaoLi",20);
  29. System.out.println(hm);//{XiaoLi=20, Harry=23, Jenny=24}
  30. System.out.println(hm.keySet());//[XiaoLi, Harry, Jenny]
  31. System.out.println(hm.values());//[20, 23, 24]
  32. Set<Map.Entry<String, Integer>> entries = hm.entrySet();
  33. for (Map.Entry<String, Integer> entry : entries) {
  34. System.out.println(entry.getKey());//XiaoLi, Harry, Jenny
  35. System.out.println(entry.getValue());//20, 23, 24
  36. entry.setValue(50); //设置该Entry中包含的value值为50
  37. System.out.println(hm);//最后为{XiaoLi=50, Harry=50, Jenny=50}
  38. }
  39. }
  40. }

1-3、Java-Map-java8新增方法

  1. /**
  2. * Object compute(Object key,BiFurcation remappingFunction);使用remappingFunction根据原键
  3. * 值对计算一个新的value,只要新value不为null,就覆盖原value;如果新value为null则删除该键值对,如果同
  4. * 时为null则不改变任何键值对,直接返回null。
  5. */
  6. //例子
  7. public class MapDemo2 {
  8. public static void main(String[] args){
  9. Map<String, Integer> hm = new HashMap<String, Integer>();
  10. //放入元素
  11. hm.put("Harry",23);
  12. hm.put("Jenny",24);
  13. hm.put("XiaoLi",20);
  14. System.out.println(hm);//{XiaoLi=20, Harry=23, Jenny=24}
  15. hm.compute("Harry",(key,value)->(Integer)value+10);
  16. System.out.println(hm);////{XiaoLi=20, Harry=33, Jenny=24}
  17. hm.compute("qwerty",(key,value)->(Integer)value+10);//Map集合没有该key时,会抛出空指针异常
  18. System.out.println(hm);//java.lang.NullPointerException
  19. }
  20. }
  21. /**
  22. * Object computeIfAbsent(Object key,Furcation mappingFunction):如果传给该方法的key参数在
  23. * Map中对应的value为null,则使用mappingFunction根据key计算一个新的结果,如果计算结果不为null,则计算* 结果覆盖原有的value,如果原Map原来不包含该Key,那么该方法可能会添加一组键值对。
  24. */
  25. //例子
  26. public class MapDemo3 {
  27. public static void main(String[] args){
  28. Map<String, Integer> hm = new HashMap<String, Integer>();
  29. //放入元素
  30. hm.put("Harry",23);
  31. hm.put("Jenny",24);
  32. hm.put("XiaoLi",20);
  33. hm.put("LiMing",null);
  34. //指定key为null则计算结果作为value
  35. hm.computeIfAbsent("LiMing",(key)->10);
  36. System.out.println(hm);//{XiaoLi=20, Harry=23, Jenny=24, LiMing=10}
  37. //如果指定key本来不存在,则添加对应键值对
  38. hm.computeIfAbsent("XiaoHong",(key)->34);
  39. System.out.println(hm);//{XiaoLi=20, Harry=23, XiaoHong=34, Jenny=24, LiMing=10}
  40. }
  41. }
  42. /**
  43. * Object computeIfPresent(Object key,BiFurcation remappingFunction):如果传给该方法的key
  44. * 参数在Map中对应的value不为null,则通过计算得到新的键值对,如果计算结果不为null,则覆盖原来的value,如* 果计算结果为null,则删除原键值对。
  45. */
  46. //例子
  47. public class MapDemo4 {
  48. public static void main(String[] args){
  49. Map<String, Integer> hm = new HashMap<String, Integer>();
  50. //放入元素
  51. hm.put("Harry",23);
  52. hm.put("Jenny",24);
  53. hm.put("XiaoLi",20);
  54. hm.put("LiMing",null);
  55. hm.computeIfPresent("Harry",(key,value)->(Integer)value*(Integer)value);
  56. System.out.println(hm);//{XiaoLi=20, Harry=529, Jenny=24, LiMing=null}
  57. }
  58. }
  59. /**
  60. * void forEach(BiConsumer action);遍历键值对。
  61. * Object getOrDefault(Object key,V defaultValue);
  62. * 获取指定key对应的value,如果key不存在则返回defaultValue;
  63. */
  64. //例子
  65. public class MapDemo5 {
  66. public static void main(String[] args){
  67. HashMap<String, Integer> hm = new HashMap<>();
  68. //放入元素
  69. hm.put("Harry",23);
  70. hm.put("Jenny",24);
  71. hm.put("XiaoLi",20);
  72. hm.put("LiMing",null);
  73. hm.forEach((key,value)-> System.out.println(key+"->"+value));
  74. /*XiaoLi->20
  75. Harry->23
  76. Jenny->24
  77. LiMing->null*/
  78. System.out.println(hm.getOrDefault("Harry",33));//23
  79. System.out.println(hm.getOrDefault("Liu",33));//33
  80. }
  81. }
  82. /**
  83. * Object merge(Object key,Object value,BiFurcation remappingFunction):该方法会先根据key参* 数获取该Map中对应的value。如果获取的value为null,则直接用传入的value覆盖原有的value,如果获取的
  84. * value不为null,则使用remappingFunction函数根据原value、新value计算一个新的结果,并用得到的结果去
  85. * 覆盖原有的value。
  86. */
  87. //例子
  88. public class MapDemo6 {
  89. public static void main(String[] args){
  90. HashMap<String, Integer> hm = new HashMap<>();
  91. //放入元素
  92. hm.put("Harry",23);
  93. hm.put("Jenny",24);
  94. hm.put("XiaoLi",20);
  95. hm.put("LiMing",null);
  96. hm.merge("LiMing",44,(key,value)->value+20);
  97. System.out.println(hm);//{XiaoLi=20, Harry=23, Jenny=24, LiMing=24}
  98. hm.merge("Harry",44,(key,value)->value+10);
  99. System.out.println(hm);//{XiaoLi=20, Harry=54, Jenny=24, LiMing=24}
  100. }
  101. }
  102. /**
  103. * Object putIfAbsent(Object key,Object value);该方法会自动检测指定key对应的value是否为null,
  104. * 如果为null,则用新value去替换原来的null值。
  105. * Object replace(Object key,Object value);将key对应的value替换成新的value,如果key不存在则返回* null。
  106. * boolean replace(K key,V oldValue,V newValue);将指定键值对的value替换成新的value,如果未找到则* 返回false;
  107. * replaceAll(BiFunction Function);使用BiFunction对原键值对执行计算,并将结果作为该键值对的value
  108. * 值。
  109. */

1-4、Java-Map-遍历

  1. 1Map遍历的方式有4种,分别是:
  2. 1-1、使用for循环/foreach循环遍历map
  3. 1-2、使用迭代器(Iterator)遍历map;[在遍历的过程中我们可以对集合中的元素进行删除。]
  4. 1-3、使用keySet迭代遍历map;[效率很低,不推荐使用]
  5. 1-4、使用entrySet遍历map。[性能最好]
  6. 1-5、注意: Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。(Iterable接口的所有子接口和实现类都能使用增强For循环遍历,而Map接口并不属于Iterable派系)。
  7. 2Demo:
  8. 2-1、使用for循环遍历map
  9. public class Demo1 {
  10. public static void main(String[] args){
  11. Map<String, Integer> mapTest = new HashMap<>();
  12. //放入元素
  13. mapTest.put("Harry",23);
  14. mapTest.put("Jenny",24);
  15. mapTest.put("XiaoLi",20);
  16. mapTest.put("LiMing",null);
  17. for(Map.Entry<String, Integer> entry:mapTest.entrySet()){
  18. System.out.println(entry.getKey()+"--->"+entry.getValue());
  19. }
  20. /** XiaoLi--->20
  21. Harry--->23
  22. Jenny--->24
  23. LiMing--->null */
  24. }
  25. }
  26. 2-2、使用迭代器(Iterator)遍历map
  27. public class Demo2 {
  28. public static void main(String[] args){
  29. Map<String, Integer> mapTest = new HashMap<>();
  30. //放入元素
  31. mapTest.put("Harry",23);
  32. mapTest.put("Jenny",24);
  33. mapTest.put("XiaoLi",20);
  34. mapTest.put("LiMing",null);
  35. Set set = map.entrySet();
  36. Iterator i = set.iterator();
  37. while(i.hasNext()){
  38. Map.Entry<String, Integer> entry1=(Map.Entry<String, Integer>)i.next();
  39. System.out.println(entry1.getKey()+"=="+entry1.getValue());
  40. }
  41. /** XiaoLi--->20
  42. Harry--->23
  43. Jenny--->24
  44. LiMing--->null */
  45. }
  46. }
  47. 2-3、使用keySet迭代遍历map:通过key去获取对应的value。【效率低】
  48. public class Demo3 {
  49. public static void main(String[] args){
  50. Map<String, Integer> mapTest = new HashMap<>();
  51. //放入元素
  52. mapTest.put("Harry",23);
  53. mapTest.put("Jenny",24);
  54. mapTest.put("XiaoLi",20);
  55. mapTest.put("LiMing",null);
  56. Set set = mapTest.keySet(); //得到map集合中所有的键值
  57. Iterator it = set.iterator();
  58. while(it.hasNext()){
  59. String key;
  60. Integer value;
  61. key=it.next().toString();
  62. //通过指定的键值,从map集合中获取对应的value值
  63. value=mapTest.get(key);
  64. System.out.println(key+"--"+value);
  65. }
  66. /** XiaoLi--->20
  67. Harry--->23
  68. Jenny--->24
  69. LiMing--->null */
  70. }
  71. }
  72. 2-4、使用entrySet遍历map
  73. public class Demo4 {
  74. public static void main(String[] args){
  75. Map<String, Integer> mapTest = new HashMap<>();
  76. //放入元素
  77. mapTest.put("Harry",23);
  78. mapTest.put("Jenny",24);
  79. mapTest.put("XiaoLi",20);
  80. mapTest.put("LiMing",null);
  81. String key;
  82. Integer value;
  83. //得到一个包含多个键值对元素的Set集合
  84. Set<Map.Entry<String, Integer>> entries = mapTest.entrySet();
  85. Iterator it = entries.iterator();
  86. while(it.hasNext()){
  87. Map.Entry entry = (Map.Entry)it.next();
  88. key=entry.getKey().toString();
  89. value= (Integer)entry.getValue();
  90. System.out.println(key+"===="+value);
  91. }
  92. for (Map.Entry<String, Integer> entry : mapTest.entrySet()) {
  93. System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
  94. }
  95. /** XiaoLi====20
  96. Harry====23
  97. Jenny====24
  98. LiMing====null
  99. key= XiaoLi and value= 20
  100. key= Harry and value= 23
  101. key= Jenny and value= 24
  102. key= LiMing and value= null */
  103. }
  104. }

1-5、Java-Map-常用类继承结构图image.png

1-6、Java-Map-hashcode()|equals()-关系

  1. 1hashcode()|equals()-关系:
  2. 1-1、如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true
  3. 1-2、如果两个对象hashCode()相等,它们并不一定相等。
  4. 1-2-1、因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:"两个不同的键值对,哈希值相等",这就是哈希冲突。(若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。)

2、Java-Map-HashMap-概述

  1. 1HashMap-概述:
  2. 1-1HashMap是一个用于存储Key-Value键值对的集合,是一个散列表,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中。
  3. 1-2Hashmap继承自abstractmap,它的数据结构是数组和链表;
  4. 1-3HashMap是非同步的,也就意味着是非线程安装的,它的keyvalue都可以为null(null可以作为键,这样的键只有一个)。此外,HashMap中的映射不是有序的。
  5. 1-4HashMap 是一个利用哈希表原理来存储元素的集合。遇到冲突时,HashMap 是采用的链地址法来解决,在 JDK1.7 中,HashMap 是由 数组+链表构成的。但是在 JDK1.8 中,HashMap 是由 数组+链表+红黑树构成,新增了红黑树作为底层数据结构。
  6. 1-5、[容易忽视!!!]HashMap是无序的,迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。
  7. 2HashMap-继承关系:
  8. public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {}
  9. 3、哈希冲突处理:
  10. 3-1、采用哈希的解决地址冲突算法,在当前hashCode值处类似一个新的链表, 在同一个hashCode值的后面存储存储不同的对象,这样就保证了元素的唯一性。
  11. 3-2、根据对冲突的处理方式不同,哈希表有两种实现方式,一种开放地址方式(Open addressing),另一种是冲突链表方式(Separate chaining with linked lists)。Java HashMap采用的是冲突链表方式。

2-1、Java-Map-HashMap-继承关系-示意图

image.png

2-2、Java-Map-HashMap-工作原理

  1. 1HashMap-相关概念
  2. 1-1HashMap中的两个重要的参数:HashMap中有两个重要的参数:初始容量大小和加载因子,初始容量大小是创建时给数组分配的容量大小,默认值为16,用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍(扩容)。
  3. 1-2、在JDK1.6JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。
  4. JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树。
  5. 2HashMap-工作原理-简介
  6. 2-1HashMap基于哈希(hashing)原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。
  7. 2-2HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
  8. 2-3、当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
  9. 2-4、其他见源码分析......

2-3、Java-Map-HashMap-源码分析

容器(Map)-HashMap-源码分析

2-4、Java-Map-HashMap-小结

  1. 1HashMap-小结
  2. 1-1、基于JDK1.8HashMap是由数组+链表+红黑树组成,当链表长度超过 8 时会自动转换成红黑树,当红黑树节点个数小于 6 时,又会转化成链表。相对于早期版本的 JDK HashMap 实现,新增了红黑树作为底层数据结构,在数据量较大且哈希碰撞较多时,能够极大的增加检索的效率。
  3. 1-2、允许 key value 都为 nullkey 重复会被覆盖,value 允许重复。
  4. 1-3、非线程安全。
  5. 1-4、无序(遍历HashMap得到元素的顺序不是按照插入的顺序)。
  6. 1-5、扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
  7. 1-6、负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。
  8. 1-7、哈希冲突,也叫哈希碰撞:如果两个不同的元素,通过哈希函数得出的实际存储地址相同。哈希冲突的解决方案有多种。 开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式。
  9. 1-8HashMap Iterator 使用的是fail-fast 迭代器,当有其他线程改变了 HashMap 的结构(增加、删除、修改元素),将会抛出ConcurrentModificationException

3、Java-Map-HashSet-概述

  1. 1HashSet-概述
  2. 1-1HashSetJava集合Set的一个实现类,Set是一个接口,其实现类除HashSet之外,还有TreeSet,并继承了Collection
  3. 1-2、元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性。
  4. 1-3、检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
  5. 2HashSet-继承关系
  6. public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {}

3-1、Java-Map-HashSet-继承关系示意图

image.png

3-2、Java-Map-HashSet-实现原理

  1. 1HashSet-实现原理-简介:
  2. 1-1、是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap key 来保存,而 HashMap value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
  3. private transient HashMap<E,Object> map;
  4. //要与支持映射中的对象关联的伪值
  5. private static final Object PRESENT = new Object();
  6. //默认无参构造函数
  7. public HashSet() {
  8. map = new HashMap<>();
  9. }
  10. 1-2、将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true

3-3、Java-Map-HashSet-源码分析

容器(Map)-HashSet-源码分析

3-4、Java-Map-HashSet-小结

  1. 1HashSet-小结:
  2. 1-1HashSet底层声明了一个HashMapHashSet做了一层包装,操作HashSet里的元素时其实是在操作HashMap里的元素。

4、Java-Map-TreeSet-概述

  1. 1TreeSet-概述:
  2. 1-1TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
  3. 1-2TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。
  4. 1-3TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。
  5. 1-4TreeSet是一个有序的集合,它的作用是提供有序的Set集合。它继承了AbstractSet抽象类,实现了NavigableSet<E>,CloneableSerializable接口。
  6. 2TreeSet-继承关系:
  7. public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable {}

4-1、Java-Map-TreeSet-继承关系示意图

image.png

4-2、Java-Map-TreeSet-实现原理

  1. 1TreeSet-实现原理:
  2. 1-1TreeSet存储对象的时候, 可以排序, 但是需要指定排序的算法
  3. 1-2Integer能排序(有默认顺序), String能排序(有默认顺序), 自定义的类存储的时候出现异常(没有顺序)
  4. 1-3、如果想把自定义类的对象存入TreeSet进行排序, 那么必须实现Comparable接口:在类上implement Comparable,重写compareTo()方法,在方法内定义比较算法, 根据大小关系, 返回正数负数或零。
  5. 1-4、在使用TreeSet存储对象的时候, add()方法内部就会自动调用compareTo()方法进行比较, 根据比较结果使用二叉树形式进行存储
  6. 1-5TreeSet是依靠TreeMap来实现的。

4-3、Java-Map-TreeSet-源码分析

容器(Map)-TreeSet-源码分析

4-4、Java-Map-TreeSet-用法

  1. 1、自定义对象的话,必须要告诉TreeSet如何来进行比较元素,如果不指定,就会抛出这个异常:java.lang.ClassCastException
  2. public static void demoOne() {
  3. TreeSet<Person> ts = new TreeSet<>();
  4. ts.add(new Person("张三", 11));
  5. ts.add(new Person("李四", 12));
  6. ts.add(new Person("王五", 15));
  7. ts.add(new Person("赵六", 21));
  8. System.out.println(ts); //需要在自定义类(Person)中实现Comparable接口,并重写接口中的compareTo方法
  9. }
  10. //需要在自定义类(Person)中实现Comparable接口,并重写接口中的compareTo方法
  11. //如果将compareTo()返回值写死为0,元素值每次比较,都认为是相同的元素,这时就不再向TreeSet中插入除第一个外的新元素。所以TreeSet中就只存在插入的第一个元素。
  12. //如果将compareTo()返回值写死为1,元素值每次比较,都认为新插入的元素比上一个元素大,于是二叉树存储时,会存在根的右侧,读取时就是正序排列的。
  13. //如果将compareTo()返回值写死为-1,元素值每次比较,都认为新插入的元素比上一个元素小,于是二叉树存储时,会存在根的左侧,读取时就是倒序序排列的。
  14. public class Person implements Comparable<Person> {
  15. private String name;
  16. private int age;
  17. ...
  18. public int compareTo(Person o) {
  19. return 0; //当compareTo方法返回0的时候集合中只有一个元素
  20. return 1; //当compareTo方法返回正数的时候集合会怎么存就怎么取
  21. return -1; //当compareTo方法返回负数的时候集合会倒序存储
  22. }
  23. }

4-5、Java-Map-TreeSet-小结

  1. 1TreeSet-小结
  2. 1-1TreeSet中的元素必须实现Comparable接口并重写compareTo()方法,TreeSet判断元素是否重复 、以及确定元素的顺序 靠的都是这个方法;
  3. 1-1-1、对于Java类库中定义的类,TreeSet可以直接对其进行存储,如StringInteger等,因为这些类已经实现了Comparable接口);
  4. 1-1-2、对于自定义类,如果不做适当的处理,TreeSet中只能存储一个该类型的对象实例,否则无法判断是否重复。
  5. 1-2、相对HashSet,TreeSet的优势是有序,劣势是相对读取慢。

5、Java-Map-TreeMap-概述

  1. 1TreeMap-概述
  2. 1-1TreeMap 继承于AbstractMap,是一个有序的key-value集合,它是通过红黑树实现的。
  3. 1-2TreeMap 实现了NavigableMap接口,NavigableMap接口继承了SortedMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
  4. 1-3TreeMap 实现了Cloneable接口,意味着它能被克隆。实现了java.io.Serializable接口,意味着它支持序列化。
  5. 1-4TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
  6. 1-5TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。
  7. 1-6TreeMap的基本操作 containsKeygetput remove 的时间复杂度是 log(n)
  8. 2TreeMap-继承关系:
  9. public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable {}

5-1、Java-Map-TreeMap-工作原理

  1. 1TreeMap-工作原理:
  2. 1-1TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现。该集合最重要的特点就是可排序,TreeMap可以对添加进来的元素进行排序,可以按照默认的排序方式,也可以自己指定排序方式。
  3. 1-2、红黑树回顾:https://www.cnblogs.com/LiaHon/p/11203229.html

5-2、Java-Map-TreeMap-继承关系-示意图

image.png

5-3、Java-Map-TreeMap-源码分析

容器(Map)-TreeMap-源码分析

5-4、Java-Map-TreeMap-小结

  1. 1TreeMap-小结
  2. 1-1TreeMap底层是红黑树,能够实现该map集合有序。
  3. 1-2、如果在构造方法中传递了Comparator对象,那么就以传入的对象的方法进行比较,否则,使用Comparable的.compareTo(T o)方法来比较。
  4. 1-2-1、使用Comparable的.compareTo(T o)方法来比较,key不能为null,且key需要实现了Comparable接口。
  5. 1-3TreeMap是非同步的,可以使用Collections来进行封装,达到同步。
  6. 1-3-1Collections.synchronizedMap(new TreeMap<>())
  7. 1-3-2ReentrantReadWriteLock,并发锁
  8. 1-4、关于红黑树的节点插入操作,首先是改变新节点,新节点的父节点,祖父节点,和新节点的颜色,能在当前分支通过节点的旋转改变的,则通过此种操作,来满足红黑书的特点。
  9. 1-4-1、如果当前相关节点的旋转解决不了红黑树的冲突,则通过将红色的节点移动到根节点解决,最后在将根节点设置为黑色。

6、Java-Map-ConcurrentHashMap-概述

  1. 1ConcurrentHashMap-概述:
  2. 1-1ConcurrentHashMap是建立在 Java 内存模型基础上的。
  3. 1-2ConcurrentHashMap,它是HashMap的一个线程安全的、支持高效并发的版本。在默认理想状态下,ConcurrentHashMap可以支持16个线程执行并发写操作及任意数量线程的读操作。
  4. 1-3ConcurrentHashMap本质上是一个Segment数组,而一个Segment实例又包含若干个桶,每个桶中都包含一条由若干个 HashEntry 对象链接起来的链表。
  5. 2ConcurrentHashMap-优点:
  6. 2-1、线程安全:在并发编程中,jdk1.7的情况下使用 HashMap 可能造成死循环,而jdk1.8 中有可能会造成数据丢失。
  7. 2-2HashTable 效率非常低下。
  8. 3ConcurrentHashMap-继承关系:ConcurrentHashMap 继承了AbstractMap并实现了ConcurrentMap接口
  9. public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>, Serializable {}

6-1、Java-Map-ConcurrentHashMap-继承关系-示意图

image.png

6-2、Java-Map-ConcurrentHashMap-工作原理

  1. 1ConcurrentHashMap-工作原理:
  2. 1-1、锁分离 (Lock Stripping):ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
  3. final Segment<K,V>[] segments;
  4. 1-2、不变(Immutable)和易变(Volatile):ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。
  5. static final class HashEntry<K,V> {
  6. final K key;
  7. final int hash;
  8. volatile V value;
  9. final HashEntry<K,V> next;
  10. }

6-3、Java-Map-ConcurrentHashMap-源码解析

6-4、Java-Map-ConcurrentHashMap-小结

  1. 1ConcurrentHashMap-小结:
  2. 1-1、在使用锁来协调多线程间并发访问的模式下,减小对锁的竞争可以有效提高并发性。
  3. 1-1-1、减小请求同一个锁的频率。
  4. 1-1-2、减少持有锁的时间。
  5. 1-2JDK6,7中的ConcurrentHashmap主要使用Segment来实现减小锁粒度,把HashMap分割成若干个Segment,在put的时候需要锁住Segmentget时候不加锁,使用volatile来保证可见性,当要统计全局时(比如size),首先会尝试多次计算modcount来确定,这几次尝试中,是否有其他线程进行了修改操作,如果没有,则直接返回size。如果有,则需要依次锁住所有的Segment来计算。
  6. 1-3jdk7ConcurrentHashmap中,当长度过长碰撞会很频繁,链表的增改删查操作都会消耗很长的时间,影响性能。
  7. 1-3-1JDK1.7 版本的 ReentrantLock+Segment+HashEntry
  8. 1-4jdk8 中完全重写了concurrentHashmap,主要设计上的变化有以下几点:
  9. 1-4-1、不采用segment而采用node,锁住node来实现减小锁粒度。
  10. 1-4-2、设计了MOVED状态 resize的中过程中 线程2还在put数据,线程2会帮助resize
  11. 1-4-3、使用3CAS操作来确保node的一些操作的原子性,这种方式代替了锁。
  12. 1-4-4sizeCtl的不同值来代表不同含义,起到了控制的作用。
  13. 1-4-5JDK1.8 版本中synchronized+CAS+Node+红黑树。
  14. 2JDK1.8为什么要摒弃分段锁(ReentrantLock),而采用重入锁(synchronized)?
  15. 2-1jdk1.8中锁的粒度更细了。jdk1.7ConcurrentHashMap concurrentLevel(并发数)基本上是固定的。jdk1.8中的concurrentLevel是和数组大小保持一致的,每次扩容,并发度扩大一倍.
  16. 2-2、红黑树的引入,对链表的优化使得 hash 冲突时的 put get 效率更高
  17. 2-3、获得JVM的支持 ReentrantLock 毕竟是 API 这个级别的,后续的性能优化空间很小。 synchronized 则是 JVM 直接支持的, JVM 能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得 synchronized 能够随着 JDK 版本的升级而不改动代码的前提下获得性能上的提升。

7、Java-Map-LinkedHashMap-概述

  1. 1LinkedHashMap-概述:
  2. 1-1LinkedHashMap通过维护一个运行于所有条目的双向链表,保证了集合元素迭代的顺序,这个顺序可以是插入顺序或者访问顺序。
  3. 1-2LinkedHashMap存储数据是有序的,而且分为两种:插入顺序(默认值:accessOrder = false)和访问顺序(LRU算法-最少访问)。
  4. 2LinkedHashMap-特点:
  5. 2-1keyvalue都允许为空
  6. 2-2key重复会覆盖,value可以重复
  7. 2-3、有序的
  8. 2-4LinkedHashMap是非线程安全的
  9. 3LinkedHashMap-基本结构:
  10. 3-1LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。
  11. 3-2LinkedHashMap的基本实现思想就是----多态。
  12. 4LinkedHashMap-应用场景:
  13. 4-1HashMap是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap
  14. 5LinkedHashMap-继承关系:
  15. public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {}

7-1、Java-Map-LinkedHashMap-继承关系-示意图

image.png

7-2、Java-Map-LinkedHashMap-源码分析

容器(Map)-LinkedHashMap-源码分析

7-3、Java-Map-LinkedHashMap-小结

  1. 1LinkedHashMap-小结:
  2. 1-1LinkedHashMap底层是HashMap,与HashMap没太大的区别,重写了部分方法,在HashMap中一些空的实现,LinkedHashMap都做了实现,扩展了HashMap类的功能。
  3. 1-1-1LlinkedHashMapiterator也是遍历的双向链表。
  4. 1-1-2LinkedHashMap可以保存元素的插入顺序,顺序有两种方式一种是按照插入顺序排序,一种按照访问做排序。默认以插入顺序排序,性能比HashMap略低,线程也是不安全的。
  5. 1-2LinkedHashMap只有在使用三个参数的构造方法并制定accessOrdertrue时,才有顺序,不指定那么和HashMap基本没什么大的区别。
  6. 1-3LinkedHashMap HashMap数据结构-红黑树之上,通过维护一条双向链表,实现了散列数据结构的有序遍历。

8、Java-Map-WeakHashMap-概述

  1. 1WeakHashMap-概述:
  2. 1-1WeakHashMap功能与HashMap没什么区别,但WeakHashMap是基于弱引用的(java.lang.ref包下的弱引用WeakReference)。
  3. 1-1-1WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null,不允许重复。
  4. 1-1-2WeakHashMap的键是"弱键",对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
  5. 1-2WeakHashMap是不同步的,非线程安全的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap
  6. 2WeakHashMap-继承关系:
  7. public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {}

8-1、Java-Map-WeakHashMap-继承关系-示意图

image.png

8-2、Java-Map-WeakHashMap-工作原理

  1. 1WeakHashMap-工作原理:
  2. 1-1"弱键"的原理:通过WeakReferenceReferenceQueue实现的。 WeakHashMapkey是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的"弱键"
  3. 1-2"弱键"删除步骤:
  4. 1-2-1WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
  5. 1-2-2、当某"弱键"不再被其它对象引用,并被GC回收时。在GC回收该"弱键"时,这个"弱键"也同时会被添加到ReferenceQueue(queue)队列中。
  6. 1-2-3、当下一次我们需要操作WeakHashMap时,会先同步tablequeuetable中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
  7. 2WeakHashMap-使用场景:
  8. 2-1、保证缓存命中率:
  9. 2-1-1、整个缓存是一个key-value结构的(以键值对缓存为例),HashMap作为强引用对象在没有主动将key删除是不会被JVM回收的,这样HashMap中的对象就会越积越多直到OOM错误。
  10. 2-1-2WeakHashMap内部是通过弱引用来管理entry的,将一对key, value放入到 WeakHashMap 里并不能避免该key值被GC回收。
  11. 3WeakHashMap-应用场景:
  12. 3-1、实现本地缓存:
  13. 3-1-1WeakhashMap实现一个线程安全的基于LRU本地缓存。
  14. 3-1-2、也可以使用 基于 LinkedHashMap 的实现缓存工具类,基于volatile 实现LRU策略线程安全缓存。
  15. 3-1-3、配合事务进行使用,存储事务过程中的各类信息,结构:
  16. WeakHashMap<String,Map<K,V>> transactionCache;
  17. 3-1-3-1keyString类型,可以用来标志区分不同的事务,起到一个事务id的作用。value是一个map,可以是一个简单的HashMap或者LinkedHashMap,用来存放在事务中需要使用到的信息。
  18. 3-1-3-2、在事务开始时创建一个事务id,并用它来作为key,事务结束后,将这个强引用消除掉,这样既能保证在事务中可以获取到所需要的信息,又能自动释放掉map中的所有信息。
  19. 3-1-4、在Tomcat的工具类里,有这样一种实现。基于LRU策略。
  20. package org.apache.tomcat.util.collections;
  21. public final class ConcurrentCache<K, V> {
  22. private final int size;
  23. private final Map<K, V> eden;
  24. private final Map<K, V> longterm;
  25. public ConcurrentCache(int size) {
  26. this.size = size;
  27. this.eden = new ConcurrentHashMap(size);
  28. this.longterm = new WeakHashMap(size);
  29. }
  30. public V get(K k) {
  31. V v = this.eden.get(k);
  32. if (v == null) {
  33. synchronized(this.longterm) {
  34. v = this.longterm.get(k);
  35. }
  36. if (v != null) {
  37. this.eden.put(k, v);
  38. }
  39. }
  40. return v;
  41. }
  42. public void put(K k, V v) {
  43. if (this.eden.size() >= this.size) {
  44. synchronized(this.longterm) {
  45. this.longterm.putAll(this.eden);
  46. }
  47. this.eden.clear();
  48. }
  49. this.eden.put(k, v);
  50. }
  51. }

8-3、Java-Map-WeakHashMap-源码分析

容器(Map)-WeakHashMap-源码分析

8-4、Java-Map-WeakHashMap-小结

  1. 1WeakHashMap-小结:
  2. 1-1WeakHashMap是一个会自动清除EntryMap
  3. 1-2WeakHashMap的操作与HashMap完全一致。
  4. 1-3、[!!!]WeakHashMap内部数据结构是数组+链表。
  5. 1-4WeakHashMap常被用作缓存。

9、Java-Map-Hashtable-概述

  1. 1Hashtable-概述
  2. 1-1HashTable:也是一个散列表,键值对或者关联数组
  3. 1-2HashTable:其中的keyvalue键值对均为object类型,支持任何类型的键值对,每个元素都存储于DictionaryEntry对象中的键值对。键不能为空引用。
  4. 1-3、查找速度快,遍历相对慢,键值不能有空指针和重复数据。
  5. 2Hashtable-继承关系:
  6. public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {}

9-1、Java-Map-Hashtable-继承关系-示意图

image.png

9-2、Java-Map-Hashtable-工作原理

  1. 1Hashtable-工作原理:
  2. 1-1Hashtable继承Dictionary类,其中Dictionary类是任何可将键映射到相应值的类(如 Hashtable)的抽象父类,每个键和值都是对象。
  3. 1-2、在任何一个 Dictionary 对象中,每个键至多与一个值相关联。Map"key-value键值对"接口。
  4. 1-3Hashtable是通过"拉链法"实现的哈希表:它包括几个重要的成员变量:table, count, threshold, loadFactor, modCount

9-3、Java-Map-Hashtable-源码分析

容器(Map)-Hashtable-源码分析

9-4、Java-Map-Hashtable-小结

  1. 1Hashtable-小结:
  2. 1-1Hashtable继承的是Dictionary类,HashMap继承的是AbstractMap,但两者都实现了Map接口。
  3. 1-2、在Hashtable中有类似put(null,null)的操作,编译时可以通过,因为keyvalue都是Object类型,但运行时会抛出NullPointerException异常。
  4. 1-3HashMap仅支持Iterator的遍历方式,Hashtable支持IteratorEnumeration两种遍历方式。
  5. //通过Enumeration来遍历Hashtable
  6. Enumeration<String> enu = table.keys();
  7. while(enu.hasMoreElements()) {
  8. System.out.println("Enumeration:"+table.keys()+" "+enu.nextElement());
  9. }
  10. 1-4JDK8之前的版本中,Hashtable是没有fast-fail机制的。在JDK8及以后的版本中 HashTable也是使用fast-fail的。
  11. if (modCount != mc)
  12. throw new ConcurrentModificationException();

10、Java-Map-EnumMap-概述

  1. 1EnumMap-概述:
  2. 1-1EnumMap是一个用于存储 key 为枚举类型的 map,底层使用数组实现(KV 双数组),其key必须为Enum类型,两个数组,一个数组keyUniverse存储key,另一个数组vals存储val,两个数组通过下标对应起来。
  3. 1-2、所有的key都必须是单个枚举类的枚举值,创建EnumMap 时必须显示或隐式指定他对应的枚举类。
  4. EnumMap enumMap = new EnumMap<>(Season.class);
  5. 1-3EnumMapkey不允许为nullvalue可以为null,按照keyenum中的顺序进行保存,非线程安全。
  6. 1-4EnumMap中,是根据key 的自然顺序(枚举值在枚举类中的定义顺序) 来维护key-value的顺序。
  7. 2EnumMap-继承关系:
  8. public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable {}

10-1、Java-Map-EnumMap-继承关系-示意图

image.png

10-2、Java-Map-EnumMap-工作原理

  1. 1EnumMap-工作原理
  2. 1-1EnumMap是一个用于存储 key 为枚举类型的 map,底层使用数组实现(KV 双数组),其key必须为Enum类型,两个数组,一个数组keyUniverse存储key,另一个数组vals存储val,两个数组通过下标对应起来。
  3. 1-2key 数组会在构造函数中根据 keyType 进行初始化,下面我们会看到。当 EnmumMap value null 时会特殊处理为一个 Object 对象。
  4. 2EnumMap-应用场景:
  5. 2-1、如果作为key的对象是enum类型,那么,还可以使用Java集合库提供的一种EnumMap,它在内部以一个非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费。
  6. 2-2、举例Demo
  7. public static void main(String[] args) {
  8. Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
  9. map.put(DayOfWeek.MONDAY, "星期一");
  10. map.put(DayOfWeek.TUESDAY, "星期二");
  11. map.put(DayOfWeek.WEDNESDAY, "星期三");
  12. map.put(DayOfWeek.THURSDAY, "星期四");
  13. map.put(DayOfWeek.FRIDAY, "星期五");
  14. map.put(DayOfWeek.SATURDAY, "星期六");
  15. map.put(DayOfWeek.SUNDAY, "星期日");
  16. System.out.println(map);
  17. System.out.println(map.get(DayOfWeek.MONDAY));
  18. }

10-3、Java-Map-EnumMap-源码分析

容器(Map)-EnumMap-源码分析

10-4、Java-Map-EnumMap-小结

  1. 1EnumMap-小结:
  2. 1-1、添加元素方法-小结
  3. 1-1-1EnmuMap 添加键值对并没有扩容操作,因为一个枚举类型到底有多少元素在代码运行阶段是确定的,在构造函数中已经对 key 数组进行了初始化与赋值,value 数组的大小也已经被确定。
  4. 1-1-2、还有一个需要注意的问题,在上面的 .put()方法中只对 value 进行了处理,并没有处理 key,原因就是 key 数组在构造函数中已经被赋值了。
  5. 1-2EnumMap 存储键值对时并不会根据 key 获取对应的哈希值,enum 本身已经提供了一个 ordinal() 方法,该方法会返回具体枚举元素在枚举类中的位置(从 0 开始), 因此一个枚举元素从创建就已经有了一个唯一索引与其对应,这样就不存在哈希冲突的问题了。
  6. 1-3key 数组自从在构造函数中完成初始化之后就没有执行过增删改的操作,就算不添加任何键值对,也能根据其迭代器获取所有的 key,因为 key 在构造函数中已经被赋值。
  7. /**
  8. EnumMap 的 hasNext() 方法中对 value 做了非空判断
  9. */
  10. public boolean hasNext() {
  11. // 循环中会略过 value 数组中为 null 的情况
  12. while (index < vals.length && vals[index] == null)
  13. index++;
  14. return index != vals.length;
  15. }

11、Java-Map-Properties-概述

  1. 1Properties-概述
  2. 1-1Properties(Java.util.Properties),该类主要用于读取Java的配置文件,不同的编程语言有自己所支持的配置文件,配置文件中很多变量是经常改变的,为了方便用户的配置,能让用户够脱离程序本身去修改相关的变量设置。
  3. 1-2、继承了Hashtable
  4. public class Properties extends Hashtable<Object,Object> {}

11-1、Properties类-API

  1. 1、代码示例
  2. Properties pps = new Properties();
  3. //Properties ps = System.getProperties();
  4. 2、在线API
  5. [http://www.matools.com/api/java7]
  6. [https://tool.oschina.net/apidocs/apidoc?api=jdk-zh]
  1. https://www.pdai.tech/md/java/collection/java-map-LinkedHashMap&LinkedHashSet.html
  1. hashMap
  2. https://www.cnblogs.com/LiaHon/p/11142958.html
  3. https://www.cnblogs.com/LiaHon/p/11149644.html
  4. removeTreeNode方法具体实现可参考 TreeMap原理实现及常用方法
  5. afterNodeRemoval方法具体实现可参考LinkedHashMap如何保证顺序性
  6. Float.isNaN(loadFactor)
  7. 遍历树查找元素:.getTreeNode(hash, key)
  8. 红黑树:
  9. https://www.cnblogs.com/LiaHon/p/11203229.html
  10. https://www.cnblogs.com/skywang12345/p/3245399.html
  11. 线性时间排序
  12. https://www.cnblogs.com/shixisheng/p/6840763.html
  13. 将非线程安全类包装为线程安全的类,方法:
  14. https://blog.csdn.net/yingfeng612/article/details/79903926
  15. JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用、软引用、弱引用、虚引用4 种。
  16. private T referent; /* Treated specially by GC */
  17. volatile ReferenceQueue<? super T> queue;
  18. ReferenceWeakReference
  19. 二次散列?
  20. 掩码?
  21. length必定是2的幂,所以减1后就变成了掩码,再进行与操作就能直接得到hashcode mod length的结果。
  22. h & (length-1) = h/length
  23. 快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你
  24. //桶的索引是用key的hash值和0x7FFFFFFF做与运算,最后对数组长度取余
  25. //&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而 hash & 0x7FFFFFFF 后,只有符号外改变,而后面的位都不变。
  26. int index = (hash & 0x7FFFFFFF) % tab.length;
  27. HashtableHashMap的区别详解:https://blog.csdn.net/wangxing233/article/details/79452946?utm_source=blogxgwz5
  28. 通过 JavaLangAccess SharedSecrets 来获取 JVM 中对象实例