面试常见八股
JAVA篇
基础
1、自动拆箱和装箱
装箱:装箱是将值类型(如int
、double
、struct
等)转换为object
类型或任何接口类型的过程。由于object
是所有类型的基类(在.NET中),并且接口是引用类型,因此装箱操作实际上是在堆(Heap)上分配一个新的对象,并将值类型的值复制到该对象中。这个新分配的对象是对原始值类型值的引用包装。
例如:
Integer i =19;
原因:需要将值类型作为参数传递给接受object
类型或接口类型的方法
拆箱:拆箱是将之前装箱的对象转换回原始的值类型的过程。拆箱操作包括检查对象是否确实包含原始值类型的值,并提取该值。
int n = i;
实际案例:
public static void main(String[] args) {
// 创建一个ArrayList来存储Integer对象
List<Integer> numbers = new ArrayList<>();
// 装箱:将int值添加到ArrayList中
numbers.add(5); // 这里自动发生了装箱:int -> Integer
numbers.add(10);
// 遍历ArrayList并打印每个元素
for (Integer number : numbers) {
System.out.println(number);
}
// 拆箱:从ArrayList中获取Integer对象并作为int值处理
// 假设我们知道列表中确实存储的是int值对应的Integer对象
int firstNumber = numbers.get(0); // 这里自动发生了拆箱:Integer -> int
System.out.println("第一个数字是:" + firstNumber);
// 注意:如果尝试从一个可能包含null的ArrayList中拆箱,可能会引发NullPointerException
// 因此,在拆箱之前进行null检查是一个好习惯
}
2、接口和抽象类
共同点:都不能直接实例化,接口要通过实现类,抽象类要通过继承才能创建具体的对象。
区别:
接口主要是为了对类的行为进行约束,而抽象类是为了代码复用,强调从属关系。
一个类只能继承一个类包括抽象类,但是可以实现多个接口,一个接口也能继承多个接口。
接口中的成员变量只能是 public static final
类型的,不能被修改且必须有初始值。抽象类的成员变量可以有任何修饰符(private
, protected
, public
),可以在子类中被重新定义或赋值。
3、深拷贝和浅拷贝
浅拷贝:浅拷贝在堆上创建一个新的对象,但是如果当前对象内部属性是引用类型的话就直接拷贝引用的地址,之后创建的新对象和原对象共用一个对象。
深拷贝:完全复制整个对象,包括这个对象内部的对象。
4、String、StringBuffer、StringBuilder
String对象因为有final修饰符修饰,所以不可变,可以理解为常量,线程安全。而另外2个继承自AbstractStringBuilder类,没有修饰符所以可变。而且提供了一些修改字符串的方法可以使用比如append、insert这些。其中StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的。
集合
1、Map
HashMap的实现
JDK1.8 之前 HashMap底层是数组和链表结合在一起使用也就是链表散列。HashMap通过key的 hashcode
经过扰动函数处理过后得到hash值,然后通过 (n - 1)&hash
判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
JDK1.8 之后改为红黑树,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
HashMap和HashTable的区别
线程安全方面(table内部方法都用synchornized修饰)、效率、null key和null value、底层数据结构
扩容:不指定的话,默认table为11,扩充到2n+1;map默认为16,扩充到2n。指定的话,table直接使用指定的大小,map会扩充为2的幂次。
HashMap为什么不安全?Table为什么安全?
因为在JDK1.8前,hashmap如果在多线程情况下,好几个线程同时对链表进行扩容,会造成死循环。1.8之后,也会有数据丢失的问题,因为多个键值对被分到一个桶中,采用红黑树进行存储,多个线程操作可能因为时间片的问题,导致同样哈希值的一个操作覆盖另一个操作结果,不安全。
ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashMap:1.7的时候,采用分段思想,segment。之后,采用Node数组+树的方式,使用synchornized和CAS来操作。
Hashtable:同一把锁,synchornized,效率比较低。
并发
1、CAS
全称是比较和交换,通常用于乐观锁之中。乐观锁与悲观锁区别在对于共享资源访问时候不同的上锁方式,悲观是默认修改每次都上锁,一次只给一个线程使用。乐观锁默认不修改,但是其他线程提交前需要判断是否有修改。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。
2、ThreadLocal
想实现一个线程有自己的专属本地变量,绑定自己的值。如果创建了一个ThreadLocal
变量,那么访问这个变量的每个线程都会有这个变量的本地副本,线程使用 get()
和 set()
方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。
3、线程池
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。