HashMap 底层原理是什么
- HashMap是一个键值对形式的集合,源码中每一个键值对用Node<K,V>表示,Node<K,V>是一个内部类,实现了Map.Entry<K,V>,Node内部类中有4个属性,分别是hash、key、value、next,key是键,value是值,next存储的是下一个元素的引用
- HashMap的数据结构是 数组+(链表或红黑树),数组查询效率高,增删效率低,链表查询效率低,增删效率高,HashMap使用这两种相结合,使增删查的效率都很高
- HashMap存储元素时,首先会计算key的hashCode值,然后利用hashCode与数组的长度取模,得到的值就是存储的索引位置,如果索引处没有值,直接存入元素,如果有值,使用equals()来比较值是否相等,相等则直接覆盖,不相等则会使用链表的形式存储该元素;当链表的长度大于等于8的时候,判断数组长度是否小于64,如果小于64就会进行数组扩容,链表不会转红黑树,如果数组长度大于64,链表会直接转为红黑树存储,因为红黑树是一颗自平衡的二叉搜索树,查询效率要比单链表高
- HashMap有两个重要的参数:初始容量大小和加载因子,初始容量大小默认16,加载因子0.75,当集合中元素的个数大于等于初始容量大小乘以加载因子,就会扩容1倍,还有一种扩容情况就是链表长度大于等于8,但是数组长度小于64,也会扩容,扩容需要对数组中的元素重新计算hashCode,比较消耗性能
- 对上面第三点中的计算索引位置做补充,jdk1.8的hashMap中,是先对key进行hash计算,公式
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
,然后用(数组长度-1)& 前面计算出的hash值
说一说JVM的内存结构
- 类加载器子系统(Class Loader):负责装载.class文件,三步骤:装载->链接->初始化
- 堆(Heap):jvm中最大的内存区域,所有线程共享,存放对象的实例和数组
- 方法区(Method Area):所有线程共享,存放类信息(版本、方法、属性等)、静态变量、常量、运行时常量池
- jvm栈(VM Stack):为 JVM 执行 Java 方法服务,线程私有
- 本地方法栈(Native Method Stack):为 JVM 使用到的 Native 方法服务,本地方法都是用C和C++实现的,线程私有
- 程序计数器(Program Counter Register)::较小的内存空间,线程私有,当前线程所执行字节码文件的行号指示器,执行下一个将要执行的指令代码,由执行引擎来读取;如果线程执行 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是 Native 方法,计数器值为Undefined
- 执行引擎(Execution Engine):执行字节码文件
- 本地库接口(Native Interface):可以调用非java代码(C或C++)的接口
说一说线程池各个构造参数的作用
- corePoolSize:核心线程数,即使空闲着,也不会销毁
- maximumPoolSize:线程池中允许的最大线程数
- keepAliveTime:当线程池中的线程数大于核心线程数,多余的空闲线程,空闲时间达到keepAliveTime时,多余线程将被销毁
- unit:keepAliveTime的时间单位
- workQueue:在执行任务之前用于保存任务的队列,此队列将只保存由execute方法提交的Runnable任务
- threadFactory:执行程序创建新线程时要使用的工厂,使用默认的即可
- handler:当执行因达到线程边界和队列容量而阻塞时使用的处理程序(拒绝策略)
说一说线程池的执行流程
提交任务,线程池先判断当前线程数是否小于核心线程数,如果是就创建核心线程执行任务,如果否就再判断任务队列是否已满,如果队列没满,就放入队列中,如果满了,再判断当前线程数是否小于最大线程数,小于就创建非核心线程来执行任务,大于最大线程数,就采用拒绝策略
什么是ioc
ioc指的是控制反转,一种设计思想,DI是具体实现方式,可以注入属性,建立对象间的依赖关系。IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖,以前我们创建对象都是主动创建(new一个对象),控制权在程序员手上,现在交给IOC容器控制创建对象,实现了控制的反转