寫本文之前,其實我自己已經開源了一個 Java學習指南的文檔,裡面包含了一些基礎知識和一些偏後端(Java方向)的知識。到目前為止收穫了 6.1k star 以及 1.5 k fork,close 了 10個 pr 以及 10 個issue。開源只是為了讓更多的人看到和參與進來,這樣文檔的正確性和質量才能很好的保證。畢竟,我個人能力、時間以及知識廣度和深度有限,一份好的項目的誕生肯定離不開和其他人的共同努力。
另外,我個人覺得不論你是前端還是後端(部分內容可能會偏 Java 方向一點)都能從本文中學到東西。
本人技術水品有限,歡迎各位指正!寫的不好的話,請多見諒!
目錄
- 前言
- 一 簡歷該如何寫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 三次握手和四次揮手
- 三 Linux3.1-簡單介紹一下-linux-文件系統?
- 3.2 一些常見的 Linux 命令瞭解嗎?
- 四 MySQL4.1 說說自己對於 MySQL 常見的兩種存儲引擎:MyISAM與InnoDB的理解
- 4.2 數據庫索引瞭解嗎?
- 4.3 對於大表的常見優化手段說一下
- 五 Redis5.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 如何保證緩存與數據庫雙寫時的數據一致性?
- 六 Java6.1 Java 基礎知識
- 6.2 Java 集合框架
- 6.3 Java多線程
- 6.4 Java虛擬機
- 6.5 設計模式
- 七 數據結構
- 八 算法
- 九 Spring9.1 Spring Bean 的作用域
- 9.2 Spring 事務中的隔離級別
- 9.3 Spring 事務中的事務傳播行為
- 9.4 AOP
- 9.5 IOC
- 十 實際場景題
關注我:私信回覆“架構資料”獲取往期Java高級架構資料、源碼、筆記、視頻
Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術
前言
不論是校招還是社招都避免不了各種面試、筆試,如何去準備這些東西就顯得格外重要。不論是筆試還是面試都是有章可循的,我這個“有章可循”說的意思只是說應對技術面試是可以提前準備。 我其實特別不喜歡那種臨近考試就提前背啊記啊各種題的行為,非常反對!我覺得這種方法特別極端,而且在稍有一點經驗的面試官面前是根本沒有用的。建議大家還是一步一個腳印踏踏實實地走。
運籌帷幄之後,決勝千里之外!不打毫無準備的仗,我覺得大家可以先從下面幾個方面來準備面試:
- 自我介紹。(你可千萬這樣介紹:“我叫某某,性別,來自哪裡,學校是那個,自己愛幹什麼”,記住:多說點簡歷上沒有的,多說點自己哪裡比別人強!)
- 自己面試中可能涉及哪些知識點、那些知識點是重點。
- 面試中哪些問題會被經常問到、面試中自己改如何回答。(強烈不推薦背題,第一:通過背這種方式你能記住多少?能記住多久?第二:背題的方式的學習很難堅持下去!)
- 自己的簡歷該如何寫。
“80%的offer掌握在20%的人手中” 這句話也不是不無道理的。決定你面試能否成功的因素中實力固然佔有很大一部分比例,但是如果你的心態或者說運氣不好的話,依然無法拿到滿意的 offer。運氣暫且不談,就拿心態來說,千萬不要因為面試失敗而氣餒或者說懷疑自己的能力,面試失敗之後多總結一下失敗的原因,後面你就會發現自己會越來越強大。
另外,大家要明確的很重要的幾點是:
- 寫在簡歷上的東西一定要慎重,這可能是面試官大量提問的地方;
- 大部分應屆生找工作的硬傷是沒有工作經驗或實習經歷;
- 將自己的項目經歷完美的展示出來非常重要。
筆主能力有限,如果有不對的地方或者和你想法不同的地方,敬請雅正、不捨賜教。
一 簡歷該如何寫
俗話說的好:“工欲善其事,必先利其器”。準備一份好的簡歷對於能不能找到一份好工作起到了至關重要的作用。
1.1 為什麼說簡歷很重要?
假如你是網申,你的簡歷必然會經過HR的篩選,一張簡歷HR可能也就花費10秒鐘看一下,然後HR就會決定你這一關是Fail還是Pass。
假如你是內推,如果你的簡歷沒有什麼優勢的話,就算是內推你的人再用心,也無能為力。
另外,就算你通過了篩選,後面的面試中,面試官也會根據你的簡歷來判斷你究竟是否值得他花費很多時間去面試。
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.5 專業技能該怎麼寫?
先問一下你自己會什麼,然後看看你意向的公司需要什麼。一般HR可能並不太懂技術,所以他在篩選簡歷的時候可能就盯著你專業技能的關鍵詞來看。對於公司有要求而你不會的技能,你可以花幾天時間學習一下,然後在簡歷上可以寫上自己瞭解這個技能。比如你可以這樣寫:
- Dubbo:精通
- Spring:精通
- Docker:掌握
- SOA分佈式開發 :掌握
- Spring Cloud:瞭解
1.6 開源程序員簡歷模板分享
- 儘量避免主觀表述,少一點語義模糊的形容詞,儘量要簡潔明瞭,邏輯結構清晰。
- 注意排版(不需要花花綠綠的),儘量使用Markdown語法。
- 如果自己有博客或者個人技術棧點的話,寫上去會為你加分很多。
- 如果自己的Github比較活躍的話,寫上去也會為你加分很多。
- 注意簡歷真實性,一定不要寫自己不會的東西,或者帶有欺騙性的內容
- 項目經歷建議以時間倒序排序,另外項目經歷不在於多,而在於有亮點。
- 如果內容過多的話,不需要非把內容壓縮到一頁,保持排版乾淨整潔就可以了。
- 簡歷最後最好能加上:“感謝您花時間閱讀我的簡歷,期待能有機會和您共事。”這句話,顯的你會很有禮貌。
二 計算機網絡常見面試點總結
計算機網絡常見問題回顧
- TCP三次握手和四次揮手、
- 在瀏覽器中輸入url地址->>顯示主頁的過程
- TCP 協議如何保證可靠傳輸
- HTTP和HTTPS的區別
- TCP、UDP協議的區別
- 常見的狀態碼。
下面列舉幾個常見問題的回答!
2.1 TCP、UDP 協議的區別
UDP 在傳送數據之前不需要先建立連接,遠地主機在收到 UDP 報文後,不需要給出任何確認。雖然 UDP 不提供可靠交付,但在某些情況下 UDP 確是一種最有效的工作方式(一般用於即時通信),比如: QQ 語音、 QQ 視頻 、直播等等
TCP 提供面向連接的服務。在傳送數據之前必須先建立連接,數據傳送結束後要釋放連接。 TCP 不提供廣播或多播服務。由於 TCP 要提供可靠的,面向連接的運輸服務(TCP的可靠體現在TCP在傳遞數據之前,會有三次握手來建立連接,而且在數據傳遞時,有確認、窗口、重傳、擁塞控制機制,在數據傳完後,還會斷開連接用來節約系統資源),這一難以避免增加了許多開銷,如確認,流量控制,計時器以及連接管理等。這不僅使協議數據單元的首部增大很多,還要佔用許多處理機資源。TCP 一般用於文件傳輸、發送和接收郵件、遠程登錄等場景。
2.2 在瀏覽器中輸入url地址 ->> 顯示主頁的過程
打開一個網頁,整個過程會使用哪些協議
總體來說分為以下幾個過程:
- DNS解析
- TCP連接
- 發送HTTP請求
- 服務器處理請求並返回HTTP報文
- 瀏覽器解析渲染頁面
- 連接結束
2.3 各種協議與HTTP協議之間的關係
一般面試官會通過這樣的問題來考察你對計算機網絡知識體系的理解。
2.4 HTTP長連接、短連接
在HTTP/1.0中默認使用短連接。也就是說,客戶端和服務器每進行一次HTTP操作,就建立一次連接,任務結束就中斷連接。當客戶端瀏覽器訪問的某個HTML或其他類型的Web頁中包含有其他的Web資源(如JavaScript文件、圖像文件、CSS文件等),每遇到這樣一個Web資源,瀏覽器就會重新建立一個HTTP會話。
而從HTTP/1.1起,默認使用長連接,用以保持連接特性。使用長連接的HTTP協議,會在響應頭加入這行代碼:
<code>Connection:keep-alive複製代碼/<code>
在使用長連接的情況下,當一個網頁打開完成後,客戶端和服務器之間用於傳輸HTTP數據的TCP連接不會關閉,客戶端再次訪問這個服務器時,會繼續使用這一條已經建立的連接。Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。實現長連接需要客戶端和服務端都支持長連接。
HTTP協議的長連接和短連接,實質上是TCP協議的長連接和短連接。
2.5 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 連接則需要“四次揮手”:
- 客戶端-發送一個 FIN,用來關閉客戶端到服務器的數據傳送
- 服務器-收到這個 FIN,它發回一 個 ACK,確認序號為收到的序號加1 。和 SYN 一樣,一個 FIN 將佔用一個序號
- 服務器-關閉與客戶端的連接,發送一個FIN給客戶端
- 客戶端-發回 ACK 報文確認,並將確認序號設置為收到序號加1
為什麼要四次揮手
任何一方都可以在數據傳送結束後發出連接釋放的通知,待對方確認後進入半關閉狀態。當另一方也沒有數據再發送的時候,則發出連接釋放通知,對方確認後就完全關閉了TCP連接。
舉個例子:A 和 B 打電話,通話即將結束後,A 說“我沒啥要說的了”,B回答“我知道了”,但是 B 可能還會有要說的話,A 不能要求 B 跟著自己的節奏結束通話,於是 B 可能又巴拉巴拉說了一通,最後 B 說“我說完了”,A 回答“知道了”,這樣通話才算結束。
三 Linux
3.1 簡單介紹一下 Linux 文件系統?
Linux文件系統簡介
在Linux操作系統中,所有被操作系統管理的資源,例如網絡接口卡、磁盤驅動器、打印機、輸入輸出設備、普通文件或是目錄都被看作是一個文件。
也就是說在LINUX系統中有一個重要的概念:一切都是文件。其實這是UNIX哲學的一個體現,而Linux是重寫UNIX而來,所以這個概念也就傳承了下來。在UNIX系統中,把一切資源都看作是文件,包括硬件設備。UNIX系統把每個硬件都看成是一個文件,通常稱為設備文件,這樣用戶就可以用讀寫文件的方式實現對硬件的訪問。
文件類型與目錄結構
Linux支持5種文件類型 :
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 -: 切換到上一個所在目錄
目錄的操作命令(增刪改查)
- mkdir 目錄名稱: 增加目錄
- ls或者ll(ll是ls -l的縮寫,ll命令以看到該目錄下的所有目錄和文件的詳細信息):查看目錄信息
- find 目錄 參數: 尋找目錄(查)
- mv 目錄名稱 新目錄名稱: 修改目錄的名稱(改)
- 注意:mv的語法不僅可以對目錄進行重命名而且也可以對各種文件,壓縮包等進行 重命名的操作。mv命令用來對文件或目錄重新命名,或者將文件從一個目錄移到另一個目錄中。後面會介紹到mv命令的另一個用法。
- mv 目錄名稱 目錄的新位置: 移動目錄的位置---剪切(改)
- 注意:mv語法不僅可以對目錄進行剪切操作,對文件和壓縮包等都可執行剪切操作。另外mv與cp的結果不同,mv好像文件“搬家”,文件個數並未增加。而cp對文件進行復制,文件個數增加了。
- cp -r 目錄名稱 目錄拷貝的目標位置: 拷貝目錄(改),-r代表遞歸拷貝
- 注意:cp命令不僅可以拷貝目錄還可以拷貝文件,壓縮包等,拷貝文件和壓縮包時不 用寫-r遞歸
- rm [-rf] 目錄: 刪除目錄(刪)
- 注意:rm不僅可以刪除目錄,也可以刪除其他文件或壓縮包,為了增強大家的記憶, 無論刪除任何目錄或文件,都直接使用rm -rf 目錄/文件/壓縮包
文件的操作命令(增刪改查)
- touch 文件名稱: 文件的創建(增)
- cat/more/less/tail 文件名稱 文件的查看(查)
- cat: 只能顯示最後一屏內容
- more: 可以顯示百分比,回車可以向下一行, 空格可以向下一頁,q可以退出查看
- less: 可以使用鍵盤上的PgUp和PgDn向上 和向下翻頁,q結束查看
- tail-10 : 查看文件的後10行,Ctrl+C結束
- 注意:命令 tail -f 文件 可以對某個文件進行動態監控,例如tomcat的日誌文件, 會隨著程序的運行,日誌會變化,可以使用tail -f catalina-2016-11-11.log 監控 文 件的變化
- 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
4.1 說說自己對於 MySQL 常見的兩種存儲引擎:MyISAM與InnoDB的理解
關於二者的對比與總結:
- count運算上的區別:因為MyISAM緩存有表meta-data(行數等),因此在做COUNT(*)時對於一個結構很好的查詢是不需要消耗多少資源的。而對於InnoDB來說,則沒有這種緩存。
- 是否支持事務和崩潰後的安全恢復: MyISAM 強調的是性能,每次查詢具有原子性,其執行數度比InnoDB類型更快,但是不提供事務支持。但是InnoDB 提供事務支持事務,外部鍵等高級數據庫功能。 具有事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。
- 是否支持外鍵: 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工程師修煉之道》
4.3 對於大表的常見優化手段說一下
當MySQL單表記錄數過大時,數據庫的CRUD性能會明顯下降,一些常見的優化措施如下:
- 限定數據的範圍: 務必禁止不帶任何限制數據範圍條件的查詢語句。比如:我們當用戶在查詢訂單歷史的時候,我們可以控制在一個月的範圍內。;
- 讀/寫分離: 經典的數據庫拆分方案,主庫負責寫,從庫負責讀;
- 緩存: 使用MySQL的緩存,另外對重量級、更新少的數據可以考慮使用應用級別的緩存;
- 垂直分區:
- 根據數據庫裡面數據表的相關性進行拆分。 例如,用戶表中既有用戶的登錄信息又有用戶的基本信息,可以將用戶表拆分成兩個單獨的表,甚至放到單獨的庫做分庫。
- 簡單來說垂直拆分是指數據表列的拆分,把一張列比較多的表拆分為多張表。 如下圖所示,這樣來說大家應該就更容易理解了。
垂直拆分的優點: 可以使得行數據變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分區可以簡化表的結構,易於維護。
垂直拆分的缺點: 主鍵會出現冗餘,需要管理冗餘列,並會引起Join操作,可以通過在應用層進行Join來解決。此外,垂直分區會讓事務變得更加複雜;
水平分區:
保持數據表結構不變,通過某種策略存儲數據分片。這樣每一片數據分散到不同的表或者庫中,達到了分佈式的目的。 水平拆分可以支撐非常大的數據量。
水平拆分是指數據錶行的拆分,表的行數超過200萬行時,就會變慢,這時可以把一張的表的數據拆成多張表來存放。舉個例子:我們可以將用戶信息表拆分成多個用戶信息表,這樣就可以避免單一表數據量過大對性能造成影響。
- 水品拆分可以支持非常大的數據量。需要注意的一點是:分表僅僅是解決了單一表數據過大的問題,但由於表的數據還是在同一臺機器上,其實對於提升MySQL併發能力沒有什麼意義,所以 水品拆分最好分庫 。
- 水平拆分能夠 支持非常大的數據量存儲,應用端改造也少,但 分片事務難以解決 ,跨界點Join性能較差,邏輯複雜。《Java工程師修煉之道》的作者推薦 儘量不要對數據進行分片,因為拆分會帶來邏輯、部署、運維的各種複雜度 ,一般的數據表在優化得當的情況下支撐千萬以下的數據量是沒有太大問題的。如果實在要分片,儘量選擇客戶端分片架構,這樣可以減少一次和中間件的網絡I/O。
- 下面補充一下數據庫分片的兩種常見方案:
- 客戶端代理: 分片邏輯在應用端,封裝在jar包中,通過修改或者封裝JDBC層來實現。 噹噹網的 Sharding-JDBC 、阿里的TDDL是兩種比較常用的實現。
- 中間件代理: 在應用和數據中間加了一個代理層。分片邏輯統一維護在中間件服務中。 我們現在談的 Mycat 、360的Atlas、網易的DDB等等都是這種架構的實現。
五 Redis
關於 redis 必知必會的11個問題!後兩個問題,暫未更新!如有需要,可以關注,私信回覆“架構資料”即可 領取
- redis 簡介
- 為什麼要用 redis /為什麼要用緩存
- 為什麼要用 redis 而不用 map/guava 做緩存?
- redis 和 memcached 的區別
- redis 常見數據結構以及使用場景分析
- redis 設置過期時間
- redis 內存淘汰機制
- redis 持久化機制(怎麼保證 redis 掛掉之後再重啟數據可以進行恢復)
- 緩存雪崩和緩存穿透問題解決方案
- 如何解決 Redis 的併發競爭 Key 問題
- 如何保證緩存與數據庫雙寫時的數據一致性?
5.1 redis 簡介
Redis 是一個開源(BSD許可)的,內存中的數據結構存儲系統,它可以用作數據庫、緩存和消息中間件。 它支持多種類型的數據結構,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 與範圍查詢, bitmaps, hyperloglogs 和 地理空間(geospatial) 索引半徑查詢。 Redis 內置了 複製(replication),LUA腳本(Lua>
5.2 為什麼要用 redis /為什麼要用緩存
主要從“高性能”和“高併發”這兩點來看待這個問題。
高性能:
假如用戶第一次訪問數據庫中的某些數據。這個過程會比較慢,因為是從硬盤上讀取的。將該用戶訪問的數據存在數緩存中,這樣下一次再訪問這些數據的時候就可以直接從緩存中獲取了。操作緩存就是直接操作內存,所以速度相當快。如果數據庫中的對應數據改變的之後,同步改變緩存中相應的數據即可!
高併發:
直接操作緩存能夠承受的請求是遠遠大於直接訪問數據庫的,所以我們可以考慮把數據庫中的部分數據轉移到緩存中去,這樣用戶的一部分請求會直接到緩存這裡而不用經過數據庫。
5.3 為什麼要用 redis 而不用 map/guava 做緩存?
緩存分為本地緩存和分佈式緩存。以java為例,使用自帶的map或者guava實現的是本地緩存,最主要的特點是輕量以及快速,生命週期隨著 jvm 的銷燬而結束,並且在多實例的情況下,每個實例都需要各自保存一份緩存,緩存不具有一致性。
使用 redis 或 memcached 之類的稱為分佈式緩存,在多實例的情況下,各實例共用一份緩存數據,緩存具有一致性。缺點是需要保持 redis 或 memcached服務的高可用,整個程序架構上較為複雜。
5.4 redis 和 memcached 的區別
對於 redis 和 memcached 我總結了下面四點。現在公司一般都是用 redis 來實現緩存,而且 redis 自身也越來越強大了!
- redis支持更豐富的數據類型(支持更復雜的應用場景):Redis不僅僅支持簡單的k/v類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。memcache支持簡單的數據類型,String。
- Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啟的時候可以再次加載進行使用,而Memecache把數據全部存在內存之中。
- 集群模式:memcached沒有原生的集群模式,需要依靠客戶端來實現往集群中分片寫入數據;但是redis目前是原生支持cluster模式的,redis官方就是支持redis cluster集群模式的,比memcached來說要更好。
- Memcached是多線程,非阻塞IO複用的網絡模型;Redis使用單線程的多路 IO 複用模型。
來自網絡上的一張圖,這裡分享給大家!
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 類型存放了我本人的一些信息:
<code>key=JavaUser293847value={ “id”: 1, “name”: “SnailClimb”, “age”: 22, “location”: “Wuhan, Hubei”}複製代碼/<code>
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可以非常方便的實現如共同關注、共同粉絲、共同喜好等功能。這個過程也就是求交集的過程,具體命令如下:
<code>sinterstore key1 key2 key3 將交集存在key1內複製代碼/<code>
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 中有相關注釋,我這裡就不貼了,大家可以自行查閱或者通過這個網址查看: download.redis.io/redis-stabl…
redis 提供 6種數據淘汰策略:
- volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
- volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
- volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
- allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key(這個是最常用的).
- allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
- 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配置文件中默認有此下配置:
<code>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命令創建快照。複製代碼/<code>
AOF(append-only file)持久化
與快照持久化相比,AOF持久化 的實時性更好,因此已成為主流的持久化方案。默認情況下Redis沒有開啟AOF(append only file)方式的持久化,可以通過appendonly參數開啟:
<code>appendonly yes複製代碼/<code>
開啟AOF持久化後每執行一條會更改Redis中的數據的命令,Redis就會將該命令寫入硬盤中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數設置的,默認的文件名是appendonly.aof。
在Redis的配置文件中存在三種不同的 AOF 持久化方式,它們分別是:
<code>appendfsync always #每次有數據修改發生時都會寫入AOF文件,這樣會嚴重降低Redis的速度appendfsync everysec #每秒鐘同步一次,顯示地將多個寫命令同步到硬盤appendfsync no #讓操作系統決定何時進行同步複製代碼/<code>
為了兼顧數據和寫入性能,用戶可以考慮 appendfsync everysec選項 ,讓Redis每秒同步一次AOF文件,Redis性能幾乎沒受到任何影響。而且這樣即使出現系統崩潰,用戶最多隻會丟失一秒之內產生的數據。當硬盤忙於執行寫入操作的時候,Redis還會優雅的放慢自己的速度以便適應硬盤的最大寫入速度。
補充內容:AOF 重寫
AOF重寫可以產生一個新的AOF文件,這個新的AOF文件和原有的AOF文件所保存的數據庫狀態一樣,但體積更小。
AOF重寫是一個有歧義的名字,該功能是通過讀取數據庫中的鍵值對來實現的,程序無須對現有AOF文件進行任伺讀入、分析或者寫人操作。
在執行 BGREWRITEAOF 命令時,Redis 服務器會維護一個 AOF 重寫緩衝區,該緩衝區會在子進程創建新AOF文件期間,記錄服務器執行的所有寫命令。當子進程完成創建新AOF文件的工作之後,服務器會將重寫緩衝區中的所有內容追加到新AOF文件的末尾,使得新舊兩個AOF文件所保存的數據庫狀態一致。最後,服務器用新的AOF文件替換舊的AOF文件,以此來完成AOF文件重寫操作
5.9 緩存雪崩和緩存穿透問題解決方案
緩存雪崩
簡介:緩存同一時間大面積的失效,所以,後面的請求都會落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。
解決辦法(中華石杉老師在他的視頻中提到過):
- 事前:儘量保證整個 redis 集群的高可用性,發現機器宕機儘快補上。選擇合適的內存淘汰策略。
- 事中:本地ehcache緩存 + hystrix限流&降級,避免MySQL崩掉
- 事後:利用 redis 持久化機制保存的數據儘快恢復緩存
緩存穿透
簡介:一般是黑客故意去請求緩存中不存在的數據,導致所有的請求都落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。
解決辦法: 有很多種方法可以有效地解決緩存穿透問題,最常見的則是採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。另外也有一個更為簡單粗暴的方法(我們採用的就是這種),如果一個查詢返回的數據為空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。
六 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
<code>abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; int count; AbstractStringBuilder() { } AbstractStringBuilder(int capacity) { value = new char[capacity]; }複製代碼/<code>
線程安全性
String 中的對象是不可變的,也就可以理解為常量,線程安全。AbstractStringBuilder 是 StringBuilder 與 StringBuffer 的公共父類,定義了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 對方法加了同步鎖或者對調用的方法加了同步鎖,所以是線程安全的。StringBuilder 並沒有對方法進行加同步鎖,所以是非線程安全的。
性能
每次對 String 類型進行改變的時候,都會生成一個新的 String 對象,然後將指針指向新的 String 對象。StringBuffer 每次都會對 StringBuffer 對象本身進行操作,而不是生成新的對象並改變對象引用。相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的性能提升,但卻要冒多線程不安全的風險。
對於三者使用的總結:
- 操作少量的數據 = String
- 單線程操作字符串緩衝區下操作大量數據 = StringBuilder
- 多線程操作字符串緩衝區下操作大量數據 = StringBuffer
自動裝箱與拆箱
裝箱:將基本類型用它們對應的引用類型包裝起來;
拆箱:將包裝類型轉換為基本數據類型;
== 與 equals
== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象。(基本數據類型==比較的是值,引用數據類型==比較的是內存地址)
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:
- 情況1:類沒有覆蓋 equals() 方法。則通過 equals() 比較該類的兩個對象時,等價於通過“==”比較這兩個對象。
- 情況2:類覆蓋了 equals() 方法。一般,我們都覆蓋 equals() 方法來兩個對象的內容相等;若它們的內容相等,則返回 true (即,認為這兩個對象相等)。
舉個例子:
<code>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"); } }}複製代碼/<code>
說明:
- String 中的 equals 方法是被重寫過的,因為 object 的 equals 方法是比較的對象的內存地址,而 String 的 equals 方法比較的是對象的值。
- 當創建 String 類型的對象時,虛擬機會在常量池中查找有沒有已經存在的值和要創建的值相同的對象,如果有就把它賦給當前引用。如果沒有就在常量池中重新創建一個 String 對象。
關於 final 關鍵字的一些總結
final關鍵字主要用在三個地方:變量、方法、類。
- 對於一個final變量,如果是基本數據類型的變量,則其數值一旦在初始化之後便不能更改;如果是引用類型的變量,則在對其初始化之後便不能再讓其指向另一個對象。
- 當用final修飾一個類時,表明這個類不能被繼承。final類中的所有成員方法都會被隱式地指定為final方法。
- 使用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 方法更加簡化,但是原理不變。
<code> static final int hash(Object key) { int h; // key.hashCode():返回散列值也就是hashcode // ^ :按位異或 // >>>:無符號右移,忽略符號位,空位都以0補齊 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }複製代碼/<code>
對比一下 JDK1.7的 HashMap 的 hash 方法源碼.
<code>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);}複製代碼/<code>
相比於 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能會稍差一點點,因為畢竟擾動了 4 次。
所謂 “拉鍊法” 就是:將鏈表和數組相結合。也就是說創建一個鏈表數組,數組中每一格就是一個鏈表。若遇到哈希衝突,則將衝突的值加到鏈表中即可。
②JDK1.8之後
相比於之前的版本, JDK1.8之後在解決哈希衝突時有了較大的變化,當鏈表長度大於閾值(默認為8)時,將鏈表轉化為紅黑樹,以減少搜索時間。
TreeMap、TreeSet以及JDK1.8之後的HashMap底層都用到了紅黑樹。紅黑樹就是為了解決二叉查找樹的缺陷,因為二叉查找樹在某些情況下會退化成一個線性結構。
設計模式
設計模式比較常見的就是讓你手寫一個單例模式(注意單例模式的幾種不同的實現方法)或者讓你說一下某個常見的設計模式在你的項目中是如何使用的,另外面試官還有可能問你抽象工廠和工廠方法模式的區別、工廠模式的思想這樣的問題。
建議把代理模式、觀察者模式、(抽象)工廠模式好好看一下,這三個設計模式也很重要。
七 數據結構
數據結構比較常問的就是:二叉樹、紅黑樹(很可能讓你手繪一個紅黑樹出來哦!)、二叉查找樹(BST) 、平衡二叉樹(Self-balancing binary search tree)、B-樹,B+樹與B*樹的優缺點比較、 LSM 樹這些知識點。
數據結構很重要,而且學起來也相對要難一些。建議學習數據結構一定要循序漸進的來,一步一個腳印的走好。一定要搞懂原理,最好自己能用代碼實現一遍。
八 算法
常見的加密算法、排序算法都需要自己提前瞭解一下,排序算法最好自己能夠獨立手寫出來。
我覺得面試中最刺激、最有壓力或者說最有挑戰的一個環節就是手撕算法了。面試中大部分算法題目都是來自於Leetcode、劍指offer上面,建議大家可以每天擠出一點時間刷一下算法題。
九 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 代理類——不過這個選擇過程對開發者完全透明、開發者也無需關心。
9.5 IOC
Spring IOC的初始化過程:
十 實際場景題
我覺得實際場景題就是對你的知識運用能力以及思維能力的考察。建議大家在平時養成多思考問題的習慣,這樣面試的時候碰到這樣的問題就不至於慌了。另外,如果自己實在不會就給面試官委婉的說一下,面試官可能會給你提醒一下。切忌不懂裝懂,亂答一氣。 面試官可能會問你類似這樣的問題:①假設你要做一個銀行app,有可能碰到多個人同時向一個賬戶打錢的情況,有可能碰到什麼問題,如何解決(鎖)②你是怎麼保證你的代碼質量和正確性的?③下單過程中是下訂單減庫存還是付款減庫存,分析一下兩者的優劣;④同時給10萬個人發工資,怎麼樣設計併發方案,能確保在1分鐘內全部發完。⑤如果讓你設計xxx系統的話,你會如何設計。
寫在最後
最後,再強調幾點:
- 一定要謹慎對待寫在簡歷上的東西,一定要對簡歷上的東西非常熟悉。因為一般情況下,面試官都是會根據你的簡歷來問的; 2. 能有一個上得了檯面的項目也非常重要,這很可能是面試官會大量發問的地方,所以在面試之前好好回顧一下自己所做的項目;
- 和麵試官聊基礎知識比如設計模式的使用、多線程的使用等等,可以結合具體的項目場景或者是自己在平時是如何使用的;
- 注意自己開源的Github項目,面試官可能會挖你的Github項目提問;
- 建議提前瞭解一下自己想要面試的公司的價值觀,判斷一下自己究竟是否適合這個公司。
另外,我個人覺得面試也像是一場全新的征程,失敗和勝利都是平常之事。所以,勸各位不要因為面試失敗而灰心、喪失鬥志。也不要因為面試通過而沾沾自喜,等待你的將是更美好的未來,繼續加油!
初次之外,筆主也在這裡給自己挖一個坑,關於 dubbo、zookeeper 等內容我會在後續做一個系統總結。保證大家看了之後,一定有收穫!
關注我:私信回覆“架構”獲取往期Java高級架構資料、源碼、筆記、視頻
Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術
閱讀更多 Java技術虎 的文章