HashMap

参考:https://www.cnblogs.com/zengcongcong/p/11295349.html
https://zhuanlan.zhihu.com/p/79507868
http://baijiahao.baidu.com/s?id=1665667572592680093&wfr=spider&for=pc

HashMap 就是一个用来存储 键值对 的集合

一、HashMap 整和了数组 & 链表的优点
数组的特点:

1、优点:查询块–在内存中一块连续的存储空间,使用数组下标可随机访问元素
2、缺点:插入和删除慢–插入和删除时会涉及到大量元素的移动。以及扩容比较复杂

链表的特点:

1、优点:插入删除快–链式结构找到元素删除之后,直接修改next 指向即可
2、缺点:查找慢–每次查找都要遍历链表

而 HashMap 底层实现则是采用了 数组、链表、树结构,如下图所示:
在这里插入图片描述
在这里插入图片描述

put 方法

1、是否需要初始化

需要则先进行 resize() 初始化,默认 16

2、获取数据下标

构造(k,v) node 节点之后,根据底层的 hashcode() 函数 对 k 进行计算获取到 hash 值,然后再对 hash 值进行计算(% 上数组长度),得到数组下标。

3、存储元素

1、数组下标位置没有元素的时候,那直接将该节点放入下标位置,如果数组元素数量达到扩容要求(数量 /长度 >= 加载因子) ,则resize进行扩容 2 倍
2、数组下标有元素
1、且存储的是链表结构,则遍历链表,若存在 equals(k) 为 true,则覆盖该元素,不存在则进行链表的尾部插入。如果链表的长度 >=8 时(为什么是8 ,数学上的波松分布问题),需要转换为树结构
3、且存储的是树结构,若树结构中存在 equals(k) 为 true,则覆盖该元素,不存在则进行树的插入操作。

get 方法:

1、获取数组下标

根据底层的 hashcode() 函数 对 k 进行计算获取到 hash 值,然后再对 hash 值进行计算(% 上数组长度),得到数组下标。

2、获取元素

1、数组下标没有元素,返回 null
2、数组下标有元素

1、且存储的是链表结构,则在链表中进行查找 equals(k) 为 true,存在则返回对用 v,否则返回 null
2、且存储的是树结构,则在树中进行查找 equals(k) 为 true,存在则返回对用 v,否则返回 null

HashMap 的 特点:

1、存取效率高

如上底层实现结构,数组 和 链表配合
增删时结合了链表的增删,避免了数组的移动元素。
查询时结合了数组的下标访问,部分遍历。

2、需要扩容

因为采用了数组结构,不可避免需要扩容,扩容是一件消耗很大的事情,重新开辟空间,对每一个元素重新计算 hash 存储。

3、线程不安全

为了提高效率。内部没有实现锁机制,hashTable 是线程安全的实现了 synchronized

4、为什么扩容至 2 倍

其实在判断下标时时采用 (n-1)& hash,其中 n 是数组长度。当数组长度为 2的x次幂时,刚好 n - 1 的二进制表示每一位都是1, 执行 & 运算时能更好的均匀分布。
6. 举例:当HashMap的容量是16时,它的二进制是10000,(n-1)的二进制是01111,与hash值得计算结果如下: 11111

自己的使用小 trips:
如果自己程序中 v 可能会存在 null,判断是否存在 k 的时候,不能使用 get(key) == null
应该使用 continesKey(key) == null
源码中的说明

这段是抄过来的,哈哈哈哈哈哈哈哈哈:

12.HashMap和HashTable的区别

相同点:都是存储key-value键值对的

不同点:

  • HashMap允许Key-value为null,hashTable不允许;
  • hashMap没有考虑同步,是线程不安全的。hashTable是线程安全的,给api套上了一层synchronized修饰;
  • HashMap继承于AbstractMap类,hashTable继承与Dictionary类。
  • 迭代器(Iterator)。
    • HashMap的迭代器(Iterator)是fail-fast迭代器,所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException
    • 而Hashtable的enumerator迭代器不是fail-fast的。
  • 容量的初始值和增加方式都不一样
    • HashMap默认的容量大小是16;增加容量时,每次将容量变为"原始容量x2"。
    • Hashtable默认的容量大小是11;增加容量时,每次将容量变为"原始容量x2 + 1";
  • 添加key-value时的hash值算法不同
    • HashMap添加元素时,是使用自定义的哈希算法。
    • Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。

二、map 常见的几种遍历方法

  1. 迭代器
  2. ForEach 遍历
  3. lambda 表达式遍历
  4. StreamsApi 遍历
public static void test1() {
        HashMap<Student, Integer> studentInfoMap = new HashMap<>();
        studentInfoMap.put(new Student("小张", 10), 80);
        studentInfoMap.put(new Student("小王", 9), 88);
        studentInfoMap.put(new Student("小李", 11), 90);

        log.info("for 循环遍历 map, keySet");
        for (Student student : studentInfoMap.keySet()) {
            log.info("key: {}, value:{}",student.toString(),studentInfoMap.get(student));
        }

        log.info("for 循环遍历 map, entrySet");
        for (Map.Entry<Student, Integer>  entry: studentInfoMap.entrySet()) {
            log.info("key: {}, value:{}",entry.getKey(),entry.getValue());
        }

        log.info("迭代器遍历 map, keySet");
        Iterator<Student> iteratorKeySet = studentInfoMap.keySet().iterator();
        while(iteratorKeySet.hasNext()){
            Student student = iteratorKeySet.next();
            log.info("key: {}, value:{}",student.toString(),studentInfoMap.get(student));
        }

        log.info("迭代器遍历 map, entrySet");
        Iterator<Map.Entry<Student, Integer>> iteratorEntrySet = studentInfoMap.entrySet().iterator();
        while(iteratorEntrySet.hasNext()){
            Map.Entry<Student, Integer> next = iteratorEntrySet.next();
            log.info("key: {}, value:{}",next.getKey(),next.getValue());
        }

        log.info("使用 lamb 表达式遍历 map");
        studentInfoMap.forEach((key,value) ->{
            log.info("key: {}, value:{}",key,value);
        });

        log.info("使用 streamApi 遍历 map, entrySet");
        studentInfoMap.entrySet().stream().forEach( studentIntegerEntry -> {
            log.info("key: {}, value:{}",studentIntegerEntry.getKey(),studentIntegerEntry.getValue());

        });

        log.info("使用 streamApi 遍历 map, keySet");
        studentInfoMap.keySet().stream().forEach( student -> {
            log.info("key: {}, value:{}",student,studentInfoMap.get(student));
        });

        log.info("使用 streamApi 多线程遍历 map, entrySet");
        studentInfoMap.entrySet().parallelStream().forEach( studentIntegerEntry -> {
            log.info("key: {}, value:{}",studentIntegerEntry.getKey(),studentIntegerEntry.getValue());
        });
        
    }
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值