2020年太难了,只能刷刷题了。后续会记录一些面试题方面的总结,方便复习查看。今天是基础篇的几个典型面试题。文章内容来源于自己的思考、书本、网络,如有雷同,不是巧合。
1.HashMap的源码:
HashMap是java散列表数据结构的一种实现方式.
底层是基于数组和链表。还有红黑树。
首先通过hash函数,计算下标,因为考虑到hash冲突,所以横向又通过链表存储,这样就近似能做到O(1)的查询。
HashMap初始化时容量是16,当负载因子超过0.75时,会进行扩容。扩容会进行数据的拷贝,所以一般在已知数据容量的情况下,可以给一个初始容量。
当拉链长度超过8之后,就会采用红黑树。
LinkedHashMap和HashMap的区别是,LinkedHashMap维护了一个双向链表,保证了数据的顺序。即插入顺序和访问顺序一致。TreeMap则进行了排序,默认是升序,可以重写Comparator方法进行排序规则重写。
扩展:
ConcurrentHashMap 原理
1、最大特点是引入了 CAS(借助 Unsafe 来实现【native code】)
- CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
- Unsafe 借助 CPU 指令 cmpxchg 来实现
HashMap ,HashTable 区别
- 默认容量不同。扩容不同
- 线程安全性,HashTable 安全
- 效率不同 HashTable 要慢因为加锁
考察知识点:
1.对常用java数据结构包装类的熟悉。
2.考察队低层数据结构的认识程度
2.Set的实现:
元素不重复,HashSet低层也是通过HashMap实现,只是只保存了值,没有保存key.
- HashSet
- LinkHashSet
- TreeSet
3.List实现:
相同点:
都实现了List接口和Collection;
不同点:
1、ArrayList是基于数组实现的;LinkedList是基于链表实现的;
2、ArrayList随机查询速度快;LinkedList插入和删除速度快;
原理解析:
ArrayList是基于数组实现的,他的特性就是可以使用索引来提升查询效率;插入和删除数组中某个元素,会导致其后面的元素需要重新调整索引,产生一定的性能消耗;
LinkedList是基于链表实现的,没有索引,所以查询效率不高,但是插入和删除效率却很高;为什么呢?因为链表里插入或删除某个元素,只需要调整前后元素的引用即可;
4.讲解线程execute
考点:java的线程池
线程池作用:
1.降低资源消耗(创建销毁线程需要占用资源)
2.提高效率(线程的复用)
3.提高线程的可管理性
使用:
1.创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
可以创建以下几种类型的线程池:
- newSingleThreadExecutor
- newFixedThreadPool
- newCachedThreadPool
- newScheduledThreadPool
- newSingleThreadScheduledExecutor
2.创建要执行任务的线程
可以是Thread,Runnable, Callable
3.添加任务,等待执行
void execute(Runnable task)
Future<?> submit(Runnable task)
Future<?> submit(Runnable task, T result)
Future<?> submit(Callable task)
其中execute没有返回值,submit将返回值放在Future中
4.如果有返回结果,在future中获取返回结果
原理
核心类是ThreadPoolExecutor,一般使用提供的工厂类Executors创建一个包装类ExecutorService进行操作。
ThreadPoolExecutor来实现,我们使用的ExecutorService的各种线程池策略都是基于ThreadPoolExecutor实现的。
step1.调用ThreadPoolExecutor的execute提交线程,首先检查CorePool,如果CorePool内的线程小于CorePoolSize,新创建线程执行任务。
step2.如果当前CorePool内的线程大于等于CorePoolSize,那么将线程加入到BlockingQueue。
step3.如果不能加入BlockingQueue,在小于MaxPoolSize的情况下创建线程执行任务。
step4.如果线程数大于等于MaxPoolSize,那么执行拒绝策略。
具体解释一下上述参数:
- corePoolSize 核心线程池大小
- maximumPoolSize 线程池最大容量大小
- keepAliveTime 线程池空闲时,线程存活的时间
- TimeUnit 时间单位
- ThreadFactory 线程工厂
- BlockingQueue任务队列
- RejectedExecutionHandler 线程拒绝策略
woker:是线程池中的线程。
Task: 是Runnable,会被worker调用run方法
queen: 任务队列
pool: 线程池
线程池的生命周期:
-
RUNNING: Accept new tasks and process queued tasks
-
SHUTDOWN: Don’t accept new tasks, but process queued tasks
-
STOP: Don’t accept new tasks, don’t process queued tasks,
and interrupt in-progress tasks
-
TIDYING: All tasks have terminated, workerCount is zero,
the thread transitioning to state TIDYING
will run the terminated() hook method
-
TERMINATED: terminated() has completed
状态转换:
RUNNING -> SHUTDOWN(调用shutdown())
On invocation of shutdown(), perhaps implicitly in finalize()
(RUNNING or SHUTDOWN) -> STOP(调用shutdownNow())
On invocation of shutdownNow()
SHUTDOWN -> TIDYING(queue和pool均empty)
When both queue and pool are empty
STOP -> TIDYING(pool empty,此时queue已经为empty)
When pool is empty
TIDYING -> TERMINATED(调用terminated())
When the terminated() hook method has completed
Threads waiting in awaitTermination() will return when the
state reaches TERMINATED.
说明:
(1 newFixedThreadPool
是固定大小的线程池 有结果可见 我们指定2 在运行时就只有2个线程工作
若其中有一个线程异常 会有新的线程替代他
(2 shutdown方法有2个重载:
void shutdown() 启动一次顺序关闭,等待执行以前提交的任务完成,但不接受新任务。
List shutdownNow() 试图立即停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
(3 submit 与 execute
3.1 submit是ExecutorService中的方法 用以提交一个任务
他的返回值是future对象 可以获取执行结果
Future submit(Callable task) 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
Future<?> submit(Runnable task) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Future submit(Runnable task, T result) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
3.2 execute是Executor接口的方法
他虽然也可以像submit那样让一个任务执行 但并不能有返回值
void execute([Runnable](mk:@MSITStore:C:\Users\Administrator\Desktop\JDK1.6 API帮助文档.CHM::/java/lang/Runnable.html) command)
在未来某个时间执行给定的命令。该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。
(4 Future
Future 表示异步计算的结果。
它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。
取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。
如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。
必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
也就是说Future提供了三种功能:
–判断任务是否完成;
–能够中断任务;
–能够获取任务执行结果。
boolean cancel(boolean mayInterruptIfRunning) 试图取消对此任务的执行。
V get() 如有必要,等待计算完成,然后获取其结果。
V get(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
boolean isCancelled() 如果在任务正常完成前将其取消,则返回 true。
boolean isDone() 如果任务已完成,则返回 true。
复习参考:https://juejin.im/post/5aabb948f265da237506a7f5
https://www.cnblogs.com/wihainan/p/4760910.html
5.Runable和Callnable的区别
这两者类似,前者实现run方法,后者实现call方法。不通点是后者,有返回值。
6.使用泛型的好处
java中的泛型和c++中的template模板类似,是在在定义时,不指明类型,而是在编译时就可以做类型检查,并且消除了强制类型转换,效率更高。
E
: ElementK
: KeyN
: NumberT
: TypeV
: ValueS
,U
,V
, and so on: Second, third, and fourth types in a multiparameter situation
7.JDK动态代理和Cglib的区别
JDK动态代理只能代理interface (所以目标类必须实现该接口,进而代理类也需要实现该接口)
Cglib则是通过继承目标类来实现代理。
静态代理:运行前就存在代理类的字节码
动态代理:运行时动态生成代理类
JDK动态代理使用
1.首先定义一个类实现InvocationHandler 定义代理行为。
其中构造方法传入要代理的类对象,invoke方法中
public class SubjectProxyHandler implements InvocationHandler{
private static final Logger LOG = LoggerFactory.getLogger(SubjectProxyHandler.class);
private Object target;
@SuppressWarnings("rawtypes")
public SubjectProxyHandler(Class clazz) {
try {
this.target = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
LOG.error("Create proxy for {} failed", clazz.getName());
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
preAction();
Object result = method.invoke(target, args);
postAction();
LOG.info("Proxy class name {}", proxy.getClass().getName());
return result;
}
private void preAction() {
LOG.info("SubjectProxyHandler.preAction()");
}
private void postAction() {
LOG.info("SubjectProxyHandler.postAction()");
}
}
Object result = method.invoke(target, args);
是调用目标对象的方法,在Invoke方法中可以进行前置增强或者后置增强等。
调用:
public static void main(String[] args) {
InvocationHandler handler = new SubjectProxyHandler(Subject.class);
ISubject proxy =
(ISubject) Proxy.newProxyInstance(ProxyT.class.getClassLoader(),
new Class[] {ISubject.class}, handler);
proxy.action();
}
首先创建一个InvocationHandler,传入目标对象Subject(实现了接口ISubject)
然后通过Proxy.newProxyInstance动态生成代理类ISubject。
其中handler中定义的代理类的行为,代理类是动态生成的。从newProxyInstance的参数可以看出,可以代理类可以实现多个接口。
cglib
cglib是一个强大的高性能代码生成库,它的底层是通过使用一个小而快的字节码处理框架ASM(Java字节码操控框架)来转换字节码并生成新的类
1.定义代理行为类
public class SubjectInteceptor implements MethodInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(SubjectInteceptor.class);
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
preAction();
Object result = methodProxy.invokeSuper(obj, args);
postAction();
return result;
}
private void preAction() {
LOG.info("SubjectProxyHandler.preAction()");
}
private void postAction() {
LOG.info("SubjectProxyHandler.postAction()");
}
}
代理行为在intercept方法中定义
对比:
jdk动态代理创建速度快,但是执行效率比cglib低。cglib生成代理速度较慢,但是执行效率高。
在Spring中可以通过参数 optimize参数 设置为true强制使用cglib进行代理,对于单例模式,建议采用cglib
SpringBoot中可以通过下面的配置指定代理类型:
#开启对AOP的支持
spring.aop.auto=true
#设置代理模式 true(cglib) false(java JDK代理)
spring.aop.proxy-target-class=true
1.如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2.如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3.如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换