image.png

1、Map接口及其多个实现类的对比

  1. import org.junit.Test;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. /**
  5. * 一、Map的实现类的结构:
  6. * |----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)
  7. * |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
  8. * |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
  9. * 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
  10. * 对于频繁的遍历操作,此类执行效率高于HashMap。
  11. * |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
  12. * 底层使用红黑树
  13. * |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
  14. * |----Properties:常用来处理配置文件。key和value都是String类型
  15. *
  16. *
  17. * HashMap的底层:数组+链表 (jdk7及之前)
  18. * 数组+链表+红黑树 (jdk 8)
  19. *
  20. * 面试题:
  21. * 1. HashMap的底层实现原理?
  22. * 2. HashMap 和 Hashtable的异同?
  23. * 3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)
  24. *
  25. */
  26. public class MapTest {
  27. @Test
  28. public void test(){
  29. Map map = new HashMap();
  30. // map = new Hashtable();
  31. map.put(null,123);
  32. }
  33. }

2、Map中存储的key-value的特点

  • Map与Collection并列存在。用于保存具有映射关系的数据:key-value
  • Map中的key和value都可以是任何引用类型的数据
  • Map 中的key 用Set来存放,不允许重复,即同一个Map 对象所对应的类,须重写hashCode()和equals()方法
  • 常用String类作为Map的“键”
  • key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的、确定的value
  • Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是Map接口使用频率最高的实现类 ```java /**
    • 二、Map结构的理解:
    • Map中的key:无序的、不可重复的,使用Set存储所有的key —-> key所在的类要重写equals()和hashCode() (以HashMap为例)
    • Map中的value:无序的、可重复的,使用Collection存储所有的value —->value所在的类要重写equals()
    • 一个键值对:key-value构成了一个Entry对象。
    • Map中的entry:无序的、不可重复的,使用Set存储所有的entry /
  1. <a name="mPhDm"></a>
  2. # 3、Map实现类之一:HashMap
  3. - **HashMap是Map接口使用频率最高的实现类**
  4. - 允许使用null键和null值,与HashSet一样,不保证映射的顺序
  5. - 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
  6. - 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
  7. - 一个key-value构成一个entry
  8. - 所有的entry构成的集合是Set:无序的、不可重复的
  9. - HashMap判断两个key相等的标准是:两个key通过equals()方法返回true,hashCode值也相等。
  10. - HashMap判断两个value相等的标准是:两个value 通过equals()方法返回true。
  11. <a name="LSduu"></a>
  12. ## 3.1、HashMap的底层实现原理
  13. **JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)**<br />**JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。**<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22472217/1632894842783-62f3c22e-27c4-48b8-94bc-00e4b09362e3.png#clientId=uc1e0a5a0-8a4f-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ua70d208b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=628&originWidth=1638&originalType=url&ratio=1&rotation=0&showTitle=false&size=362443&status=done&style=none&taskId=u01c74e2f-2d02-474f-8195-a5b775aee4a&title=)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22472217/1632894852196-ba1aaab6-c081-4cc1-896c-de9c7aba4bb6.png#clientId=uc1e0a5a0-8a4f-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u7429993c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=621&originWidth=1624&originalType=url&ratio=1&rotation=0&showTitle=false&size=494191&status=done&style=none&taskId=u15d08b30-7731-4359-a39f-cf80d742a13&title=)<br />HashMap源码中的重要常量
  14. ```java
  15. /*
  16. * DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
  17. * DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
  18. * threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
  19. * TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
  20. * MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
  21. */

3.1.1、HashMap在JDK7中的底层原理实现

  • HashMap的内部存储结构其实是数组加链表的结合。当实例化完成一个HashMap时,系统会创建一个长度为16的Entry类型的数组,这个长度的在哈希表中被称为容量,这个数组中可存放的元素的位置我们称之为桶(bucket),每个桶都有自己的索引,系统会根据索引快速查找桶中的元素
  • 每个bucket存储一个元素,即一个Entry对象,但是每一个对象可以带有一个引用变量,用于指向下一个元素 ,因此在一个桶中,就可能生出一个Entry链。而且新添加的元素作为链表的head
  • 添加元素的过程
    • 向HashMap中添加entry1(key,value),首先需要计算entry1中key的哈希值(根据key 所在类中的hashCode()计算得到),此哈希值经过一些算法处理以后得到底层Entry[]数组中要存的位置i.
    • 如果此位置上没有数据,那么entry1元素就添加成功
    • 如果此位置上已经存在数据entry2(或还有链表的存在entry3,entry4),则需要再次通过比较entry1中key的hash值和其他的entry的hash值。
    • 如果彼此哈希值不同,则直接添加成功
    • 如果哈希值相同,则需要继续通过equals()方法判断是否相同,如果不相同,则添加成功
    • 如果相同,则将原有数据的value进行替换

在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。

  1. /*
  2. * 三、HashMap的底层实现原理?以jdk7为例说明:
  3. * HashMap map = new HashMap():
  4. * 在实例化以后,底层创建了长度是16的一维数组Entry[] table。
  5. * ...可能已经执行过多次put...
  6. * map.put(key1,value1):
  7. * 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
  8. * 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
  9. * 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据
  10. * 的哈希值:
  11. * 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
  12. * 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
  13. * 如果equals()返回false:此时key1-value1添加成功。----情况3
  14. * 如果equals()返回true:使用value1替换value2。
  15. *
  16. * 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
  17. *
  18. * 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
  19. *
  20. */
  21. /**
  22. * HashMap的扩容
  23. * 当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,
  24. * 因为数组的长度是固定的。所以为了提高查询的效率,
  25. * 就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,
  26. * 最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,
  27. * 并放进去,这就是resize。
  28. *
  29. * 那么HashMap什么时候进行扩容呢?
  30. * 当HashMap中的元素个数超过数组大小(数组总大小length,
  31. * 不是数组中个数size)*loadFactor时,就 会 进 行 数 组 扩 容,
  32. * loadFactor的默认值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。
  33. * 也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,
  34. * 那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,
  35. * 也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍,
  36. * 然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,
  37. * 所以如果我们已经预知HashMap中元素的个数,
  38. * 那么预设元素的个数能够有效的提高HashMap的性能。
  39. */

3.3.2、HashMap在JDK8中的底层实现原理

  • 在jdk8后,创建hashMap实例后,底层并没有去创建一个长度为16的数组,而是等到首次进行put()方法是底层在去创建一个长度为16的数组
  • jdk8底层的数组是Node[]类型,而非Entry[]
  • jdk7底层结构只有数组+链表,jdk8 中的底层是 数组 + 链表 + 红黑树
  • 当数组的某一个索引位置上的元素以链表形式存在数据个数大于8 并且当前数组的长度大于64时,此时索引上位置上的所有数据都改为使用红黑树进行存储

    4、Map集合中的常用方法

    ```java import org.junit.Test;

import java.util.; /*

  • 五、Map中定义的方法:
  • 添加、删除、修改操作:
  • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
  • void putAll(Map m):将m中的所有key-value对存放到当前map中
  • Object remove(Object key):移除指定key的key-value对,并返回value
  • void clear():清空当前map中的所有数据
  • 元素查询的操作:
  • Object get(Object key):获取指定key对应的value
  • boolean containsKey(Object key):是否包含指定的key
  • boolean containsValue(Object value):是否包含指定的value
  • int size():返回map中key-value对的个数
  • boolean isEmpty():判断当前map是否为空
  • boolean equals(Object obj):判断当前map和参数对象obj是否相等
  • 元视图操作的方法:
  • Set keySet():返回所有key构成的Set集合
  • Collection values():返回所有value构成的Collection集合
  • Set entrySet():返回所有key-value对构成的Set集合 / public class MapTest {

    /**

    • 元素查询的操作:
    • Object get(Object key):获取指定key对应的value
    • boolean containsKey(Object key):是否包含指定的key
    • boolean containsValue(Object value):是否包含指定的value
    • int size():返回map中key-value对的个数
    • boolean isEmpty():判断当前map是否为空
    • boolean equals(Object obj):判断当前map和参数对象obj是否相等 */ @Test public void test4(){ Map map = new HashMap(); map.put(“AA”,123); map.put(45,123); map.put(“BB”,56); // Object get(Object key) System.out.println(map.get(45)); //containsKey(Object key) boolean isExist = map.containsKey(“BB”); System.out.println(isExist);

    isExist = map.containsValue(123); System.out.println(isExist);

    map.clear();

    System.out.println(map.isEmpty()); }

    /**

    • 添加、删除、修改操作:
    • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
    • void putAll(Map m):将m中的所有key-value对存放到当前map中
    • Object remove(Object key):移除指定key的key-value对,并返回value
    • void clear():清空当前map中的所有数据 */ @Test public void test3(){ Map map = new HashMap(); //添加 map.put(“AA”,123); map.put(45,123); map.put(“BB”,56); //修改 map.put(“AA”,87);

    System.out.println(map);

    Map map1 = new HashMap(); map1.put(“CC”,123); map1.put(“DD”,456); map.putAll(map1);

    System.out.println(map);

    //remove(Object key) Object value = map.remove(“CC”); System.out.println(value); System.out.println(map);

    //clear() map.clear();//与map = null操作不同 System.out.println(map.size()); System.out.println(map); } }

  1. ```java
  2. import org.junit.Test;
  3. import java.util.*;
  4. /**
  5. * 五、Map中定义的方法:
  6. * 添加、删除、修改操作:
  7. * Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
  8. * void putAll(Map m):将m中的所有key-value对存放到当前map中
  9. * Object remove(Object key):移除指定key的key-value对,并返回value
  10. * void clear():清空当前map中的所有数据
  11. * 元素查询的操作:
  12. * Object get(Object key):获取指定key对应的value
  13. * boolean containsKey(Object key):是否包含指定的key
  14. * boolean containsValue(Object value):是否包含指定的value
  15. * int size():返回map中key-value对的个数
  16. * boolean isEmpty():判断当前map是否为空
  17. * boolean equals(Object obj):判断当前map和参数对象obj是否相等
  18. * 元视图操作的方法:
  19. * Set keySet():返回所有key构成的Set集合
  20. * Collection values():返回所有value构成的Collection集合
  21. * Set entrySet():返回所有key-value对构成的Set集合
  22. *
  23. * 总结:常用方法:
  24. * 添加:put(Object key,Object value)
  25. * 删除:remove(Object key)
  26. * 修改:put(Object key,Object value)
  27. * 查询:get(Object key)
  28. * 长度:size()
  29. * 遍历:keySet() / values() / entrySet()
  30. *
  31. * 面试题:
  32. * 1. HashMap的底层实现原理?
  33. * 2. HashMap 和 Hashtable的异同?
  34. * 1.HashMap与Hashtable都实现了Map接口。由于HashMap的非线程安全性,效率上可能高于Hashtable。Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。
  35. * 2.HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。
  36. * 3.HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。
  37. * 4.Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。
  38. * 5.Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。
  39. *
  40. * 3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)
  41. *
  42. */
  43. public class MapTest {
  44. /**
  45. * 元视图操作的方法:
  46. * Set keySet():返回所有key构成的Set集合
  47. * Collection values():返回所有value构成的Collection集合
  48. * Set entrySet():返回所有key-value对构成的Set集合
  49. */
  50. @Test
  51. public void test5(){
  52. Map map = new HashMap();
  53. map.put("AA",123);
  54. map.put(45,1234);
  55. map.put("BB",56);
  56. //遍历所有的key集:keySet()
  57. Set set = map.keySet();
  58. Iterator iterator = set.iterator();
  59. while(iterator.hasNext()){
  60. System.out.println(iterator.next());
  61. }
  62. System.out.println("*****************");
  63. //遍历所有的values集:values()
  64. Collection values = map.values();
  65. for(Object obj : values){
  66. System.out.println(obj);
  67. }
  68. System.out.println("***************");
  69. //遍历所有的key-values
  70. //方式一:
  71. Set entrySet = map.entrySet();
  72. Iterator iterator1 = entrySet.iterator();
  73. while (iterator1.hasNext()){
  74. Object obj = iterator1.next();
  75. //entrySet集合中的元素都是entry
  76. Map.Entry entry = (Map.Entry) obj;
  77. System.out.println(entry.getKey() + "---->" + entry.getValue());
  78. }
  79. System.out.println("/");
  80. //方式二:
  81. Set keySet = map.keySet();
  82. Iterator iterator2 = keySet.iterator();
  83. while(iterator2.hasNext()){
  84. Object key = iterator2.next();
  85. Object value = map.get(key);
  86. System.out.println(key + "=====" + value);
  87. }
  88. }
  89. }

5、TreeMap两种添加方式的使用

  • TreeMap存储Key-Value 对时,需要根据key-value对进行排序。TreeMap可以保证所有的Key-Value 对处于有序状态。
  • TreeSet底层使用红黑树结构存储数据
  • TreeMap的Key的排序
    • 自然排序:TreeMap的所有的Key 必须实现Comparable接口,而且所有的Key应该是同一个类的对象,否则将会抛出ClasssCastException
    • 定制排序:创建TreeMap时,传入一个Comparator 对象,该对象负责对TreeMap中的所有key 进行排序。此时不需要Map 的Key实现Comparable 接口
  • TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0

    1. public class User implements Comparable{
    2. private String name;
    3. private int age;
    4. public User() {
    5. }
    6. public User(String name, int age) {
    7. this.name = name;
    8. this.age = age;
    9. }
    10. public String getName() {
    11. return name;
    12. }
    13. public void setName(String name) {
    14. this.name = name;
    15. }
    16. public int getAge() {
    17. return age;
    18. }
    19. public void setAge(int age) {
    20. this.age = age;
    21. }
    22. @Override
    23. public String toString() {
    24. return "User{" +
    25. "name='" + name + '\'' +
    26. ", age=" + age +
    27. '}';
    28. }
    29. @Override
    30. public boolean equals(Object o) {
    31. System.out.println("User equals()....");
    32. if (this == o) return true;
    33. if (o == null || getClass() != o.getClass()) return false;
    34. User user = (User) o;
    35. if (age != user.age) return false;
    36. return name != null ? name.equals(user.name) : user.name == null;
    37. }
    38. @Override
    39. public int hashCode() { //return name.hashCode() + age;
    40. int result = name != null ? name.hashCode() : 0;
    41. result = 31 * result + age;
    42. return result;
    43. }
    44. //按照姓名从大到小排列,年龄从小到大排列
    45. @Override
    46. public int compareTo(Object o) {
    47. if(o instanceof User){
    48. User user = (User)o;
    49. // return -this.name.compareTo(user.name);
    50. int compare = -this.name.compareTo(user.name);
    51. if(compare != 0){
    52. return compare;
    53. }else{
    54. return Integer.compare(this.age,user.age);
    55. }
    56. }else{
    57. throw new RuntimeException("输入的类型不匹配");
    58. }
    59. }
    60. }

    ```java import org.junit.Test;

import java.util.*;

public class TreeMapTest {

  1. /**
  2. * 向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
  3. * 因为要按照key进行排序:自然排序 、定制排序
  4. */
  5. //自然排序
  6. @Test
  7. public void test(){
  8. TreeMap map = new TreeMap();
  9. User u1 = new User("Tom",23);
  10. User u2 = new User("Jerry",32);
  11. User u3 = new User("Jack",20);
  12. User u4 = new User("Rose",18);
  13. map.put(u1,98);
  14. map.put(u2,89);
  15. map.put(u3,76);
  16. map.put(u4,100);
  17. Set entrySet = map.entrySet();
  18. Iterator iterator1 = entrySet.iterator();
  19. while (iterator1.hasNext()){
  20. Object obj = iterator1.next();
  21. Map.Entry entry = (Map.Entry) obj;
  22. System.out.println(entry.getKey() + "---->" + entry.getValue());
  23. }
  24. }
  25. //定制排序
  26. @Test
  27. public void test2(){
  28. TreeMap map = new TreeMap(new Comparator() {
  29. @Override
  30. public int compare(Object o1, Object o2) {
  31. if(o1 instanceof User && o2 instanceof User){
  32. User u1 = (User)o1;
  33. User u2 = (User)o2;
  34. return Integer.compare(u1.getAge(),u2.getAge());
  35. }
  36. throw new RuntimeException("输入的类型不匹配!");
  37. }
  38. });
  39. User u1 = new User("Tom",23);
  40. User u2 = new User("Jerry",32);
  41. User u3 = new User("Jack",20);
  42. User u4 = new User("Rose",18);
  43. map.put(u1,98);
  44. map.put(u2,89);
  45. map.put(u3,76);
  46. map.put(u4,100);
  47. Set entrySet = map.entrySet();
  48. Iterator iterator1 = entrySet.iterator();
  49. while (iterator1.hasNext()){
  50. Object obj = iterator1.next();
  51. Map.Entry entry = (Map.Entry) obj;
  52. System.out.println(entry.getKey() + "---->" + entry.getValue());
  53. }
  54. }

}

  1. <a name="OLL9l"></a>
  2. # 6、Hashtable
  3. - Hashtable是个古老的Map 实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
  4. - Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
  5. - 与HashMap不同,**Hashtable不允许使用null 作为key和value**
  6. - 与HashMap一样,Hashtable也不能保证其中Key-Value 对的顺序
  7. - Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。
  8. <a name="crxPw"></a>
  9. # 7、Properties处理属性文件
  10. - Properties 类是Hashtable的子类,该对象用于处理属性文件
  11. - 由于属性文件里的key、value都是字符串类型,所以**Properties 里的key和value都是字符串类型**
  12. - 存取数据时,建议使用setProperty(String key,Stringvalue)方法和getProperty(String key)方法
  13. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/22472217/1632966490599-50e7dbe6-9faf-47d6-afa9-139abbcacf62.png#clientId=u694a8737-2973-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u29fa0dca&margin=%5Bobject%20Object%5D&name=image.png&originHeight=294&originWidth=676&originalType=url&ratio=1&rotation=0&showTitle=false&size=23152&status=done&style=none&taskId=u606c67d4-124d-4c64-8d31-d89a35ddd37&title=)
  14. ```java
  15. import java.io.FileInputStream;
  16. import java.io.IOException;
  17. import java.util.Properties;
  18. public class PropertiesTest {
  19. //Properties:常用来处理配置文件。key和value都是String类型
  20. public static void main(String[] args){
  21. //快捷键:ALT+Shift+Z
  22. FileInputStream fis = null;
  23. try {
  24. Properties pros = new Properties();
  25. fis = new FileInputStream("jdbc.properties");
  26. pros.load(fis); //加载流对应文件
  27. String name = pros.getProperty("name");
  28. String password = pros.getProperty("password");
  29. System.out.println("name = " + name + ",password = " + password);
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. } finally {
  33. if(fis != null){
  34. try {
  35. fis.close();
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. }
  39. }
  40. }
  41. }
  42. }