2020java面试题-基础篇

2020年太难了,只能刷刷题了。后续会记录一些面试题方面的总结,方便复习查看。今天是基础篇的几个典型面试题。文章内容来源于自己的思考、书本、网络,如有雷同,不是巧合。

文章目录

        • 1.HashMap的源码:
        • 2.Set的实现:
        • 3.List实现:
        • 4.讲解线程execute
        • 5.Runable和Callnable的区别
        • 6.使用泛型的好处
        • 7.JDK动态代理和Cglib的区别

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,那么执行拒绝策略。

具体解释一下上述参数:

  1. corePoolSize 核心线程池大小
  2. maximumPoolSize 线程池最大容量大小
  3. keepAliveTime 线程池空闲时,线程存活的时间
  4. TimeUnit 时间单位
  5. ThreadFactory 线程工厂
  6. BlockingQueue任务队列
  7. 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: Element
  • K: Key
  • N: Number
  • T: Type
  • V: Value
  • S, 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之间转换

你可能感兴趣的:(面试,java随笔)