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