面试必备系列-Java基础相关(一)
目前网上的面试题泛滥成灾,真正有价值的很少,往往是烂大街的问题,同时也没有给出正确的解决方案 , 本文旨在整理一一系列对面试者有帮助的文章,后续会持续更新。
String 类为什么是final的
答案是为了 “效率”和安全,
安全: 由于String类被final修饰符修饰,那么他就是不可被继承,创建出来的对象之后也就是不可以被改变的。加上String字符串有常量池的概念,如果没有被final修饰符修饰,那么创建之后的对象是不可以保证的,容易产生安全性问题。同时,在String类使用非常广泛的情况下,这种设计是非常必要的。效率: 设计成final,JVM才不用对相关方法在虚函数表中查询,而直接定位到String类的相关方法上,提高了执行效率。 (涉及到了JVM的实现细节,在此笔者也不是很懂,在此分享给大家,有兴趣的可以详细了解)
HashMap的底层实现, jdk1.8有何变化
HashMap底层是通过数据+链表的形式实现的, HashMap初始化的时候,会默认创建长度为16的数组,put的时候通过HashCode求余的方式来确定KEY的位置,如果HashCode相同,那么相同hashCode的值会放在一个链表上面。 如此可见,当放入HashMap中的key的HashCode冲突比较大时,当严重影响效率。
JDK1.8之后,当链表数量达到8个以上时,会采用红黑树来实现。
HashSet集合实现原理是什么
HashSet底层采用的是哈希表来实现的,其原理和HashMap一致,只是对HashMap的API做了一层封装有点类似于value为null的hashMap
Java中的队列有哪些,有何区别
在此列出几个常用的队列。
阻塞队列
ArrayBlockingQueue :一个由数组支持的有界队列,阻塞队列,读写共享一把锁
LinkedBlockingQueue :一个由链接节点支持的可选有界队列,阻塞队列 , 读写分别采用的是不同的锁。
DelayQueue :基于时间的调度队列,比如说往这个队列中添加一个元素,同时指定3秒钟,那么只有过了3秒钟之后,才能够从该队列中取到这个元素
非阻塞队列
ConcurrentLinkedQueue
是一个基于链接节点*安全的队列,基于CAS操作保证队列里面数据的一致性 , 该队列的size()方法是会遍历整个队列的,因此及其耗费性能,通常使用isEmpty()来判断集合是否为空。
ConcurrentHashMap实现原理
采用分段锁的概念, ConcurrentHashMap在初始化时,默认会产生16个段(Segment),每个段内部又是一个数组,数组中的每个元素又是一个链表,说的通俗一点,Segment的结构有点类似于HashMap .
当有元素需要添加进来的时候,首先进行一次Hash , 计算出该元素会被分配到哪个段里面, 确定好在哪个段里面之后,进行第二次Hash, 确定在段的数组中哪个位置,第二次Hash来确定在数组中哪个位置,和HashMap的原理一致。
ConcurrentHashMap 大量的采用volatile, CAS,final,lock等技术,减少锁的竞争对性能产生的影响、
异常的结构,运行时异常和非运行时异常,各举个例子
异常的结构:
Thorwable 是所有异常和错误的基类。
Error 和Exception .
Error : 是JVM运行产生的错误,是不可以感知的,当发生时,会导致程序中断,应用程序无法捕获
Exception : 程序可以捕获的异常,应用程序可以处理的异常信息,分为运行时异常和非运行时异常
非运行时异常 : 程序必须要处理的异常,否则不能编译通过,如: IOException, SQLException
运行时异常 :程序在运行过程中可能产生的异常,这个不强制要求处理。 如: RuntimeException
弱引用, 软引用 的概念和使用方式。
软引用:当JVM内存不够时,会强制回收软引用,如果回收了之后,还不能满足当前的内存需求,则会报内存溢出的错误,通常用于本地缓存的使用。
弱引用: 通过 WeakReference 标识这是一个弱引用,生命周期比较短,当系统触发垃圾回收的时候,如果一个对象只具有弱引用,那么就会被回收 , 比如:ThreadLocal中引用的ThreadLocalMap中的Entry就是一个弱引用,大家有时间可以取看一下。
volatile的理解
volatile是在多线程的环境中,对于变量的一个修饰符。
场景:
大家都知道,每个程序有主内存, 每个线程都有自己的工作内存, 线程工作内存之中的数据是不
共享的,在这里举个例子;
Thread A 查询 int x ,此时主内存中的x = 0 ,那么Thread A 通过read ,load 的操作将变量x加载到自己的工作内存当中,同时修改x = 1 , 这个时候主内存当中的x 还是等于0 ,所以当另外一个线程Thread B 过来加载x 的时候,获取到的还是0 。那么volatile的作用是什么呢? 该关键字主要是保证线程之间变量的可见性。 按照上面的例子,如果volatile int x = 0 , 然后被Thread A加载到他的工作内存中修改为了1 ,那么他在修改的同时,会将
x = 1 ,推送到主内存,修改主内存里面的值。
总而言之: volatile保证变量的可见性,但不保证原子性。
CAS是什么
CAS是CPU底层的原子命令 , 里面有三个概念,一定要记住
内存值,预期值,更新值
当: 内存值==预期值 的时候,才会将内存值修改为 更新值, 否则不做任何操作
AtomicInteger有用过吗? 通常用于什么场景? 基于什么原理实现的。
用过, 在项目当中一般用于高并发场景,主要用于计数的功能 。 该类是通过CAS的方式来保证线程安全的。
i++为什么不是线程安全的
因为在多线程的环境下, i++本身就不是一个原子操作,i++的执行过程过下,
Thread从主内存中读取i = 1 到线程的工作内存, 在线程中使用完毕之后,执行 i = i+1 , 这个变量i 并没有同步到主内存中区,Thread B又来读取了,这个时候读取到的还是 1 ,想到这里大家应该都明白了,在多线程的情况下,i++是会造成数据错误的
解决方式:
使用synchronized同步,但是比较耗费性能。不推荐
使用AtomicInteger , 其是通过CAS原子操作来保证数据的安全行的,推荐使用
SimpleDateFormat是线程安全的吗?
不是, SimpleDateFormat的内部都是使用Calendar这个对象来操作时间的,如果SimpleDateFormat是静态的,那么在多线程环境下,多个线程操作一个对象,是会有线程安全问题的。
下面是SimpleDateFormat的parse方法
Date parse() {
calendar.clear(); // 清理calendar
... // 执行一些操作, 设置 calendar 的日期什么的
calendar.getTime(); // 获取calendar的时间
}
这里会导致的问题就是, 如果 线程A 调用了 sdf.parse(), 并且进行了 calendar.clear()后还未执行calendar.getTime()的时候,线程B又调用了sdf.parse(), 这时候线程B也执行了sdf.clear()方法, 这样就导致线程A的的calendar数据被清空了(实际上A,B的同时被清空了). 又或者当 A 执行了calendar.clear()后被挂起, 这时候B 开始调用sdf.parse()并顺利i结束, 这样 A 的 calendar内存储的的date 变成了后来B设置的calendar的date
解决方案:
每次使用之前,创建一个SimpleDateFormat 对象 ,在高并发的情况下,每次都要创建和销毁该对象是非常耗费性能的,因此不推荐使用。
使用同步代码块synchronized,同步局部代码。
使用TheadLocal存储日期对象,为每个线程创建自己的SimpleDateFormat 对象,这样在高并发的情况对性能影响较小。
使用第三方的时间转换的库
PS: 以上是笔者自己的理解,如果有什么地方错误了,请留言告知我, 我马上修改 , 大家在面试过程中遇到过哪些奇葩问题? 快快留言告诉我,我整理出来分享给大家。
感兴趣的可以关注一下本人的公众号,大量技术干货分享
上一篇: 《加密与解密》笔记四(一)
推荐阅读
-
面试必备系列-Java基础相关(一)
-
面试必备系列-Java基础相关(二)
-
java基础面试题(一)
-
java基础面试相关
-
【类之间的关系】JAVA面试题【类之间的关系】(精选java面试题、最最基础java面试题目、java面试必备、java面试必知必会)
-
java 面试整理一——基础知识 博客分类: javajava编程 java面试数据结构编程
-
java 面试整理一——基础知识 博客分类: javajava编程 java面试数据结构编程
-
Java面试题-基础篇一 博客分类: 面试题系列 面试javajava面试宝典Java面试题
-
【面试题系列】——Java基础
-
一些java二进制的相关基础知识