歡迎光臨
每天分享高質量文章

可能是一份最適合你的後端面試指南(部分內容前端同樣適用)

來自:Java面試通關手冊(微訊號:Java_Guide)

之前也總結了一些面試相關的文章,昨天也有很多人找到我詢問了面試這方面的內容。今天給大家分享一篇之前花了很長時間寫的文章,這篇文章實際上是前段時間掘金社群徵文活動的時候寫的,目前獲得了接近 2w+ 的閱讀以及 1.3K+ 的喜歡。本文內容(乾貨)很多,大家可以慢慢消化。嘿嘿!這些東西都是在面試中或者說是準備面試的時候比較重要的東西,強烈推薦大家好好讀一下

目錄

  • 前言

  • 一 簡歷該如何寫

    1.1 為什麼說簡歷很重要?

    1.2-這3點你必須知道

    1.3-兩大法則瞭解一

    1.4-專案經歷怎麼寫?

    1.5-專業技能該怎麼寫?

    1.6-開源程式員簡歷模板分享

    1.7 其他的一些小tips

  • 二 計算機網路常見面試點總結

    計算機網路常見問題回顧

    2.1 TCP、UDP 協議的區別

    2.2 在瀏覽器中輸入url地址 ->> 顯示主頁的過程

    2.3 各種協議與HTTP協議之間的關係

    2.4 HTTP長連線、短連線

    2.5 TCP 三次握手和四次揮手

  • 三 Linux

    3.1-簡單介紹一下-linux-檔案系統?

    3.2 一些常見的 Linux 命令瞭解嗎?

  • 四 MySQL

    4.1 說說自己對於 MySQL 常見的兩種儲存引擎:MyISAM與InnoDB的理解

    4.2 資料庫索引瞭解嗎?

    4.3 對於大表的常見最佳化手段說一下

  • 五 Redis

    5.1 redis 簡介

    5.2 為什麼要用 redis /為什麼要用快取

    5.3 為什麼要用 redis 而不用 map/guava 做快取?

    5.4 redis 和 memcached 的區別

    5.5 redis 常見資料結構以及使用場景分析

    5.6 redis 設定過期時間

    5.7 redis 記憶體淘汰機制

    5.8 redis 持久化機制(怎麼保證 redis 掛掉之後再重啟資料可以進行恢復)

    5.9 快取雪崩和快取穿透問題解決方案

    5.10 如何解決 Redis 的併發競爭 Key 問題

    5.11 如何保證快取與資料庫雙寫時的資料一致性?

  • 六 Java

    6.1 Java 基礎知識

    6.2 Java 集合框架

    6.3 Java多執行緒

    6.4 Java虛擬機器

    6.5 設計樣式

  • 七 資料結構

  • 八 演演算法

    8.1 舉個慄子(手寫快排)

  • 九 Spring

    9.1 Spring Bean 的作用域

    9.2 Spring 事務中的隔離級別

    9.3 Spring 事務中的事務傳播行為

    9.4 AOP

    9.5 IOC

  • 十 實際場景題

  • 寫在最後

前言

不論是校招還是社招都避免不了各種面試、筆試,如何去準備這些東西就顯得格外重要。不論是筆試還是面試都是有章可循的,我這個“有章可循”說的意思只是說應對技術面試是可以提前準備。 我其實特別不喜歡那種臨近考試就提前背啊記啊各種題的行為,非常反對!我覺得這種方法特別極端,而且在稍有一點經驗的面試官面前是根本沒有用的。建議大家還是一步一個腳印踏踏實實地走。

運籌帷幄之後,決勝千里之外!不打毫無準備的仗,我覺得大家可以先從下麵幾個方面來準備面試:

  1. 自我介紹。(你可千萬這樣介紹:“我叫某某,性別,來自哪裡,學校是那個,自己愛乾什麼”,記住:多說點簡歷上沒有的,多說點自己哪裡比別人強!)

  2. 自己面試中可能涉及哪些知識點、那些知識點是重點。

  3. 面試中哪些問題會被經常問到、面試中自己改如何回答。(強烈不推薦背題,第一:透過背這種方式你能記住多少?能記住多久?第二:背題的方式的學習很難堅持下去!)

  4. 自己的簡歷該如何寫。

“80%的offer掌握在20%的人手中” 這句話也不是不無道理的。決定你面試能否成功的因素中實力固然佔有很大一部分比例,但是如果你的心態或者說運氣不好的話,依然無法拿到滿意的 offer。運氣暫且不談,就拿心態來說,千萬不要因為面試失敗而氣餒或者說懷疑自己的能力,面試失敗之後多總結一下失敗的原因,後面你就會發現自己會越來越強大。

另外,大家要明確的很重要的幾點是:

  1. 寫在簡歷上的東西一定要慎重,這可能是面試官大量提問的地方;

  2. 大部分應屆生找工作的硬傷是沒有工作經驗或實習經歷;

  3. 將自己的專案經歷完美的展示出來非常重要。

筆主能力有限,如果有不對的地方或者和你想法不同的地方,敬請雅正、不捨賜教。

如果想瞭解我的更多資訊,可以關註我的 Github 或者微信公眾號:”Java面試通關手冊”。

一 簡歷該如何寫

程式員的簡歷之道

俗話說的好:“工欲善其事,必先利其器”。準備一份好的簡歷對於能不能找到一份好工作起到了至關重要的作用。

1.1 為什麼說簡歷很重要?

假如你是網申,你的簡歷必然會經過HR的篩選,一張簡歷HR可能也就花費10秒鐘看一下,然後HR就會決定你這一關是Fail還是Pass。

假如你是內推,如果你的簡歷沒有什麼優勢的話,就算是內推你的人再用心,也無能為力。

另外,就算你透過了篩選,後面的面試中,面試官也會根據你的簡歷來判斷你究竟是否值得他花費很多時間去面試。

1.2 這3點你必須知道

  1. 大部分應屆生找工作的硬傷是沒有工作經驗或實習經歷;

  2. 寫在簡歷上的東西一定要慎重,這可能是面試官大量提問的地方;

  3. 將自己的專案經歷完美的展示出來非常重要。

1.3 兩大法則瞭解一下

目前寫簡歷的方式有兩種普遍被認可,一種是 STAR, 一種是 FAB。

STAR法則(Situation Task Action Result):

  • Situation: 事情是在什麼情況下發生;

  • Task:: 你是如何明確你的任務的;

  • Action: 針對這樣的情況分析,你採用了什麼行動方式;

  • Result: 結果怎樣,在這樣的情況下你學習到了什麼。

FAB 法則(Feature Advantage Benefit):

  • Feature: 是什麼;

  • Advantage: 比別人好在哪些地方;

  • Benefit: 如果僱傭你,招聘方會得到什麼好處。

1.4 專案經歷怎麼寫?

簡歷上有一兩個專案經歷很正常,但是真正能把專案經歷很好的展示給面試官的非常少。對於專案經歷大家可以考慮從如下幾點來寫:

  1. 對專案整體設計的一個感受

  2. 在這個專案中你負責了什麼、做了什麼、擔任了什麼角色

  3. 從這個專案中你學會了那些東西,使用到了那些技術,學會了那些新技術的使用

  4. 另外專案描述中,最好可以體現自己的綜合素質,比如你是如何協調專案組成員協同開發的或者在遇到某一個棘手的問題的時候你是如何解決的。

1.5 專業技能該怎麼寫?

先問一下你自己會什麼,然後看看你意向的公司需要什麼。一般HR可能並不太懂技術,所以他在篩選簡歷的時候可能就盯著你專業技能的關鍵詞來看。對於公司有要求而你不會的技能,你可以花幾天時間學習一下,然後在簡歷上可以寫上自己瞭解這個技能。比如你可以這樣寫:

  • Dubbo:精通

  • Spring:精通

  • Docker:掌握

  • SOA分散式開發 :掌握

  • Spring Cloud:瞭解

1.6 開源程式員簡歷模板分享

分享一個Github上開源的程式員簡歷模板。包括PHP程式員簡歷模板、iOS程式員簡歷模板、Android程式員簡歷模板、Web前端程式員簡歷模板、Java程式員簡歷模板、C/C++程式員簡歷模板、NodeJS程式員簡歷模板、架構師簡歷模板以及通用程式員簡歷模板 。
Github地址:https://github.com/geekcompany/ResumeSample

如果想學如何用 Markdown 寫簡歷寫一份高質量簡歷,請看這裡:https://github.com/Snailclimb/Java-Guide/blob/master/面試必備/手把手教你用Markdown寫一份高質量的簡歷.md

1.7 其他的一些小tips

  1. 儘量避免主觀表述,少一點語意模糊的形容詞,儘量要簡潔明瞭,邏輯結構清晰。

  2. 註意排版(不需要花花綠綠的),儘量使用Markdown語法。

  3. 如果自己有部落格或者個人技術棧點的話,寫上去會為你加分很多。

  4. 如果自己的Github比較活躍的話,寫上去也會為你加分很多。

  5. 註意簡歷真實性,一定不要寫自己不會的東西,或者帶有欺騙性的內容

  6. 專案經歷建議以時間倒序排序,另外專案經歷不在於多,而在於有亮點。

  7. 如果內容過多的話,不需要非把內容壓縮到一頁,保持排版乾凈整潔就可以了。

  8. 簡歷最後最好能加上:“感謝您花時間閱讀我的簡歷,期待能有機會和您共事。”這句話,顯的你會很有禮貌。

二 計算機網路常見面試點總結

網路分層結構

計算機網路常見問題回顧

  • TCP三次握手和四次揮手、

  • 在瀏覽器中輸入url地址->>顯示主頁的過程

  • TCP 協議如何保證可靠傳輸

  • HTTP和HTTPS的區別

  • TCP、UDP協議的區別

  • 常見的狀態碼。

下麵列舉幾個常見問題的回答!

2.1 TCP、UDP 協議的區別

TCP、UDP協議的區別

UDP 在傳送資料之前不需要先建立連線,遠地主機在收到 UDP 報文後,不需要給出任何確認。雖然 UDP 不提供可靠交付,但在某些情況下 UDP 確是一種最有效的工作方式(一般用於即時通訊),比如: QQ 語音、 QQ 影片 、直播等等

TCP 提供面向連線的服務。在傳送資料之前必須先建立連線,資料傳送結束後要釋放連線。 TCP 不提供廣播或多播服務。由於 TCP 要提供可靠的,面向連線的運輸服務(TCP的可靠體現在TCP在傳遞資料之前,會有三次握手來建立連線,而且在資料傳遞時,有確認、視窗、重傳、擁塞控制機制,在資料傳完後,還會斷開連線用來節約系統資源),這一難以避免增加了許多開銷,如確認,流量控制,計時器以及連線管理等。這不僅使協議資料單元的首部增大很多,還要佔用許多處理機資源。TCP 一般用於檔案傳輸、傳送和接收郵件、遠端登入等場景。

2.2 在瀏覽器中輸入url地址 ->> 顯示主頁的過程

百度好像最喜歡問這個問題。

開啟一個網頁,整個過程會使用哪些協議

圖片來源:《圖解HTTP》

狀態碼

總體來說分為以下幾個過程:

  1. DNS解析

  2. TCP連線

  3. 傳送HTTP請求

  4. 伺服器處理請求並傳回HTTP報文

  5. 瀏覽器解析渲染頁面

  6. 連線結束

具體可以參考下麵這篇文章:

  • https://segmentfault.com/a/1190000006879700

2.3 各種協議與HTTP協議之間的關係

一般面試官會透過這樣的問題來考察你對計算機網路知識體系的理解。

圖片來源:《圖解HTTP》

各種協議與HTTP協議之間的關係

2.4 HTTP長連線、短連線

在HTTP/1.0中預設使用短連線。也就是說,客戶端和伺服器每進行一次HTTP操作,就建立一次連線,任務結束就中斷連線。當客戶端瀏覽器訪問的某個HTML或其他型別的Web頁中包含有其他的Web資源(如JavaScript檔案、影象檔案、CSS檔案等),每遇到這樣一個Web資源,瀏覽器就會重新建立一個HTTP會話。

而從HTTP/1.1起,預設使用長連線,用以保持連線特性。使用長連線的HTTP協議,會在響應頭加入這行程式碼:

Connection:keep-alive

在使用長連線的情況下,當一個網頁開啟完成後,客戶端和伺服器之間用於傳輸HTTP資料的TCP連線不會關閉,客戶端再次訪問這個伺服器時,會繼續使用這一條已經建立的連線。Keep-Alive不會永久保持連線,它有一個保持時間,可以在不同的伺服器軟體(如Apache)中設定這個時間。實現長連線需要客戶端和服務端都支援長連線。

HTTP協議的長連線和短連線,實質上是TCP協議的長連線和短連線。

—— 《HTTP長連線、短連線究竟是什麼?》

2.5 TCP 三次握手和四次揮手(面試常客)

為了準確無誤地把資料送達標的處,TCP協議採用了三次握手策略。

漫畫圖解:

圖片來源:《圖解HTTP》

TCP三次握手

簡單示意圖:

TCP三次握手
  • 客戶端–傳送帶有 SYN 標誌的資料包–一次握手–服務端

  • 服務端–傳送帶有 SYN/ACK 標誌的資料包–二次握手–客戶端

  • 客戶端–傳送帶有帶有 ACK 標誌的資料包–三次握手–服務端

為什麼要三次握手?

三次握手的目的是建立可靠的通訊通道,說到通訊,簡單來說就是資料的傳送與接收,而三次握手最主要的目的就是雙方確認自己與對方的傳送與接收是正常的。

第一次握手:Client 什麼都不能確認;Server 確認了對方傳送正常

第二次握手:Client 確認了:自己傳送、接收正常,對方傳送、接收正常;Server 確認了:自己接收正常,對方傳送正常

第三次握手:Client 確認了:自己傳送、接收正常,對方傳送、接收正常;Server 確認了:自己傳送、接收正常,對方傳送接收正常

所以三次握手就能確認雙發收發功能都正常,缺一不可。

為什麼要傳回 SYN

接收端傳回傳送端所傳送的 SYN 是為了告訴傳送端,我接收到的資訊確實就是你所傳送的訊號了。

SYN 是 TCP/IP 建立連線時使用的握手訊號。在客戶機和伺服器之間建立正常的 TCP 網路連線時,客戶機首先發出一個 SYN 訊息,伺服器使用 SYN-ACK 應答表示接收到了這個訊息,最後客戶機再以 ACK(Acknowledgement[漢譯:確認字元 ,在資料通訊傳輸中,接收站發給傳送站的一種傳輸控制字元。它表示確認發來的資料已經接受無誤。 ])訊息響應。這樣在客戶機和伺服器之間才能建立起可靠的TCP連線,資料才可以在客戶機和伺服器之間傳遞。

傳了 SYN,為啥還要傳 ACK

雙方通訊無誤必須是兩者互相傳送資訊都無誤。傳了 SYN,證明傳送方到接收方的通道沒有問題,但是接收方到傳送方的通道還需要 ACK 訊號來進行驗證。

TCP四次揮手

斷開一個 TCP 連線則需要“四次揮手”:

  • 客戶端-傳送一個 FIN,用來關閉客戶端到伺服器的資料傳送

  • 伺服器-收到這個 FIN,它發回一 個 ACK,確認序號為收到的序號加1 。和 SYN 一樣,一個 FIN 將佔用一個序號

  • 伺服器-關閉與客戶端的連線,傳送一個FIN給客戶端

  • 客戶端-發回 ACK 報文確認,並將確認序號設定為收到序號加1

為什麼要四次揮手

任何一方都可以在資料傳送結束後發出連線釋放的通知,待對方確認後進入半關閉狀態。當另一方也沒有資料再傳送的時候,則發出連線釋放通知,對方確認後就完全關閉了TCP連線。

舉個例子:A 和 B 打電話,通話即將結束後,A 說“我沒啥要說的了”,B回答“我知道了”,但是 B 可能還會有要說的話,A 不能要求 B 跟著自己的節奏結束通話,於是 B 可能又巴拉巴拉說了一通,最後 B 說“我說完了”,A 回答“知道了”,這樣通話才算結束。

上面講的比較概括,推薦一篇講的比較細緻的文章:

  • https://blog.csdn.net/qzcsu/article/details/72861891

三 Linux

Linux

3.1 簡單介紹一下 Linux 檔案系統?

Linux檔案系統簡介

在Linux作業系統中,所有被作業系統管理的資源,例如網路介面卡、磁碟驅動器、印表機、輸入輸出裝置、普通檔案或是目錄都被看作是一個檔案。

也就是說在LINUX系統中有一個重要的概念:一切都是檔案。其實這是UNIX哲學的一個體現,而Linux是重寫UNIX而來,所以這個概念也就傳承了下來。在UNIX系統中,把一切資源都看作是檔案,包括硬體裝置。UNIX系統把每個硬體都看成是一個檔案,通常稱為裝置檔案,這樣使用者就可以用讀寫檔案的方式實現對硬體的訪問。

檔案型別與目錄結構

Linux支援5種檔案型別 :

檔案型別

Linux的目錄結構如下:

Linux檔案系統的結構層次鮮明,就像一棵倒立的樹,最頂層是其根目錄:

Linux的目錄結構

常見目錄說明:

  • /bin: 存放二進位制可執行檔案(ls,cat,mkdir等),常用命令一般都在這裡;

  • /etc: 存放系統管理和配置檔案;

  • /home: 存放所有使用者檔案的根目錄,是使用者主目錄的基點,比如使用者user的主目錄就是/home/user,可以用~user表示;

  • /usr : 用於存放系統應用程式;

  • /opt: 額外安裝的可選應用程式包所放置的位置。一般情況下,我們可以把tomcat等都安裝到這裡;

  • /proc: 虛擬檔案系統目錄,是系統記憶體的對映。可直接訪問這個目錄來獲取系統資訊;

  • /root: 超級使用者(系統管理員)的主目錄(特權階級^o^);

  • /sbin: 存放二進位制可執行檔案,只有root才能訪問。這裡存放的是系統管理員使用的系統級別的管理命令和程式。如ifconfig等;

  • /dev: 用於存放裝置檔案;

  • /mnt: 系統管理員安裝臨時檔案系統的安裝點,系統提供這個目錄是讓使用者臨時掛載其他的檔案系統;

  • /boot: 存放用於系統引導時使用的各種檔案;

  • /lib : 存放著和系統執行相關的庫檔案 ;

  • /tmp: 用於存放各種臨時檔案,是公用的臨時檔案儲存點;

  • /var: 用於存放執行時需要改變資料的檔案,也是某些大檔案的上限溢位區,比方說各種服務的日誌檔案(系統啟動日誌等。)等;

  • /lost+found: 這個目錄平時是空的,系統非正常關機而留下“無家可歸”的檔案(windows下叫什麼.chk)就在這裡。

3.2 一些常見的 Linux 命令瞭解嗎?

目錄切換命令

  • `cd usr`: 切換到該目錄下usr目錄

  • `cd ..(或cd../)`: 切換到上一層目錄

  • `cd /`: 切換到系統根目錄

  • `cd ~`: 切換到使用者主目錄

  • `cd -`: 切換到上一個所在目錄

目錄的操作命令(增刪改查)

  1. `mkdir 目錄名稱`: 增加目錄

  2. `ls或者ll`(ll是ls -l的縮寫,ll命令以看到該目錄下的所有目錄和檔案的詳細資訊):檢視目錄資訊

  3. `find 目錄 引數`: 尋找目錄(查)

  4. mv 目錄名稱 新目錄名稱 修改目錄的名稱(改)

    註意:mv的語法不僅可以對目錄進行重新命名而且也可以對各種檔案,壓縮包等進行 重新命名的操作。mv命令用來對檔案或目錄重新命名,或者將檔案從一個目錄移到另一個目錄中。後面會介紹到mv命令的另一個用法。

  5. mv 目錄名稱 目錄的新位置 移動目錄的位置—剪下(改)

    註意:mv語法不僅可以對目錄進行剪下操作,對檔案和壓縮包等都可執行剪下操作。另外mv與cp的結果不同,mv好像檔案“搬家”,檔案個數並未增加。而cp對檔案進行複製,檔案個數增加了。

  6. cp -r 目錄名稱 目錄複製的標的位置 複製目錄(改),-r代表遞迴複製

    註意:cp命令不僅可以複製目錄還可以複製檔案,壓縮包等,複製檔案和壓縮包時不 用寫-r遞迴

  7. rm [-rf] 目錄: 刪除目錄(刪)

    註意:rm不僅可以刪除目錄,也可以刪除其他檔案或壓縮包,為了增強大家的記憶, 無論刪除任何目錄或檔案,都直接使用rm -rf 目錄/檔案/壓縮包

檔案的操作命令(增刪改查)

  1. `touch 檔案名稱`: 檔案的建立(增)

  2. cat/more/less/tail 檔案名稱 檔案的檢視(查)

    註意:命令 tail -f 檔案 可以對某個檔案進行動態監控,例如tomcat的日誌檔案, 會隨著程式的執行,日誌會變化,可以使用tail -f catalina-2016-11-11.log 監控 文 件的變化

  • `cat`: 只能顯示最後一屏內容

  • `more`: 可以顯示百分比,回車可以向下一行, 空格可以向下一頁,q可以退出檢視

  • `less`: 可以使用鍵盤上的PgUp和PgDn向上 和向下翻頁,q結束檢視

  • `tail-10` : 檢視檔案的後10行,Ctrl+C結束

  • vim 檔案 修改檔案的內容(改)

    vim編輯器是Linux中的強大元件,是vi編輯器的加強版,vim編輯器的命令和快捷方式有很多,但此處不一一闡述,大家也無需研究的很透徹,使用vim編輯修改檔案的方式基本會使用就可以了。

    在實際開發中,使用vim編輯器主要作用就是修改配置檔案,下麵是一般步驟:

    vim 檔案——>進入檔案—–>命令樣式——>按i進入編輯樣式—–>編輯檔案 ——->按Esc進入底行樣式—–>輸入:wq/q! (輸入wq代表寫入內容並退出,即儲存;輸入q!代表強制退出不儲存。)

  • rm -rf 檔案 刪除檔案(刪)

    同目錄刪除:熟記 rm -rf 檔案 即可

  • 壓縮檔案的操作命令

    1)打包並壓縮檔案:

    Linux中的打包檔案一般是以.tar結尾的,壓縮的命令一般是以.gz結尾的。

    而一般情況下打包和壓縮是一起進行的,打包並壓縮後的檔案的字尾名一般.tar.gz。
    命令:tar -zcvf 打包壓縮後的檔案名 要打包壓縮的檔案
    其中:

    z:呼叫gzip壓縮命令進行壓縮

    c:打包檔案

    v:顯示執行過程

    f:指定檔案名

    比如:加入test目錄下有三個檔案分別是 :aaa.txt bbb.txt ccc.txt,如果我們要打包test目錄並指定壓縮後的壓縮包名稱為test.tar.gz可以使用命令:tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt或:tar -zcvf test.tar.gz /test/

    2)解壓壓縮包:

    命令:tar [-xvf] 壓縮檔案

    其中:x:代表解壓

    示例:

    1 將/test下的test.tar.gz解壓到當前目錄下可以使用命令:tar -xvf test.tar.gz

    2 將/test下的test.tar.gz解壓到根目錄/usr下:tar -xvf xxx.tar.gz -C /usr(- C代表指定解壓的位置)

    其他常用命令

    • `pwd`: 顯示當前所在位置

    • `grep 要搜尋的字串 要搜尋的檔案 –color`: 搜尋命令,–color代表高亮顯示

    • ps -ef/ps aux 這兩個命令都是檢視當前系統正在執行行程,兩者的區別是展示格式不同。如果想要檢視特定的行程可以使用這樣的格式:ps aux|grep redis (檢視包括redis字串的行程)

      註意:如果直接用ps((Process Status))命令,會顯示所有行程的狀態,通常結合grep命令檢視某行程的狀態。

    • kill -9 行程的pid 殺死行程(-9 表示強制終止。)

      先用ps查詢行程,然後用kill殺掉

    • 網路通訊命令:

      • 檢視當前系統的網絡卡資訊:ifconfig

      • 檢視與某臺機器的連線情況:ping

      • 檢視當前系統的埠使用:netstat -an

    • `shutdown`: shutdown -h now: 指定現在立即關機;shutdown +5 "System will shutdown after 5 minutes":指定5分鐘後關機,同時送出警告資訊給登入使用者。

    • `reboot`: `reboot`: 重開機。`reboot -w`: 做個重開機的模擬(只有紀錄並不會真的重開機)。

    四 MySQL

    MySQL

    4.1 說說自己對於 MySQL 常見的兩種儲存引擎:MyISAM與InnoDB的理解

    關於二者的對比與總結:

    1. count運算上的區別:因為MyISAM快取有表meta-data(行數等),因此在做COUNT(*)時對於一個結構很好的查詢是不需要消耗多少資源的。而對於InnoDB來說,則沒有這種快取。

    2. 是否支援事務和崩潰後的安全恢復: MyISAM 強調的是效能,每次查詢具有原子性,其執行數度比InnoDB型別更快,但是不提供事務支援。但是InnoDB 提供事務支援事務,外部鍵等高階資料庫功能。 具有事務(commit)、回滾(rollback)和崩潰修複能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。

    3. 是否支援外來鍵: MyISAM不支援,而InnoDB支援。

    MyISAM更適合讀密集的表,而InnoDB更適合寫密集的的表。 在資料庫做主從分離的情況下,經常選擇MyISAM作為主庫的儲存引擎。
    一般來說,如果需要事務支援,並且有較高的併發讀取頻率(MyISAM的表鎖的粒度太大,所以當該表寫併發量較高時,要等待的查詢就會很多了),InnoDB是不錯的選擇。如果你的資料量很大(MyISAM支援壓縮特性可以減少磁碟的空間佔用),而且不需要支援事務時,MyISAM是最好的選擇。

    4.2 資料庫索引瞭解嗎?

    Mysql索引使用的資料結構主要有BTree索引 和 雜湊索引 。對於雜湊索引來說,底層的資料結構就是雜湊表,因此在絕大多數需求為單條記錄查詢的時候,可以選擇雜湊索引,查詢效能最快;其餘大部分場景,建議選擇BTree索引。

    Mysql的BTree索引使用的是B數中的B+Tree,但對於主要的兩種儲存引擎的實現方式是不同的。

    • MyISAM: B+Tree葉節點的data域存放的是資料記錄的地址。在索引檢索的時候,首先按照B+Tree搜尋演演算法搜尋索引,如果指定的Key存在,則取出其 data 域的值,然後以 data 域的值為地址讀取相應的資料記錄。這被稱為“非聚簇索引”。

    • InnoDB: 其資料檔案本身就是索引檔案。相比MyISAM,索引檔案和資料檔案是分離的,其表資料檔案本身就是按B+Tree組織的一個索引結構,樹的葉節點data域儲存了完整的資料記錄。這個索引的key是資料表的主鍵,因此InnoDB表資料檔案本身就是主索引。這被稱為“聚簇索引(或聚集索引)”。而其餘的索引都作為輔助索引(非聚集索引),輔助索引的data域儲存相應記錄主鍵的值而不是地址,這也是和MyISAM不同的地方。在根據主索引搜尋時,直接找到key所在的節點即可取出資料;在根據輔助索引查詢時,則需要先取出主鍵的值,在走一遍主索引。 因此,在設計表的時候,不建議使用過長的欄位作為主鍵,也不建議使用非單調的欄位作為主鍵,這樣會造成主索引頻繁分裂。 PS:整理自《Java工程師修煉之道》

    另外,再推薦幾篇比較好的關於索引的文章:

    • https://juejin.im/post/5b55b842f265da0f9e589e79#comment

    • https://www.jianshu.com/p/1775b4ff123a

    4.3 對於大表的常見最佳化手段說一下

    當MySQL單表記錄數過大時,資料庫的CRUD效能會明顯下降,一些常見的最佳化措施如下:

    1. 限定資料的範圍: 務必禁止不帶任何限制資料範圍條件的查詢陳述句。比如:我們當使用者在查詢訂單歷史的時候,我們可以控制在一個月的範圍內。;

    2. 讀/寫分離: 經典的資料庫拆分方案,主庫負責寫,從庫負責讀;

    3. 快取: 使用MySQL的快取,另外對重量級、更新少的資料可以考慮使用應用級別的快取;

    4. 垂直分割槽:

      根據資料庫裡面資料表的相關性進行拆分。 例如,使用者表中既有使用者的登入資訊又有使用者的基本資訊,可以將使用者表拆分成兩個單獨的表,甚至放到單獨的庫做分庫。

      簡單來說垂直拆分是指資料表列的拆分,把一張列比較多的表拆分為多張表。 如下圖所示,這樣來說大家應該就更容易理解了。

      垂直拆分的優點: 可以使得行資料變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分割槽可以簡化表的結構,易於維護。

      垂直拆分的缺點: 主鍵會出現冗餘,需要管理冗餘列,並會引起Join操作,可以透過在應用層進行Join來解決。此外,垂直分割槽會讓事務變得更加複雜;

    5. 水平分割槽:

      保持資料表結構不變,透過某種策略儲存資料分片。這樣每一片資料分散到不同的表或者庫中,達到了分散式的目的。 水平拆分可以支撐非常大的資料量。

      水平拆分是指資料錶行的拆分,表的行數超過200萬行時,就會變慢,這時可以把一張的表的資料拆成多張表來存放。舉個例子:我們可以將使用者資訊表拆分成多個使用者資訊表,這樣就可以避免單一表資料量過大對效能造成影響。

      資料庫水平拆分

      水品拆分可以支援非常大的資料量。需要註意的一點是:分表僅僅是解決了單一表資料過大的問題,但由於表的資料還是在同一臺機器上,其實對於提升MySQL併發能力沒有什麼意義,所以 水品拆分最好分庫 。

      水平拆分能夠 支援非常大的資料量儲存,應用端改造也少,但 分片事務難以解決,跨界點Join效能較差,邏輯複雜。《Java工程師修煉之道》的作者推薦 儘量不要對資料進行分片,因為拆分會帶來邏輯、部署、運維的各種複雜度 ,一般的資料表在最佳化得當的情況下支撐千萬以下的資料量是沒有太大問題的。如果實在要分片,儘量選擇客戶端分片架構,這樣可以減少一次和中介軟體的網路I/O。

      下麵補充一下資料庫分片的兩種常見方案:

    • 客戶端代理: 分片邏輯在應用端,封裝在jar包中,透過修改或者封裝JDBC層來實現。 噹噹網的 Sharding-JDBC 、阿裡的TDDL是兩種比較常用的實現。

    • 中介軟體代理: 在應用和資料中間加了一個代理層。分片邏輯統一維護在中介軟體服務中。 我們現在談的 Mycat 、360的Atlas、網易的DDB等等都是這種架構的實現。

    五 Redis

    Redis

    關於 redis 必知必會的11個問題!後兩個問題,暫未更新!如有需要,可以關註我的 Github 或者微信公眾號:“Java面試通關手冊”獲取後續更新內容。

    1. redis 簡介

    2. 為什麼要用 redis /為什麼要用快取

    3. 為什麼要用 redis 而不用 map/guava 做快取?

    4. redis 和 memcached 的區別

    5. redis 常見資料結構以及使用場景分析

    6. redis 設定過期時間

    7. redis 記憶體淘汰機制

    8. redis 持久化機制(怎麼保證 redis 掛掉之後再重啟資料可以進行恢復)

    9. 快取雪崩和快取穿透問題解決方案

    10. 如何解決 Redis 的併發競爭 Key 問題

    11. 如何保證快取與資料庫雙寫時的資料一致性?

    5.1 redis 簡介

    簡單來說 redis 就是一個資料庫,不過與傳統資料庫不同的是 redis 的資料是存在記憶體中的,所以存寫速度非常快,因此 redis 被廣泛應用於快取方向。另外,redis 也經常用來做分散式鎖。redis 提供了多種資料型別來支援不同的業務場景。除此之外,redis 支援事務 、持久化、LUA指令碼、LRU驅動事件、多種叢集方案。

    5.2 為什麼要用 redis /為什麼要用快取

    主要從“高效能”和“高併發”這兩點來看待這個問題。

    高效能:

    假如使用者第一次訪問資料庫中的某些資料。這個過程會比較慢,因為是從硬碟上讀取的。將該使用者訪問的資料存在數快取中,這樣下一次再訪問這些資料的時候就可以直接從快取中獲取了。操作快取就是直接操作記憶體,所以速度相當快。如果資料庫中的對應資料改變的之後,同步改變快取中相應的資料即可!

    高併發:

    直接操作快取能夠承受的請求是遠遠大於直接訪問資料庫的,所以我們可以考慮把資料庫中的部分資料轉移到快取中去,這樣使用者的一部分請求會直接到快取這裡而不用經過資料庫。

    5.3 為什麼要用 redis 而不用 map/guava 做快取?

    下麵的內容來自 segmentfault 一位網友的提問,地址:https://segmentfault.com/q/1010000009106416

    快取分為本地快取和分散式快取。以java為例,使用自帶的map或者guava實現的是本地快取,最主要的特點是輕量以及快速,生命週期隨著 jvm 的銷毀而結束,並且在多實體的情況下,每個實體都需要各自儲存一份快取,快取不具有一致性。

    使用 redis 或 memcached 之類的稱為分散式快取,在多實體的情況下,各實體共用一份快取資料,快取具有一致性。缺點是需要保持 redis 或 memcached服務的高可用,整個程式架構上較為複雜。

    5.4 redis 和 memcached 的區別

    對於 redis 和 memcached 我總結了下麵四點。現在公司一般都是用 redis 來實現快取,而且 redis 自身也越來越強大了!

    1. redis支援更豐富的資料型別(支援更複雜的應用場景):Redis不僅僅支援簡單的k/v型別的資料,同時還提供list,set,zset,hash等資料結構的儲存。memcache支援簡單的資料型別,String。

    2. Redis支援資料的持久化,可以將記憶體中的資料保持在磁碟中,重啟的時候可以再次載入進行使用,而Memecache把資料全部存在記憶體之中。

    3. 叢集樣式:memcached沒有原生的叢集樣式,需要依靠客戶端來實現往叢集中分片寫入資料;但是redis目前是原生支援cluster樣式的,redis官方就是支援redis cluster叢集樣式的,比memcached來說要更好。

    4. Memcached是多執行緒,非阻塞IO復用的網路模型;Redis使用單執行緒的多路 IO 復用模型。

    來自網路上的一張圖,這裡分享給大家!

    redis 和 memcached 的區別

    5.5 redis 常見資料結構以及使用場景分析

    1. String

    常用命令: set,get,decr,incr,mget 等。

    String資料結構是簡單的key-value型別,value其實不僅可以是String,也可以是數字。 
    常規key-value快取應用; 
    常規計數:微博數,粉絲數等。

    2.Hash

    常用命令: hget,hset,hgetall 等。

    Hash 是一個 string 型別的 field 和 value 的對映表,hash 特別適合用於儲存物件,後續操作的時候,你可以直接僅僅修改這個物件中的某個欄位的值。 比如我們可以Hash資料結構來儲存使用者資訊,商品資訊等等。比如下麵我就用 hash 型別存放了我本人的一些資訊:

    key=JavaUser293847
    value={
      “id”: 1,
      “name”: “SnailClimb”,
      “age”: 22,
      “location”: “Wuhan, Hubei”
    }

    3.List

    常用命令: lpush,rpush,lpop,rpop,lrange等

    list就是連結串列,Redis list的應用場景非常多,也是Redis最重要的資料結構之一,比如微博的關註串列,粉絲串列,訊息串列等功能都可以用Redis的 list 結構來實現。

    Redis list 的實現為一個雙向連結串列,即可以支援反向查詢和遍歷,更方便操作,不過帶來了部分額外的記憶體開銷。

    另外可以透過 lrange 命令,就是從某個元素開始讀取多少個元素,可以基於 list 實現分頁查詢,這個很棒的一個功能,基於 redis 實現簡單的高效能分頁,可以做類似微博那種下拉不斷分頁的東西(一頁一頁的往下走),效能高。

    4.Set

    常用命令:
    sadd,spop,smembers,sunion 等

    set對外提供的功能與list類似是一個串列的功能,特殊之處在於set是可以自動排重的。

    當你需要儲存一個串列資料,又不希望出現重覆資料時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要介面,這個也是list所不能提供的。可以基於 set 輕易實現交集、並集、差集的操作。

    比如:在微博應用中,可以將一個使用者所有的關註人存在一個集合中,將其所有粉絲存在一個集合。Redis可以非常方便的實現如共同關註、共同粉絲、共同喜好等功能。這個過程也就是求交集的過程,具體命令如下:

    sinterstore key1 key2 key3     將交集存在key1內

    5.Sorted Set

    常用命令: zadd,zrange,zrem,zcard等

    和set相比,sorted set增加了一個權重引數score,使得集合中的元素能夠按score進行有序排列。

    舉例: 在直播系統中,實時排行資訊包含直播間線上使用者串列,各種禮物排行榜,彈幕訊息(可以理解為按訊息維度的訊息排行榜)等資訊,適合使用 Redis 中的 SortedSet 結構進行儲存。

    5.6 redis 設定過期時間

    Redis中有個設定時間過期的功能,即對儲存在 redis 資料庫中的值可以設定一個過期時間。作為一個快取資料庫,這是非常實用的。如我們一般專案中的token或者一些登入資訊,尤其是簡訊驗證碼都是有時間限制的,按照傳統的資料庫處理方式,一般都是自己判斷過期,這樣無疑會嚴重影響專案效能。

    我們set key的時候,都可以給一個expire time,就是過期時間,透過過期時間我們可以指定這個 key 可以存貨的時間。

    如果假設你設定一個一批 key 只能存活1個小時,那麼接下來1小時後,redis是怎麼對這批key進行刪除的?

    定期刪除+惰性刪除。

    透過名字大概就能猜出這兩個刪除方式的意思了。

    • 定期刪除:redis預設是每隔 100ms 就隨機抽取一些設定了過期時間的key,檢查其是否過期,如果過期就刪除。註意這裡是隨機抽取的。為什麼要隨機呢?你想一想假如 redis 存了幾十萬個 key ,每隔100ms就遍歷所有的設定過期時間的 key 的話,就會給 CPU 帶來很大的負載!

    • 惰性刪除 :定期刪除可能會導致很多過期 key 到了時間並沒有被刪除掉。所以就有了惰性刪除。假如你的過期 key,靠定期刪除沒有被刪除掉,還停留在記憶體裡,除非你的系統去查一下那個 key,才會被redis給刪除掉。這就是所謂的惰性刪除,也是夠懶的哈!

    但是僅僅透過設定過期時間還是有問題的。我們想一下:如果定期刪除漏掉了很多過期 key,然後你也沒及時去查,也就沒走惰性刪除,此時會怎麼樣?如果大量過期key堆積在記憶體裡,導致redis記憶體塊耗盡了。怎麼解決這個問題呢?

    redis 記憶體淘汰機制。

    5.7 redis 記憶體淘汰機制(MySQL裡有2000w資料,Redis中只存20w的資料,如何保證Redis中的資料都是熱點資料?)

    redis 配置檔案 redis.conf 中有相關註釋,我這裡就不貼了,大家可以自行查閱或者透過這個網址檢視: http://download.redis.io/redis-stable/redis.conf

    redis 提供 6種資料淘汰策略:

    1. volatile-lru:從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰

    2. volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選將要過期的資料淘汰

    3. volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰

    4. allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的key(這個是最常用的).

    5. allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰

    6. no-enviction:禁止驅逐資料,也就是說當記憶體不足以容納新寫入資料時,新寫入操作會報錯。這個應該沒人使用吧!

    備註: 關於 redis 設定過期時間以及記憶體淘汰機制,我這裡只是簡單的總結一下,後面會專門寫一篇文章來總結!

    5.8 redis 持久化機制(怎麼保證 redis 掛掉之後再重啟資料可以進行恢復)

    很多時候我們需要持久化資料也就是將記憶體中的資料寫入到硬碟裡面,大部分原因是為了之後重用資料(比如重啟機器、機器故障之後回覆資料),或者是為了防止系統故障而將資料備份到一個遠端位置。

    Redis不同於Memcached的很重一點就是,Redis支援持久化,而且支援兩種不同的持久化操作。Redis的一種持久化方式叫快照(snapshotting,RDB),另一種方式是隻追加檔案(append-only file,AOF).這兩種方法各有千秋,下麵我會詳細這兩種持久化方法是什麼,怎麼用,如何選擇適合自己的持久化方法。

    快照(snapshotting)持久化(RDB)

    Redis可以透過建立快照來獲得儲存在記憶體裡面的資料在某個時間點上的副本。Redis建立快照之後,可以對快照進行備份,可以將快照複製到其他伺服器從而建立具有相同資料的伺服器副本(Redis主從結構,主要用來提高Redis效能),還可以將快照留在原地以便重啟伺服器的時候使用。

    快照持久化是Redis預設採用的持久化方式,在redis.conf配置檔案中預設有此下配置:

    save 900 1              #在900秒(15分鐘)之後,如果至少有1個key發生變化,Redis就會自動觸發BGSAVE命令建立快照。

    save 300 10            #在300秒(5分鐘)之後,如果至少有10個key發生變化,Redis就會自動觸發BGSAVE命令建立快照。

    save 60 10000        #在60秒(1分鐘)之後,如果至少有10000個key發生變化,Redis就會自動觸發BGSAVE命令建立快照。

    AOF(append-only file)持久化

    與快照持久化相比,AOF持久化 的實時性更好,因此已成為主流的持久化方案。預設情況下Redis沒有開啟AOF(append only file)方式的持久化,可以透過appendonly引數開啟:

    appendonly yes

    開啟AOF持久化後每執行一條會更改Redis中的資料的命令,Redis就會將該命令寫入硬碟中的AOF檔案。AOF檔案的儲存位置和RDB檔案的位置相同,都是透過dir引數設定的,預設的檔案名是appendonly.aof。

    在Redis的配置檔案中存在三種不同的 AOF 持久化方式,它們分別是:

    appendfsync always     #每次有資料修改發生時都會寫入AOF檔案,這樣會嚴重降低Redis的速度
    appendfsync everysec  #每秒鐘同步一次,顯示地將多個寫命令同步到硬碟
    appendfsync no      #讓作業系統決定何時進行同步

    為了兼顧資料和寫入效能,使用者可以考慮 appendfsync everysec選項 ,讓Redis每秒同步一次AOF檔案,Redis效能幾乎沒受到任何影響。而且這樣即使出現系統崩潰,使用者最多隻會丟失一秒之內產生的資料。當硬碟忙於執行寫入操作的時候,Redis還會優雅的放慢自己的速度以便適應硬碟的最大寫入速度。

    補充內容:AOF 重寫

    AOF重寫可以產生一個新的AOF檔案,這個新的AOF檔案和原有的AOF檔案所儲存的資料庫狀態一樣,但體積更小。

    AOF重寫是一個有歧義的名字,該功能是透過讀取資料庫中的鍵值對來實現的,程式無須對現有AOF檔案進行任伺讀入、分析或者寫人操作。

    在執行 BGREWRITEAOF 命令時,Redis 伺服器會維護一個 AOF 重寫緩衝區,該緩衝區會在子行程建立新AOF檔案期間,記錄伺服器執行的所有寫命令。當子行程完成建立新AOF檔案的工作之後,伺服器會將重寫緩衝區中的所有內容追加到新AOF檔案的末尾,使得新舊兩個AOF檔案所儲存的資料庫狀態一致。最後,伺服器用新的AOF檔案替換舊的AOF檔案,以此來完成AOF檔案重寫操作

    更多內容可以檢視我的這篇文章:

    • https://github.com/Snailclimb/JavaGuide/blob/master/資料儲存/Redis/Redis持久化.md

    5.9 快取雪崩和快取穿透問題解決方案

    快取雪崩

    簡介:快取同一時間大面積的失效,所以,後面的請求都會落到資料庫上,造成資料庫短時間內承受大量請求而崩掉。

    解決辦法(中華石杉老師在他的影片中提到過):

    • 事前:儘量保證整個 redis 叢集的高可用性,發現機器宕機儘快補上。選擇合適的記憶體淘汰策略。

    • 事中:本地ehcache快取 + hystrix限流&降級,避免MySQL崩掉

    • 事後:利用 redis 持久化機制儲存的資料儘快恢復快取

    快取穿透

    簡介:一般是駭客故意去請求快取中不存在的資料,導致所有的請求都落到資料庫上,造成資料庫短時間內承受大量請求而崩掉。

    解決辦法: 有很多種方法可以有效地解決快取穿透問題,最常見的則是採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap中,一個一定不存在的資料會被 這個bitmap攔截掉,從而避免了對底層儲存系統的查詢壓力。另外也有一個更為簡單粗暴的方法(我們採用的就是這種),如果一個查詢傳回的資料為空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行快取,但它的過期時間會很短,最長不超過五分鐘。

    參考:

    • https://blog.csdn.net/zeb_perfect/article/details/54135506enter link description here

    5.10 如何解決 Redis 的併發競爭 Key 問題

    所謂 Redis 的併發競爭 Key 的問題也就是多個系統同時對一個 key 進行操作,但是最後執行的順序和我們期望的順序不同,這樣也就導致了結果的不同!

    推薦一種方案:分散式鎖(zookeeper 和 redis 都可以實現分散式鎖)。(如果不存在 Redis 的併發競爭 Key 問題,不要使用分散式鎖,這樣會影響效能)

    基於zookeeper臨時有序節點可以實現的分散式鎖。大致思想為:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。 判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。 當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。完成業務流程後,刪除對應的子節點釋放鎖。

    在實踐中,當然是從以可靠性為主。所以首推Zookeeper。

    參考:

    • https://www.jianshu.com/p/8bddd381de06

    5.11 如何保證快取與資料庫雙寫時的資料一致性?

    你只要用快取,就可能會涉及到快取與資料庫雙儲存雙寫,你只要是雙寫,就一定會有資料一致性的問題,那麼你如何解決一致性問題?

    一般來說,就是如果你的系統不是嚴格要求快取+資料庫必須一致性的話,快取可以稍微的跟資料庫偶爾有不一致的情況,最好不要做這個方案,讀請求和寫請求序列化,串到一個記憶體佇列裡去,這樣就可以保證一定不會出現不一致的情況

    序列化之後,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。

    參考:

    • Java工程師面試突擊第1季(可能是史上最好的Java面試突擊課程)-中華石杉老師。影片地址見下麵!

    • 連結: https://pan.baidu.com/s/18pp6g1xKVGCfUATf_nMrOA

    • 密碼:5i58

    六 Java

    6.1 Java 基礎知識

    多載和重寫的區別

    多載: 發生在同一個類中,方法名必須相同,引數型別不同、個數不同、順序不同,方法傳回值和訪問修飾符可以不同,發生在編譯時。   

    重寫: 發生在父子類中,方法名、引數串列必須相同,傳回值範圍小於等於父類,丟擲的異常範圍小於等於父類,訪問修飾符範圍大於等於父類;如果父類方法訪問修飾符為 private 則子類就不能重寫該方法。

    String 和 StringBuffer、StringBuilder 的區別是什麼?String 為什麼是不可變的?

    可變性
     

    簡單的來說:String 類中使用 final 關鍵字字元陣列儲存字串,private final char value[],所以 String 物件是不可變的。而StringBuilder 與 StringBuffer 都繼承自 AbstractStringBuilder 類,在 AbstractStringBuilder 中也是使用字元陣列儲存字串char[]value 但是沒有用 final 關鍵字修飾,所以這兩種物件都是可變的。

    StringBuilder 與 StringBuffer 的構造方法都是呼叫父類構造方法也就是 AbstractStringBuilder 實現的,大家可以自行查閱原始碼。

    AbstractStringBuilder.java

    abstract class AbstractStringBuilder implements AppendableCharSequence {
        char[] value;
        int count;
        AbstractStringBuilder() {
        }
        AbstractStringBuilder(int capacity) {
            value = new char[capacity];
        }

    執行緒安全性

    String 中的物件是不可變的,也就可以理解為常量,執行緒安全。AbstractStringBuilder 是 StringBuilder 與 StringBuffer 的公共父類,定義了一些字串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 對方法加了同步鎖或者對呼叫的方法加了同步鎖,所以是執行緒安全的。StringBuilder 並沒有對方法進行加同步鎖,所以是非執行緒安全的。
      

    效能

    每次對 String 型別進行改變的時候,都會生成一個新的 String 物件,然後將指標指向新的 String 物件。StringBuffer 每次都會對 StringBuffer 物件本身進行操作,而不是生成新的物件並改變物件取用。相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的效能提升,但卻要冒多執行緒不安全的風險。

    對於三者使用的總結:

    1. 操作少量的資料 = String

    2. 單執行緒操作字串緩衝區下操作大量資料 = StringBuilder

    3. 多執行緒操作字串緩衝區下操作大量資料 = StringBuffer

    自動裝箱與拆箱

    裝箱:將基本型別用它們對應的取用型別包裝起來;

    拆箱:將包裝型別轉換為基本資料型別;

    == 與 equals

    == : 它的作用是判斷兩個物件的地址是不是相等。即,判斷兩個物件是不是同一個物件。(基本資料型別==比較的是值,取用資料型別==比較的是記憶體地址)

    equals() : 它的作用也是判斷兩個物件是否相等。但它一般有兩種使用情況:

    • 情況1:類沒有改寫 equals() 方法。則透過 equals() 比較該類的兩個物件時,等價於透過“==”比較這兩個物件。

    • 情況2:類改寫了 equals() 方法。一般,我們都改寫 equals() 方法來兩個物件的內容相等;若它們的內容相等,則傳回 true (即,認為這兩個物件相等)。

    舉個例子:

    public class test1 {
        public static void main(String[] args) {
            String a = new String("ab"); // a 為一個取用
            String b = new String("ab"); // b為另一個取用,物件的內容一樣
            String aa = "ab"// 放在常量池中
            String bb = "ab"// 從常量池中查詢
            if (aa == bb) // true
                System.out.println("aa==bb");
            if (a == b) // false,非同一物件
                System.out.println("a==b");
            if (a.equals(b)) // true
                System.out.println("aEQb");
            if (42 == 42.0) { // true
                System.out.println("true");
            }
        }
    }

    說明:

    • String 中的 equals 方法是被重寫過的,因為 object 的 equals 方法是比較的物件的記憶體地址,而 String 的 equals 方法比較的是物件的值。

    • 當建立 String 型別的物件時,虛擬機器會在常量池中查詢有沒有已經存在的值和要建立的值相同的物件,如果有就把它賦給當前取用。如果沒有就在常量池中重新建立一個 String 物件。

    關於 final 關鍵字的一些總結

    final關鍵字主要用在三個地方:變數、方法、類。

    1. 對於一個final變數,如果是基本資料型別的變數,則其數值一旦在初始化之後便不能更改;如果是取用型別的變數,則在對其初始化之後便不能再讓其指向另一個物件。

    2. 當用final修飾一個類時,表明這個類不能被繼承。final類中的所有成員方法都會被隱式地指定為final方法。

    3. 使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方法轉為內嵌呼叫。但是如果方法過於龐大,可能看不到內嵌呼叫帶來的任何效能提升(現在的Java版本已經不需要使用final方法進行這些優化了)。類中所有的private方法都隱式地指定為fianl。

    6.2 Java 集合框架

    Arraylist 與 LinkedList 異同

    • 1. 是否保證執行緒安全: ArrayList 和 LinkedList 都是不同步的,也就是不保證執行緒安全;

    • 2. 底層資料結構: Arraylist 底層使用的是Object陣列;LinkedList 底層使用的是雙向迴圈連結串列資料結構;

    • 3. 插入和刪除是否受元素位置的影響: ① ArrayList 採用陣列儲存,所以插入和刪除元素的時間複雜度受元素位置的影響。 比如:執行add(E e)方法的時候, ArrayList 會預設在將指定的元素追加到此串列的末尾,這種情況時間複雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element))時間複雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之後的(n-i)個元素都要執行向後位/向前移一位的操作。 ② LinkedList 採用連結串列儲存,所以插入,刪除元素時間複雜度不受元素位置的影響,都是近似 O(1)而陣列為近似 O(n)。

    • 4. 是否支援快速隨機訪問: LinkedList 不支援高效的隨機元素訪問,而ArrayList 實現了RandmoAccess 介面,所以有隨機訪問功能。快速隨機訪問就是透過元素的序號快速獲取元素物件(對應於get(int index)方法)。

    • 5. 記憶體空間佔用: ArrayList的空 間浪費主要體現在在list串列的結尾會預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗比ArrayList更多的空間(因為要存放直接後繼和直接前驅以及資料)。

    補充:資料結構基礎之雙向連結串列

    雙向連結串列也叫雙連結串列,是連結串列的一種,它的每個資料結點中都有兩個指標,分別指向直接後繼和直接前驅。所以,從雙向連結串列中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。一般我們都構造雙向迴圈連結串列,如下圖所示,同時下圖也是LinkedList 底層使用的是雙向迴圈連結串列資料結構。

    ArrayList 與 Vector 區別

    Vector類的所有方法都是同步的。可以由兩個執行緒安全地訪問一個Vector物件、但是一個執行緒訪問Vector的話程式碼要在同步操作上耗費大量的時間。

    Arraylist不是同步的,所以在不需要保證執行緒安全時時建議使用Arraylist。

    HashMap的底層實現

    ①JDK1.8之前

    JDK1.8 之前 HashMap 底層是 陣列和連結串列 結合在一起使用也就是 連結串列雜湊HashMap 透過 key 的 hashCode 經過擾動函式處理過後得到 hash 值,然後透過 (n - 1) & hash 判斷當前元素存放的位置(這裡的 n 指的時陣列的長度),如果當前位置存在元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,直接改寫,不相同就透過拉鏈法解決衝突。

    所謂擾動函式指的就是 HashMap 的 hash 方法。使用 hash 方法也就是擾動函式是為了防止一些實現比較差的 hashCode() 方法 換句話說使用擾動函式之後可以減少碰撞。

    JDK 1.8 HashMap 的 hash 方法原始碼:

    JDK 1.8 的 hash方法 相比於 JDK 1.7 hash 方法更加簡化,但是原理不變。

    java static final int hash(Object key) { int h; // key.hashCode():傳回雜湊值也就是hashcode // ^ :按位異或 // >>>:無符號右移,忽略符號位,空位都以0補齊 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 
    對比一下 JDK1.7的 HashMap 的 hash 方法原始碼.

    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).

        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    相比於 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的效能會稍差一點點,因為畢竟擾動了 4 次。

    所謂 “拉鏈法” 就是:將連結串列和陣列相結合。也就是說建立一個連結串列陣列,陣列中每一格就是一個連結串列。若遇到雜湊衝突,則將衝突的值加到連結串列中即可。

    jdk1.8之前的內部結構

    ②JDK1.8之後

    相比於之前的版本, JDK1.8之後在解決雜湊衝突時有了較大的變化,當連結串列長度大於閾值(預設為8)時,將連結串列轉化為紅黑樹,以減少搜尋時間。

    JDK1.8之後的HashMap底層資料結構

    TreeMap、TreeSet以及JDK1.8之後的HashMap底層都用到了紅黑樹。紅黑樹就是為瞭解決二叉查詢樹的缺陷,因為二叉查詢樹在某些情況下會退化成一個線性結構。

    推薦閱讀:

    • 《Java 8系列之重新認識HashMap》 :https://zhuanlan.zhihu.com/p/21673805

    HashMap 和 Hashtable 的區別

    1. 執行緒是否安全: HashMap 是非執行緒安全的,HashTable 是執行緒安全的;HashTable 內部的方法基本都經過 synchronized 修飾。(如果你要保證執行緒安全的話就使用 ConcurrentHashMap 吧!);

    2. 效率: 因為執行緒安全的問題,HashMap 要比 HashTable 效率高一點。另外,HashTable 基本被淘汰,不要在程式碼中使用它;

    3. 對Null key 和Null value的支援: HashMap 中,null 可以作為鍵,這樣的鍵只有一個,可以有一個或多個鍵所對應的值為 null。。但是在 HashTable 中 put 進的鍵值只要有一個 null,直接丟擲 NullPointerException。

    4. 初始容量大小和每次擴充容量大小的不同 : ①建立時如果不指定容量初始值,Hashtable 預設的初始大小為11,之後每次擴充,容量變為原來的2n+1。HashMap 預設的初始化大小為16。之後每次擴充,容量變為原來的2倍。②建立時如果給定了容量初始值,那麼 Hashtable 會直接使用你給定的大小,而 HashMap 會將其擴充為2的冪次方大小。也就是說 HashMap 總是使用2的冪作為雜湊表的大小,後面會介紹到為什麼是2的冪次方。

    5. 底層資料結構: JDK1.8 以後的 HashMap 在解決雜湊衝突時有了較大的變化,當連結串列長度大於閾值(預設為8)時,將連結串列轉化為紅黑樹,以減少搜尋時間。Hashtable 沒有這樣的機制。

    HashMap 的長度為什麼是2的冪次方

    為了能讓 HashMap 存取高效,儘量較少碰撞,也就是要儘量把資料分配均勻。我們上面也講到了過了,Hash 值的範圍值-2147483648到2147483648,前後加起來大概40億的對映空間,只要雜湊函式對映得比較均勻鬆散,一般應用是很難出現碰撞的。但問題是一個40億長度的陣列,記憶體是放不下的。所以這個雜湊值是不能直接拿來用的。用之前還要先做對陣列的長度取模運算,得到的餘數才能用來要存放的位置也就是對應的陣列下標。

    這個演演算法應該如何設計呢?

    我們首先可能會想到採用%取餘的操作來實現。但是,重點來了:“取餘(%)操作中如果除數是2的冪次則等價於與其除數減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 並且 採用二進位制位操作 &,相對於%能夠提高運算效率,這就解釋了 HashMap 的長度為什麼是2的冪次方。

    HashMap 多執行緒操作導致死迴圈問題

    在多執行緒下,進行 put 操作會導致 HashMap 死迴圈,原因在於 HashMap 的擴容 resize()方法。由於擴容是新建一個陣列,複製原資料到陣列。由於陣列下標掛有連結串列,所以需要複製連結串列,但是多執行緒操作有可能導致環形連結串列。複製連結串列過程如下: 
    以下模擬2個執行緒同時擴容。假設,當前 HashMap 的空間為2(臨界值為1),hashcode 分別為 0 和 1,在雜湊地址 0 處有元素 A 和 B,這時候要新增元素 C,C 經過 hash 運算,得到雜湊地址為 1,這時候由於超過了臨界值,空間不夠,需要呼叫 resize 方法進行擴容,那麼在多執行緒條件下,會出現條件競爭,模擬過程如下:

    執行緒一:讀取到當前的 HashMap 情況,在準備擴容時,執行緒二介入

    執行緒二:讀取 HashMap,進行擴容

    執行緒一:繼續執行

    這個過程為,先將 A 複製到新的 hash 表中,然後接著複製 B 到鏈頭(A 的前邊:B.next=A),本來 B.next=null,到此也就結束了(跟執行緒二一樣的過程),但是,由於執行緒二擴容的原因,將 B.next=A,所以,這裡繼續複製A,讓 A.next=B,由此,環形鏈表出現:B.next=A; A.next=B

    HashSet 和 HashMap 區別

    如果你看過 HashSet 原始碼的話就應該知道:HashSet 底層就是基於 HashMap 實現的。(HashSet 的原始碼非常非常少,因為除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不實現之外,其他方法都是直接呼叫 HashMap 中的方法。)

    HashSet 和 HashMap 區別

    ConcurrentHashMap 和 Hashtable 的區別

    ConcurrentHashMap 和 Hashtable 的區別主要體現在實現執行緒安全的方式上不同。

    • 底層資料結構: JDK1.7的 ConcurrentHashMap 底層採用 分段的陣列+連結串列 實現,JDK1.8 採用的資料結構跟HashMap1.8的結構一樣,陣列+連結串列/紅黑二叉樹。Hashtable 和 JDK1.8 之前的 HashMap 的底層資料結構類似都是採用 陣列+連結串列的形式,陣列是 HashMap 的主體,連結串列則是主要為瞭解決雜湊衝突而存在的;

    • 實現執行緒安全的方式(重要): ① 在JDK1.7的時候,ConcurrentHashMap(分段鎖) 對整個桶陣列進行了分割分段(Segment),每一把鎖只鎖容器其中一部分資料,多執行緒訪問容器裡不同資料段的資料,就不會存在鎖競爭,提高併發訪問率。(預設分配16個Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的時候已經摒棄了Segment的概念,而是直接用 Node 陣列+連結串列+紅黑樹的資料結構來實現,併發控制使用 synchronized 和 CAS 來操作。(JDK1.6以後 對 synchronized鎖做了很多最佳化) 整個看起來就像是最佳化過且執行緒安全的 HashMap,雖然在JDK1.8中還能看到 Segment 的資料結構,但是已經簡化了屬性,只是為了相容舊版本;② Hashtable(同一把鎖) :使用 synchronized 來保證執行緒安全,效率非常低下。當一個執行緒訪問同步方法時,其他執行緒也訪問同步方法,可能會進入阻塞或輪詢狀態,如使用 put 新增元素,另一個執行緒不能使用 put 新增元素,也不能使用 get,競爭會越來越激烈效率越低。

    兩者的對比圖:

    圖片來源:http://www.cnblogs.com/chengxiao/p/6842045.html

    HashTable:

    JDK1.7的ConcurrentHashMap:


    JDK1.8的ConcurrentHashMap(TreeBin: 紅黑二叉樹節點
    Node: 連結串列節點):

    ConcurrentHashMap執行緒安全的具體實現方式/底層具體實現

    ①JDK1.7(上面有示意圖)

    首先將資料分為一段一段的儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料時,其他段的資料也能被其他執行緒訪問。

    ConcurrentHashMap 是由 Segment 陣列結構和 HahEntry 陣列結構組成

    Segment 實現了 ReentrantLock,所以 Segment 是一種可重入鎖,扮演鎖的角色。HashEntry 用於儲存鍵值對資料。

    static class Segment<K,Vextends ReentrantLock implements Serializable {
    }

    一個 ConcurrentHashMap 裡包含一個 Segment 陣列。Segment 的結構和HashMap類似,是一種陣列和連結串列結構,一個 Segment 包含一個 HashEntry 陣列,每個 HashEntry 是一個連結串列結構的元素,每個 Segment 守護著一個HashEntry陣列裡的元素,當對 HashEntry 陣列的資料進行修改時,必須首先獲得對應的 Segment的鎖。

    ②JDK1.8 (上面有示意圖)

    ConcurrentHashMap取消了Segment分段鎖,採用CAS和synchronized來保證併發安全。資料結構跟HashMap1.8的結構類似,陣列+連結串列/紅黑二叉樹。

    synchronized只鎖定當前連結串列或紅黑二叉樹的首節點,這樣只要hash不衝突,就不會產生併發,效率又提升N倍。

    集合框架底層資料結構總結

    Collection

    1.List

    • Arraylist: Object陣列

    • Vector: Object陣列

    • LinkedList: 雙向迴圈連結串列
      2.Set

    • HashSet(無序,唯一): 基於 HashMap 實現的,底層採用 HashMap 來儲存元素

    • LinkedHashSet: LinkedHashSet 繼承與 HashSet,並且其內部是透過 LinkedHashMap 來實現的。有點類似於我們之前說的LinkedHashMap 其內部是基於 Hashmap 實現一樣,不過還是有一點點區別的。

    • TreeSet(有序,唯一): 紅黑樹(自平衡的排序二叉樹。)

    Map

    • HashMap: JDK1.8之前HashMap由陣列+連結串列組成的,陣列是HashMap的主體,連結串列則是主要為瞭解決雜湊衝突而存在的(“拉鏈法”解決衝突).JDK1.8以後在解決雜湊衝突時有了較大的變化,當連結串列長度大於閾值(預設為8)時,將連結串列轉化為紅黑樹,以減少搜尋時間

    • LinkedHashMap: LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基於拉鏈式雜湊結構即由陣列和連結串列或紅黑樹組成。另外,LinkedHashMap 在上面結構的基礎上,增加了一條雙向連結串列,使得上面的結構可以保持鍵值對的插入順序。同時透過對連結串列進行相應的操作,實現了訪問順序相關邏輯。詳細可以檢視:《LinkedHashMap 原始碼詳細分析(JDK1.8)》

    • HashTable: 陣列+連結串列組成的,陣列是 HashMap 的主體,連結串列則是主要為瞭解決雜湊衝突而存在的

    • TreeMap: 紅黑樹(自平衡的排序二叉樹)

    6.3 Java多執行緒

    關於 Java多執行緒,在面試的時候,問的比較多的就是①悲觀鎖和樂觀鎖( 具體可以看我的這篇文章:面試必備之樂觀鎖與悲觀鎖)、②synchronized和lock區別以及volatile和synchronized的區別③可重入鎖與非可重入鎖的區別④多執行緒是解決什麼問題的⑤執行緒池解決什麼問題⑥執行緒池的原理⑦執行緒池使用時的註意事項⑧AQS原理、⑨ReentranLock原始碼,設計原理,整體過程 等等問題。

      面試官在多執行緒這一部分很可能會問你有沒有在專案中實際使用多執行緒的經歷。所以,如果你在你的專案中有實際使用Java多執行緒的經歷 的話,會為你加分不少哦!

    6.4 Java虛擬機器

      關於Java虛擬機器,在面試的時候一般會問的大多就是①Java記憶體區域②虛擬機器垃圾演演算法③虛擬機器垃圾收集器④JVM記憶體管理⑤JVM調優這些問題了。
      
    具體可以檢視我的這兩篇文章:

    • https://github.com/Snailclimb/JavaGuide/blob/master/Java相關/可能是把Java記憶體區域講的最清楚的一篇文章.md

    • https://github.com/Snailclimb/JavaGuide/blob/master/Java相關/可能是把Java記憶體區域講的最清楚的一篇文章.md

    6.5 設計樣式

    設計樣式比較常見的就是讓你手寫一個單例樣式(註意單例樣式的幾種不同的實現方法)或者讓你說一下某個常見的設計樣式在你的專案中是如何使用的,另外面試官還有可能問你抽象工廠和工廠方法樣式的區別工廠樣式的思想這樣的問題。

    建議把代理樣式觀察者樣式(抽象)工廠樣式好好看一下,這三個設計樣式也很重要。

    七 資料結構

      資料結構比較常問的就是:二叉樹紅黑樹(很可能讓你手繪一個紅黑樹出來哦!)、二叉查詢樹(BST)平衡二叉樹(Self-balancing binary search tree)B-樹,B+樹與B*樹的優缺點比較、 LSM 樹這些知識點。

      資料結構很重要,而且學起來也相對要難一些。建議學習資料結構一定要循序漸進的來,一步一個腳印的走好。一定要搞懂原理,最好自己能用程式碼實現一遍。

    八 演演算法

      常見的加密演演算法、排序演演算法都需要自己提前瞭解一下,排序演演算法最好自己能夠獨立手寫出來。

      我覺得面試中最刺激、最有壓力或者說最有挑戰的一個環節就是手撕演演算法了。面試中大部分演演算法題目都是來自於Leetcode、劍指offer上面,建議大家可以每天擠出一點時間刷一下演演算法題。

    推薦兩個刷題必備網站:

    LeetCode:

    • LeetCode(中國)官網: https://leetcode-cn.com/

    牛客網:

    • 牛客網首頁

    8.1 舉個慄子(手寫快排)

    面試官可能會問你,瞭解哪些排序方法啊?除了氣泡排序和選擇排序能不能給我手寫一個其他的排序演演算法。或者面試官可能會直接問你:“能不能給我手寫一個快排出來?”。

    快排的基本思想: 透過選擇的參考值將待排序記錄分割成獨立的兩部分,一部分全小於選取的參考值,另一部分全大於選取的參考值。對分割之後的部分再進行同樣的操作直到無法再進行該操作位置(可以使用遞迴)。

    下麵是我寫的一個簡單的快排演演算法,我選擇的參考值是陣列的第一個元素。

    import java.util.Arrays;

    public class QuickSort {

        public static void main(String[] args) {
            // TODO Auto-generated method stub

            int[] num = { 1348510221516 };
            QuickSort.quickSort(num, 0, num.length - 1);
            System.out.println(Arrays.toString(num));
        }

        public static void quickSort(int[] a, int start, int end) {
            // 該值定義了從哪個位置開始分割陣列
            int ref;
            if (start             // 呼叫partition方法對陣列進行排序
                ref = partition(a, start, end);
                // 對分割之後的兩個陣列繼續進行排序
                quickSort(a, start, ref - 1);
                quickSort(a, ref + 1, end);
            }
        }

        /**
         * 選定參考值對給定陣列進行一趟快速排序
         * 
         * @param a
         *            陣列
         * @param start
         *            (切分)每個陣列的第一個的元素的位置
         * @param end
         *            (切分)每個陣列的最後一個的元素位置
         * @return 下一次要切割陣列的位置
         */

        public static int partition(int[] a, int start, int end) {
            // 取陣列的第一個值作為參考值(關鍵資料)
            int refvalue = a[start];
            // 從陣列的右邊開始往左遍歷,直到找到小於參考值的元素
            while (start             while (end > start && a[end] >= refvalue) {
                    end--;
                }
                // 將元素直接賦予給左邊第一個元素,即pivotkey所在的位置
                a[start] = a[end];

                // 從序列的左邊邊開始往右遍歷,直到找到大於基準值的元素
                while (end > start && a[start] <= refvalue) {
                    start++;
                }
                a[end] = a[start];
                return end;
            }
            // 最後的start是基準值所在的位置
            a[start] = refvalue;
            return start;
        }

    }

    時間複雜度分析:

    • 在最優的情況下,Partition每次都劃分得很均勻,快速排序演演算法的時間複雜度為O(nlogn)。

    • 最糟糕情況下的快排,當待排序的序列為正序或逆序排列時,時間複雜度為O(n^2)。

    空間複雜度分析:

    • 最好情況,遞迴樹的深度為log2n,其空間複雜度也就為O(logn)

    • 最壞情況,需要進行n‐1遞迴呼叫,其空間複雜度為O(n),平均情況,空間複雜度也為O(logn)。

    一種簡單最佳化的方式:

    三向切分快速排序 :核心思想就是將待排序的資料分為三部分,左邊都小於比較值,右邊都大於比較值,中間的數和比較值相等.三向切分快速排序的特性就是遇到和比較值相同時,不進行資料交換, 這樣對於有大量重覆資料的排序時,三向切分快速排序演演算法就會優於普通快速排序演演算法,但由於它整體判斷程式碼比普通快速排序多一點,所以對於常見的大量非重覆資料,它並不能比普通快速排序多大多的優勢 。

    九 Spring

      Spring一般是不可避免的,如果你的簡歷上註明瞭你會Spring Boot或者Spring Cloud的話,那麼面試官也可能會同時問你這兩個技術,比如他可能會問你springboot和spring的區別。 所以,一定要謹慎對待寫在簡歷上的東西,一定要對簡歷上的東西非常熟悉。

      另外,AOP實現原理動態代理靜態代理Spring IOC的初始化過程IOC原理自己怎麼實現一個IOC容器? 這些東西都是經常會被問到的。

    9.1 Spring Bean 的作用域

    9.2 Spring 事務中的隔離級別

    TransactionDefinition 介面中定義了五個表示隔離級別的常量:

    • TransactionDefinition.ISOLATION_DEFAULT: 使用後端資料庫預設的隔離級別,Mysql 預設採用的 REPEATABLE_READ隔離級別 Oracle 預設採用的 READ_COMMITTED隔離級別.

    • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致臟讀、幻讀或不可重覆讀

    • TransactionDefinition.ISOLATION_READ_COMMITTED: 允許讀取併發事務已經提交的資料,可以阻止臟讀,但是幻讀或不可重覆讀仍有可能發生

    • TransactionDefinition.ISOLATION_REPEATABLE_READ: 對同一欄位的多次讀取結果都是一致的,除非資料是被本身事務自己所修改,可以阻止臟讀和不可重覆讀,但幻讀仍有可能發生。

    • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生幹擾,也就是說,該級別可以防止臟讀、不可重覆讀以及幻讀。但是這將嚴重影響程式的效能。通常情況下也不會用到該級別。

    9.3 Spring 事務中的事務傳播行為

    支援當前事務的情況:

    • TransactionDefinition.PROPAGATION_REQUIRED: 如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。

    • TransactionDefinition.PROPAGATION_SUPPORTS: 如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。

    • TransactionDefinition.PROPAGATION_MANDATORY: 如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。(mandatory:強制性)

    不支援當前事務的情況:

    • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 建立一個新的事務,如果當前存在事務,則把當前事務掛起。

    • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事務方式執行,如果當前存在事務,則把當前事務掛起。

    • TransactionDefinition.PROPAGATION_NEVER: 以非事務方式執行,如果當前存在事務,則丟擲異常。

    其他情況:

    • TransactionDefinition.PROPAGATION_NESTED: 如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。

    9.4 AOP

    AOP思想的實現一般都是基於 代理樣式 ,在JAVA中一般採用JDK動態代理樣式,但是我們都知道,JDK動態代理樣式只能代理介面而不能代理類。因此,Spring AOP 會這樣子來進行切換,因為Spring AOP 同時支援 CGLIB、ASPECTJ、JDK動態代理。

    • 如果標的物件的實現類實現了介面,Spring AOP 將會採用 JDK 動態代理來生成 AOP 代理類;

    • 如果標的物件的實現類沒有實現介面,Spring AOP 將會採用 CGLIB 來生成 AOP 代理類——不過這個選擇過程對開發者完全透明、開發者也無需關心。

    這部分內容可以檢視下麵這幾篇文章:

    • https://www.jianshu.com/p/fe8d1e8bd63e

    • http://www.cnblogs.com/puyangsky/p/6218925.html

    • https://juejin.im/post/5a55af9e518825734d14813f

    9.5 IOC

    Spring IOC的初始化過程: 

    Spring IOC的初始化過程

    IOC原始碼閱讀

    • https://javadoop.com/post/spring-ioc

    十 實際場景題

      我覺得實際場景題就是對你的知識運用能力以及思維能力的考察。建議大家在平時養成多思考問題的習慣,這樣面試的時候碰到這樣的問題就不至於慌了。另外,如果自己實在不會就給面試官委婉的說一下,面試官可能會給你提醒一下。切忌不懂裝懂,亂答一氣。
      
      面試官可能會問你類似這樣的問題:①假設你要做一個銀行app,有可能碰到多個人同時向一個賬戶打錢的情況,有可能碰到什麼問題,如何解決(鎖)②你是怎麼保證你的程式碼質量和正確性的?③下單過程中是下訂單減庫存還是付款減庫存,分析一下兩者的優劣;④同時給10萬個人發工資,怎麼樣設計併發方案,能確保在1分鐘內全部發完。⑤如果讓你設計xxx系統的話,你會如何設計。
      

    寫在最後

    最後,再強調幾點:

    1. 一定要謹慎對待寫在簡歷上的東西,一定要對簡歷上的東西非常熟悉。因為一般情況下,面試官都是會根據你的簡歷來問的;

    2. 能有一個上得了臺面的專案也非常重要,這很可能是面試官會大量發問的地方,所以在面試之前好好回顧一下自己所做的專案;

    3. 和麵試官聊基礎知識比如設計樣式的使用、多執行緒的使用等等,可以結合具體的專案場景或者是自己在平時是如何使用的;

    4. 註意自己開源的Github專案,面試官可能會挖你的Github專案提問;

    5. 建議提前瞭解一下自己想要面試的公司的價值觀,判斷一下自己究竟是否適合這個公司。

    另外,我個人覺得面試也像是一場全新的徵程,失敗和勝利都是平常之事。所以,勸各位不要因為面試失敗而灰心、喪失鬥志。也不要因為面試透過而沾沾自喜,等待你的將是更美好的未來,繼續加油!

    除次之外,筆主也在這裡給自己挖一個坑,關於 dubbo、zookeeper 等內容我會在後續做一個系統總結。保證大家看了之後,一定有收穫!


    編號816,輸入編號直達本文

    ●輸入m獲取文章目錄

    推薦↓↓↓

    Web開發

    更多推薦18個技術類微信公眾號

    涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

    贊(0)

    分享創造快樂