• 吊打面试官,HashMap 这一篇就够了


    一、HashMap的简单使用

    HashMap集合的创建:

    Map map = new HashMap();

    使用put存储数据:

    1. map.put("张三","喜欢打游戏");
    2. map.put("李四","喜欢睡觉");
    3. map.put("王五","喜欢看电视");
    4. map.put("赵六","喜欢洗澡");

    使用get获取数据:

    1. System.out.println(map.get("张三"));
    2. System.out.println(map.get("李四"));

    通过Map.keySet使用iterator遍历HashMap:

    1. // 通过Map.keySet使用iterator遍历key,然后通过key得到对应的value值
    2. Iterator iterator = map.keySet().iterator();
    3. while (iterator.hasNext()) {
    4. String key = iterator.next();
    5. String value = map.get(key);
    6. System.out.println("key = " + key + ", value = " + value);
    7. }

    通过Map.entrySet使用iterator遍历HashMap:

    1. // 通过Map.entrySet使用iterator遍历key和value;注意 Set entrySet():返回所有key-value对构成的Set集合
    2. Iterator> entries = map.entrySet().iterator();
    3. while (entries.hasNext()) {
    4. Map.Entry entry = entries.next();
    5. System.out.println(entry);
    6. }

    通过Map.keySet遍历HashMap:

    1. // 通过Map.keySet遍历key,然后通过key得到对应的value值
    2. for (String key : map.keySet()) {
    3. System.out.println("key = " + key + ", value = " + map.get(key));
    4. }

    通过For-Each迭代entries,使用Map.entrySet遍历HashMap:

    1. // 使用For-Each迭代entries,通过Map.entrySet遍历key和value
    2. for (Map.Entry entry : map.entrySet()) {
    3. System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
    4. }

    使用lambda表达式ForEach遍历HashMap:

    1. // 使用lambda表达式forEach遍历
    2. map.forEach((k, v) -> System.out.println("key = " + k + ", value = " + v));

    二、HashMap的底层原理

    HashMap本身就是一个程序/工具类,主要用来存储数据

    程序=数据结构+算法

    数据结构:数组、链表、红黑树

    算法:哈希算法

    HashMap的数据结构在1.8之前是“数组+链表”

    数组:

    采用一段连续的存储单元来存储数据

    1. /**
    2. * 数组
    3. */
    4. public class MyArray {
    5. public static void main(String[] args) {
    6. Integer integer[] = new Integer[10];
    7. integer[0] = 0;
    8. integer[1] = 1;
    9. integer[9] = 2;
    10. integer[9] = 100;
    11. System.out.println(integer[9]);
    12. }
    13. }

    特点:查询o(1),删除o(N)

    总结:查询快,插入慢

    ArrayList的数据结构就是数组:

    链表:

    链表是一种物理存储单元上非连续、非顺序的存储结构

    1. /**
    2. * 链表
    3. */
    4. public class Node {
    5. public Node next;
    6. private Object data;
    7. public Node(Object data){this.data= data;}
    8. public static void main(String[] args) {
    9. Node head = new Node("睡觉");
    10. head.next = new Node("吃饭");
    11. head.next.next = new Node("打游戏");
    12. System.out.println(head.data);
    13. System.out.println(head.next.data);
    14. System.out.println(head.next.next.data);
    15. }
    16. }

    特点:插入、删除时间复杂度o(1),查找遍历时间复杂度o(N)

    总结:插入快,查找慢

    LinkedList的数据结构就是链表:

    哈希算法:

    1. /**
    2. * ascii码
    3. */
    4. public class AsciiCode {
    5. public static void main(String[] args) {
    6. char c[] = "lies".toCharArray();
    7. for (int i = 0; i < c.length; i++) {
    8. System.out.println((c[i])+":"+(int)c[i]);
    9. }
    10. }
    11. }

    “取模”的目的是为了节省空间.

    但如果两个字符串的ascii码计算结果相同,再进行取模,则会发生“哈希冲突”。.

    因为HashMap的数据结构包含了“链表”,而链表就是解决哈希冲突这一问题的关键。

    HashMap对产生哈希冲突的数据会以“链表”的形式来存储它们,其它数据则以“数组”的形式存储。

    HashMap的数据结构在1.8之后是“数组+链表+红黑树”

    由于“链表”查询速度慢的原因,所以新增了“红黑树”:

    如上图,这是一个以“链表”形式存储数据的图示

    假如我要查询“004”,“005”,“007”则会从001到007查询7次

    上图是一个以“红黑树”形式存储数据的图示

    红黑树结构特征:“左中右”对应“小中大”

    使用“红黑树”查询“004”,“005”,“007”则只需要要查询4次即可

    这里要注意,java1.8之后,HashMap默认的数据结构一开始仍然是“数组+链表”,但是当链表的阈值达到8的时候就会变成“数组+红黑树”

    查看HashMap源码,点进putVal方法:

    点进642行的常量,发现对应的数值就是8:

    再看putVal方法中的treeifyBin方法:

    此方法一开始执行的是“链表(执行Node)”,而随着条件的改变就变成了“红黑树(执行TreeNode)”:

    这里,你可能会好奇,明明知道了“链表”的查询速度慢,为什么一开始不使用“数组+红黑树”???

    这就好比“鱼和熊掌不可兼得”。

    因为“红黑树”在插入数据的时候需要做“数据左旋(也可以理解为数据位置交换)”的动作,所以“红黑树”的插入速度慢。

    而“链表”它的插入速度快,和红黑树相反,所以才有了“当链表的阈值达到8的时候就会变成“数组+红黑树””这一说法。

    不知道“阈值”是什么意思的小伙伴可以去百度一下。

    三、手写一个HashMap

    Map接口:

    1. /**
    2. * K V
    3. * @param
    4. * @param
    5. */
    6. public interface Map {
    7. V put(K k,V v);
    8. V get(K k);
    9. int size();
    10. interface Entry{
    11. K getKey();
    12. V getValue();
    13. }
    14. }

    HashMap,实现Map接口:

    1. /**
    2. * 手写实现HashMap
    3. * @param
    4. * @param
    5. */
    6. public class HashMap implements Map {
    7. Entry [] table =null;
    8. int size = 0;
    9. public HashMap() {
    10. table = new Entry[16];
    11. }
    12. /**
    13. * 1、key进行hash 取模算出index下标
    14. * 2、数组的对应节点对象 对象是否为空
    15. * 3、如果为空的话 赋值存储
    16. * 4、如果不为空的话 冲突 链表存储
    17. * 5、返回值
    18. * @param k
    19. * @param v
    20. * @return
    21. */
    22. @Override
    23. public V put(K k, V v) {
    24. //1、key进行hash 取模算出index下标
    25. int index = hash(k);
    26. //2、数组的对应节点对象 对象是否为空
    27. Entry entry = table[index];
    28. if (null == entry){
    29. //3、如果为空的话 赋值存储
    30. table[index]=new Entry<>(k,v,index,null);
    31. size++;
    32. }else {
    33. //4、如果不为空的话 冲突 链表存储
    34. table[index]=new Entry<>(k,v,index,entry);
    35. }
    36. //5、返回值
    37. return table[index].getValue();
    38. }
    39. private int hash(K k) {
    40. int index = k.hashCode()%16;
    41. return index>=0?index:-index;
    42. }
    43. /**
    44. * key 进行hash index下标
    45. * 是否为空 如果为空 直接返回null
    46. * 不为空 k 当前k进行比较
    47. * 如果相等 直接返回数据
    48. * 如果不相等 next是否为空
    49. * 如果为空 直接返回
    50. * 如果不为空
    51. * k是否key是否相等 直到相等为止
    52. * @param k
    53. * @return
    54. */
    55. @Override
    56. public V get(K k) {
    57. int index = hash(k);
    58. Entry entry = findValue(table[index],k);
    59. return entry==null?null:entry.getValue();
    60. }
    61. public Entry findValue(Entry entry,K k){
    62. if (k.equals(entry.getKey())||k==entry.getKey()){
    63. return entry;
    64. }else {
    65. if (entry.next!=null){
    66. return findValue(entry.next,k);
    67. }
    68. }
    69. return null;
    70. }
    71. @Override
    72. public int size() {
    73. return size;
    74. }
    75. class Entry implements Map.Entry {
    76. K k;V v;int index;Entry next;
    77. public Entry(K k, V v, int index, Entry next) {
    78. this.k = k;
    79. this.v = v;
    80. this.index = index;
    81. this.next = next;
    82. }
    83. @Override
    84. public K getKey() {
    85. return k;
    86. }
    87. @Override
    88. public V getValue() {
    89. return v;
    90. }
    91. }
    92. }

    测试类:

    1. public class Test {
    2. public static void main(String[] args) {
    3. HashMap map = new HashMap<>();
    4. map.put("张三","喜欢打游戏");
    5. map.put("王五","喜欢看电视");
    6. System.out.println(map.get("张三"));
    7. System.out.println(map.get("王五"));
    8. }
    9. }

    测试结果:

    1. 喜欢打游戏
    2. 喜欢看电视

  • 相关阅读:
    AB实验:科学归因与增长的利器
    格林公式推导
    Vue-springboot通讯录管理系统maven项目 idea
    lv17 安防监控项目实战 3
    LPDDR4详解
    在Visual Studio中查看C项目使用的C语言版本
    Java 类加载机制
    【解刊】IF9.9,CCF-C类,1区SCI神刊,2天见刊!
    Redis --- 安装教程
    MacOS系统中Java使用Opencv4.10.0库的编译过程和使用方法(附编译后的包)
  • 原文地址:https://blog.csdn.net/weixin_55076626/article/details/128008026