2020java面試題-基礎篇

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,那麼執行拒絕策略。

具體解釋一下上述參數:

  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之間轉換

發佈了60 篇原創文章 · 獲贊 344 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章