-
谈谈ConcurrentHashMap的扩容机制
- 1.7版本:
- 1.7版本的ConcurrentHashMap是基于Segment分段实现的
- 每个Segment相当于一个小型的HashMap
- 每个Segment内部会进行扩容,和HashMap的扩容逻辑类似
- 先生成新的数组,然后转移元素到新的数组中
- 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
- 1.8版本
- 1.8版本的ConcurrentHashMap不再基于Segment实现
- 当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容,那么该线程一起进行扩容
- 如果某个线程put时,发现没有进行扩容,则讲key——value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
- ConcurrentHashMap是支持多线程同时扩容的
- 扩容之前也先生成一个新的数组
- 再转移元素的时候,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或者多组的元素转移工作
- 1.7版本:
-
JDK1.7到JDK1.8 HashMap发生了什么变化
- 1.7中底层是数组+链表,1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap插入和查询的整体效率
- 1.7中链表插入使用的是头插法,1.8中链表的插入使用的是尾插法,因为1.8中插入key和value时需要判断链表的元素个数所以需要遍历链表统计链表的元素个数,所以正好使用尾插法
- 1.7中哈希算法比较复杂,存在各种右移与或运算,1.8中进行了简化,因为复杂的哈希算法就是为了提高散列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省CPU
-
HashMap的Put方法
- 根据key通过哈希算法与与运算得出数组下标
- 如果数组下标位置为空,则将key和value封装为Entry对象(1.7中是Entry对象,1.8中为node对象)并放入该位置
- 如果数组下标位置不为空,则需要分情况讨论
- JDK1.7:先判断是否需要扩容,如果需要扩容就先扩容,如果不需要扩容就生成Entry对象,并使用头插法添加到当前位置的链表中
- JDK1.8:则需要判断当前位置上的Node类型
- 如果是红黑树Node,则将key_value封装成一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value
- 如果是链表Node,则将key_value封装成一个链表Node并使用尾插法插入链表,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新生成的Node插入到链表尾部,此时当链表节点数大于等于8时,则将链表转成红黑树
- 将key_value封装成Node节点插入到红黑树或者链表后,再判断是否需要扩容,如果需要就扩容,不需要就结束Put方法
-
深拷贝和浅拷贝
- 深拷贝和浅拷贝指的是对象的拷贝, 一个对象中存在两种类型的属性,一种是基本的数据类型,一种是实例对象的引用
- 浅拷贝:只会拷贝基本数据类型的值以及实例对象的引用地址,不会复制引用地址所指的对象,即浅拷贝出来的对象,内部类属性指向的是同一个对象
- 深拷贝:既会拷贝基本数据类型的值,也会拷贝引用地址所指的对象,深拷贝的对象,内部属性所指向的不是同一个对象
-
HashMap的扩容机制
- 1.7版本
- 先生成新数组
- 遍历老数组的每个位置上的链表上的每个元素
- 取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
- 将元素添加到新数组中去
- 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
- 1.8版本
- 生成新数组
- 遍历老数组中的每个位置上的链表或红黑树
- 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新的数组中去
- 如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
- 统计每个下标位置的元素个数
- 如果该位置下的元素个数超过了8,则一定生成一个新的红黑树,并将根节点添加到新数组的对应位置
- 如果该位置下的元素个数没有超过8,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置
- 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
- 1.7版本
-
CopyOnWriteArrayList的底层原理是怎样的
- 首先CopyOnwrilAraylit内部也是用过数组来实现的,在向CopyoOnIWiriteARraylist添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行
- 并且,写操作会加锁,防止出现并发写入丢失数据的问题
- 写操作结束之后会把原数组指向新数组
- CopyOnWriteArraylist允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnIWiteArayist会比较占内存,同时可能读到的
数据不是实时最新的数据,所以不适合实时性要求很高的场景
-
什么是字节码?采用字节码的好处是什么?
- 编译器(favac)将Java源文件(*.java)文件编译成为字节码文件(*.class),可以做到一次编泽到处运行,windows上编译好的class文件,可以直接在Linux上运行,通过这种方式做到跨平台,不过Java的跨平台有一个前提条件,就是不同的操作系统上安装的JDK或JRE是不一样的,虽然字节码是通用的,但是需要把字节码解释成各个操作系统的机器码是需要不同的解释器的,所以针对各个操作系统需要有各自的JDK或JRE。
- 采用字节码的处,一方面实现了跨平台,另外一方面也提高了代码执行的性能,编泽器在编译源代码时可以做一些编译期的优化,比如锁消除、标量替换、方法内联等。
-
在Java的异常处理机制中,什么时候应该抛出异常,什么时候捕获异常?
- 异常相当于一种提示,如果我们抛出异常,就相当于告诉上层方法,我抛了一个异常,我处理不了这个异常,交给你来处理,而对于上层方法来说,它也需要决定自己能不能处理这个异常,是否也需要交给它的上层。
- 所以我们在写一个方法时,我们需要考表虑的就是,本方法能否合理的处理该异常,如果处理不了就继续向上抛出异常,包括本方法中在调用另外一个方法时,发现出现了异常,如果这个异常应该由自己来处理,那就捕获该异常并进行处理。