只有光頭才能變強
Redis目前還在看,今天來分享一下我在秋招看過(遇到)的一些面試題(相對比較常見的)html
簡要說一下final關鍵字,final能夠用來修飾什麼?
這題我是在真實的面試中遇到的,當時答得不太好,如今來整理一下吧。java
final能夠修飾類、方法、成員變量mysql
當final修飾方法的時候,說明該方法不能被重寫面試
當final修飾成員變量時,有兩種狀況:redis
值得一說的是:並非被final修飾的成員變量就必定是編譯期常量了。好比說咱們能夠寫出這樣的代碼:private final int java3y = new Randon().nextInt(20);
算法
你有沒有這樣的編程經驗,在編譯器寫代碼時,某個場景下必定要將變量聲明爲final,不然會出現編譯不經過的狀況。爲何要這樣設計?
在編寫匿名內部類的時候就可能會出現這種狀況,匿名內部類可能會使用到的變量:sql
class Outer { // string:外部類的實例變量 String string = ""; //ch:方法的參數 void outerTest(final char ch) { // integer:方法內局部變量 final Integer integer = 1; new Inner() { void innerTest() { System.out.println(string); System.out.println(ch); System.out.println(integer); } }; } public static void main(String[] args) { new Outer().outerTest(' '); } class Inner { } }
其中咱們能夠看到:方法或做用域內的局部變量和方法參數都要顯示使用final關鍵字來修飾(在jdk1.7下)!數據庫
若是切換到jdk1.8編譯環境下,能夠經過編譯的~編程
下面咱們首先來講一下顯示聲明爲final的緣由:爲了保持內部外部數據一致性api
爲何僅僅針對方法中的參數限制final,而訪問外部類的屬性就能夠隨意
內部類中是保存着一個指向外部類實例的引用,內部類訪問外部類的成員變量都是經過這個引用。
那當你在匿名內部類裏面嘗試改變外部基本類型的變量的值的時候,或者改變外部引用變量的指向的時候,表面上看起來好像都成功了,但實際上並不會影響到外部的變量。因此,Java爲了避免讓本身看起來那麼奇怪,才加了這個final的限制。
參考資料:
選用考量:
三個線程分別打印A,B,C,要求這三個線程一塊兒運行,打印n次,輸出形如「ABCABCABC....」的字符串。
原博主給出了4種方式,我認爲信號量這種方式比較簡單和容易理解,我這裏粘貼一下(具體的可到原博主下學習)..
public class PrintABCUsingSemaphore { private int times; private Semaphore semaphoreA = new Semaphore(1); private Semaphore semaphoreB = new Semaphore(0); private Semaphore semaphoreC = new Semaphore(0); public PrintABCUsingSemaphore(int times) { this.times = times; } public static void main(String[] args) { PrintABCUsingSemaphore printABC = new PrintABCUsingSemaphore(10); // 非靜態方法引用 x::toString 和() -> x.toString() 是等價的! new Thread(printABC::printA).start(); new Thread(printABC::printB).start(); new Thread(printABC::printC).start(); /*new Thread(() -> printABC.printA()).start(); new Thread(() -> printABC.printB()).start(); new Thread(() -> printABC.printC()).start(); */ } public void printA() { try { print("A", semaphoreA, semaphoreB); } catch (InterruptedException e) { e.printStackTrace(); } } public void printB() { try { print("B", semaphoreB, semaphoreC); } catch (InterruptedException e) { e.printStackTrace(); } } public void printC() { try { print("C", semaphoreC, semaphoreA); } catch (InterruptedException e) { e.printStackTrace(); } } private void print(String name, Semaphore current, Semaphore next) throws InterruptedException { for (int i = 0; i < times; i++) { current.acquire(); System.out.print(name); next.release(); } } }
2018年9月14日18:15:36 yy筆試題就出了..
在很多的面經都能看到它的身影哈~~~基本都是要求可以手寫代碼的。
其實邏輯並不難,歸納起來就兩句話:
基於原做者的代碼,我修改了部分並給上我認爲合適的註釋(下面附上了原做者出處,感興趣的同窗可到原文學習)
生產者:
import java.util.Random; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; public class Producer implements Runnable { // true--->生產者一直執行,false--->停掉生產者 private volatile boolean isRunning = true; // 公共資源 private final Vector sharedQueue; // 公共資源的最大數量 private final int SIZE; // 生產數據 private static AtomicInteger count = new AtomicInteger(); public Producer(Vector sharedQueue, int SIZE) { this.sharedQueue = sharedQueue; this.SIZE = SIZE; } @Override public void run() { int data; Random r = new Random(); System.out.println("start producer id = " + Thread.currentThread().getId()); try { while (isRunning) { // 模擬延遲 Thread.sleep(r.nextInt(1000)); // 當隊列滿時阻塞等待 while (sharedQueue.size() == SIZE) { synchronized (sharedQueue) { System.out.println("Queue is full, producer " + Thread.currentThread().getId() + " is waiting, size:" + sharedQueue.size()); sharedQueue.wait(); } } // 隊列不滿時持續創造新元素 synchronized (sharedQueue) { // 生產數據 data = count.incrementAndGet(); sharedQueue.add(data); System.out.println("producer create data:" + data + ", size:" + sharedQueue.size()); sharedQueue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupted(); } } public void stop() { isRunning = false; } }
消費者:
import java.util.Random; import java.util.Vector; public class Consumer implements Runnable { // 公共資源 private final Vector sharedQueue; public Consumer(Vector sharedQueue) { this.sharedQueue = sharedQueue; } @Override public void run() { Random r = new Random(); System.out.println("start consumer id = " + Thread.currentThread().getId()); try { while (true) { // 模擬延遲 Thread.sleep(r.nextInt(1000)); // 當隊列空時阻塞等待 while (sharedQueue.isEmpty()) { synchronized (sharedQueue) { System.out.println("Queue is empty, consumer " + Thread.currentThread().getId() + " is waiting, size:" + sharedQueue.size()); sharedQueue.wait(); } } // 隊列不空時持續消費元素 synchronized (sharedQueue) { System.out.println("consumer consume data:" + sharedQueue.remove(0) + ", size:" + sharedQueue.size()); sharedQueue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } }
Main方法測試:
import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test2 { public static void main(String[] args) throws InterruptedException { // 1.構建內存緩衝區 Vector sharedQueue = new Vector(); int size = 4; // 2.創建線程池和線程 ExecutorService service = Executors.newCachedThreadPool(); Producer prodThread1 = new Producer(sharedQueue, size); Producer prodThread2 = new Producer(sharedQueue, size); Producer prodThread3 = new Producer(sharedQueue, size); Consumer consThread1 = new Consumer(sharedQueue); Consumer consThread2 = new Consumer(sharedQueue); Consumer consThread3 = new Consumer(sharedQueue); service.execute(prodThread1); service.execute(prodThread2); service.execute(prodThread3); service.execute(consThread1); service.execute(consThread2); service.execute(consThread3); // 3.睡一下子而後嘗試中止生產者(結束循環) Thread.sleep(10 * 1000); prodThread1.stop(); prodThread2.stop(); prodThread3.stop(); // 4.再睡一下子關閉線程池 Thread.sleep(3000); // 5.shutdown()等待任務執行完才中斷線程(由於消費者一直在運行的,因此會發現程序沒法結束) service.shutdown(); } }
另外,上面原文中也說了可使用阻塞隊列來實現消費者和生產者。這就不用咱們手動去寫wait/notify
的代碼了,會簡單一丟丟。能夠參考:
我如今須要實現一個棧,這個棧除了能夠進行普通的push、pop操做之外,還能夠進行getMin的操做,getMin方法被調用後,會返回當前棧的最小值,你會怎麼作呢?你能夠假設棧裏面存的都是int整數
解決方案:
使用一個min變量來記住最小值,每次push的時候,看看是否須要更新min。
使用輔助棧來存儲最小值。若是當前要push的值比輔助棧的min值要小,那在輔助棧push的值是最小值
import java.util.ArrayList; import java.util.List; public class MinStack { private List<Integer> data = new ArrayList<Integer>(); private List<Integer> mins = new ArrayList<Integer>(); public void push(int num) { data.add(num); if (mins.size() == 0) { // 初始化mins mins.add(num); } else { // 輔助棧mins每次push當時最小值 int min = getMin(); if (num >= min) { mins.add(min); } else { mins.add(num); } } } public int pop() { // 棧空,異常,返回-1 if (data.size() == 0) { return -1; } // pop時兩棧同步pop mins.remove(mins.size() - 1); return data.remove(data.size() - 1); } public int getMin() { // 棧空,異常,返回-1 if (mins.size() == 0) { return -1; } // 返回mins棧頂元素 return mins.get(mins.size() - 1); } }
繼續優化:
算法的空間優化:上面的代碼咱們能夠發現:data棧和mins棧的元素個數老是相等的,mins棧中存儲幾乎都是最小的值(此部分是重複的!)
可是,若是一直push的值是最小值,那咱們的mins輔助棧仍是會有大量的重複元素,此時咱們可使用索引(mins輔助棧存儲的是最小值索引,非具體的值)!
最終代碼:
import java.util.ArrayList; import java.util.List; public class MinStack { private List<Integer> data = new ArrayList<Integer>(); private List<Integer> mins = new ArrayList<Integer>(); public void push(int num) throws Exception { data.add(num); if(mins.size() == 0) { // 初始化mins mins.add(0); } else { // 輔助棧mins push最小值的索引 int min = getMin(); if (num < min) { mins.add(data.size() - 1); } } } public int pop() throws Exception { // 棧空,拋出異常 if(data.size() == 0) { throw new Exception("棧爲空"); } // pop時先獲取索引 int popIndex = data.size() - 1; // 獲取mins棧頂元素,它是最小值索引 int minIndex = mins.get(mins.size() - 1); // 若是pop出去的索引就是最小值索引,mins纔出棧 if(popIndex == minIndex) { mins.remove(mins.size() - 1); } return data.remove(data.size() - 1); } public int getMin() throws Exception { // 棧空,拋出異常 if(data.size() == 0) { throw new Exception("棧爲空"); } // 獲取mins棧頂元素,它是最小值索引 int minIndex = mins.get(mins.size() - 1); return data.get(minIndex); } }
參考資料:
衆所周知,HashMap不是一個線程安全的類。但有可能在面試的時候會被問到:若是在多線程環境下使用HashMap會有什麼現象發生呢??
結論:
put()
的時候致使的多線程數據不一致(丟失數據)resize()
操做會致使環形鏈表
參考資料:
1、SpringBoot是可以建立出獨立的Spring應用程序的
2、簡化Spring配置
Spring Boot項目就是爲了解決配置繁瑣的問題,最大化的實現convention over configuration(約定大於配置)。
3、嵌入式Tomcat,Jetty容器,無需部署WAR包
G1收集器的設計目標是取代CMS收集器,它同CMS相比,在如下方面表現的更出色:
G1是一個有整理內存過程的垃圾收集器,不會產生不少內存碎片。
拓展閱讀:
海量數據的處理也是一個常常考的知識點,不管在面試仍是在筆試中都是比較常見的。有幸讀了下面的文章,摘錄了一些解決海量數據的思路:
Bloom filter布隆過濾器
Hashing
bit-map
堆
雙層桶劃分----其實本質上就是【分而治之】的思想,重在「分」的技巧上!
數據庫索引
倒排索引(Inverted index)
外排序
trie樹
分佈式處理 mapreduce
詳細可參考原文:
昨天去作了一套筆試題,經典的HTTP中get/post
的區別。今天回來搜了一下,發現跟以前的理解有點出入。
若是一我的一開始就作Web開發,極可能 把HTML對HTTP協議的使用方式,當成HTTP協議的惟一的合理使用方式。從而犯了以偏概全的錯誤
單純以HTTP協議規範來講,可能咱們以前總結出的GET/POST
區別就沒用了。(但通讀完整篇文章,我我的認爲:若是面試中有GET/POST
區別,仍是默認以Web開發場景下來回答較好,這也許是面試官想要的答案)
參考資料:
其中也學習到了冪等性這麼一個概念,因而也作作筆記吧~~~
Methods can also have the property of 「idempotence」 in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
從定義上看,HTTP方法的冪等性是指一次和屢次請求某一個資源應該具備一樣的反作用。
HTTP的GET/POST/DELETE/PUT
方法冪等的狀況:
GET
是冪等的,無反作用
http://localhost/order/2
,使用GET
屢次獲取,這個ID爲2的訂單(資源)是不會發生變化的!DELETE/PUT
是冪等的,有反作用
http://localhost/order/2
,使用PUT/DELETE
屢次請求,這個ID爲2的訂單(資源)只會發生一次變化(是有反作用的)!但繼續屢次刷新請求,訂單ID爲2的最終狀態都是一致的 POST
是非冪等的,有反作用的
http://localhost/order
,使用POST
屢次請求,此時可能就會建立多個名稱爲3y的訂單,這個訂單(資源)是會屢次變化的,每次請求的資源狀態都會變化!題外話:
HTTP協議自己是一種 面向資源的應用層協議,但對HTTP協議的使用實際上存在着兩種不一樣的方式:一種是 RESTful的,它把HTTP當成應用層協議,比較忠實地遵照了HTTP協議的各類規定( 充分利用了HTTP的方法);另外一種 是SOA的,它並無徹底把HTTP當成應用層協議,而是把HTTP協議做爲了傳輸層協議,而後在HTTP之上創建了本身的應用層協議
參考資料:
在查閱資料的時候,能夠發現不少博客都講了接口的冪等性。從上面咱們也能夠看出,POST
方法是非冪等的。但咱們能夠經過一些手段來令POST
方法的接口變成是冪等的。
說了那麼多,那接口設計成冪等的好處是什麼????
舉個例子說一下非冪等的壞處:
若是個人搶課接口是冪等的話,那就不會出現這個問題了。由於冪等是屢次請求某一個資源應該具備一樣的反作用。
說白了,設計冪等性接口就是爲了防止重複提交的(數據庫出現多條重複的數據)!
網上有博主也分享了幾條常看法決重複提交的方案:
參考資料:
若是以上有理解錯的地方,或者說有更好的理解方式,但願你們不吝在評論區下留言。共同進步!
若是想看更多的 原創技術文章,歡迎你們關注個人 微信公衆號:Java3y。Java技術 羣討論:742919422。公衆號還有 海量的視頻資源哦,關注便可免費領取。