Java基础
⭐包装类型常量池技术了解吗?
常量池也叫缓存池,主要存储一些常用的数据。基本类型除开double和float浮点数太大了,没有常量池,其他都实现了。当发生自动装箱的时候,如果基本类型的值在缓存的范围内,则不会重新创建对象,而是复用缓存池中已经创建好的对象。
不同的包装类缓存的范围不同,也可以通过jvm参数上调。
原理:
- 装箱:调用的是XXX.valueOf()方法;在这个方法里面有缓存区间的代码。
- 拆箱:调用的是XXX.intValue()方法。
⭐String、StringBuffer和StringBuilder的区别是什么?String为什么是不可变的?
- 可变性:String内部的value值是final修饰的,所以它是一个不可变的类。因此每次修改String的值的时候都会new一个新的对象。而StringBuffer和StringBuilder是可变的。
- 线程安全性:
- String由于它的不可变性,导致了它是一个线程安全的
- StringBuffer也是线程安全的,因为它每个方法里面都使用了synchronized关键字
- StringBuilder不是线程安全的。
- 性能:StringBuilder>StringBuffer>String
- 存储:
- String存储在字符串常量池里面
- StringBuffer和StringBuilder是存储在堆内存空间的。其次这两个类都有一个共同的父类。
字符串常量池,是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
具体要看JVM了。
java反射
- 本质:是运行时动态操作类的能力
- 实现:是直接读取操作编译之后的类.class文件,更多就是JVM了。
- 隐患:对性能和安全是有影响的。
BIO、NIO、AIO有什么区别?
- BIO:同步阻塞,传统的socket
- NIO:异步阻塞,selector+channel
- AIO:异步非阻塞,缓冲区+CompletionHandler,实用性不如NIO普及。
⭐Java集合
ArrayList自动扩容机制是什么?
ArrayList的底层存储也是一个数组(Object[]),new ArrayList()的时候在构造方法里面赋值了一个空数组,但并未真正的分配容量。在向数组添加第一个数据的时候才会开始扩容,它会判断当前数组容量是否足够存储add之后的元素个数,不能存储的时候就会调用grow方法进行扩容(第一次扩容默认是10,之后都是容量>>1,也就是1.5倍),最后使用Arrays.copyOf将数据拷贝过去。因此在能预估数组大小的时候,可以使用有参构造方法指定容量大小,避免扩容带来的性能消耗。
HashMap了解吗?它源码看过吗?底层了解吗?
它主要为了存储的key-val的键值对格式而产生的,利用hash算法快速找到key,然后根据key定位到val值。
- 在JDK1.8之前,它是由数组+链表组成的,链表主要是为了解决hash冲突而存在的,也就是拉链法。
- 在JDK1.8之后,为了更好的解决hash冲突,当链表长度>=8 && 数组长度>=64时,会将链表转为红黑数,以减少搜索时间。
- 扩容因子是0.75,然每次扩容是原来的两倍。
ConcurrentHashMap线程安全的具体方式了解吗?
因为hahsmap在并发情况下是不安全的,因此诞生了ConcurrentHashMap。
- 在JDK1.7版本的时候,它的存储结构是一个segment数组,长度默认是16(最大并发数),初始化之后不更改。数组里面存放的是hashentry数组外挂链表,有点像是在把hashmpa放在segment数组一样。
- 但是7版本的并发不够灵活,始终受限于一开始的segment;并且锁的粒度太大,一个线程就会锁住整个segment;极端情况下,链表过长性能会退化,这也是hashmap1.8使用红黑树的原因。
- 因此在JDK1.8版本的时候,它的存储结构就变成了,node数组 + 链表/红黑树。这样做的就是解决上个版本中的问题;
- 并发度:segment锁的粒度更大,segment数组的一个元素中其实有多个桶,但是在并发的情况下,两个线程要访问同一个segment不同的桶的时候,会串行访问;但是node可以让他们并行访问。
- 同时node不想segment那样初始化之后,在整个生命周期都不可以变化,node会根据当前桶数变化的。
- 当链表冲突达到了一定的长度,链表会转成红黑树。使其拥有了更高的并发度、更强的性能和应对链表过长的极端情况。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 coder-xuyong!
评论






