一 簡歷該如何寫java
1.1 爲何說簡歷很重要?
1.2-這3點你必須知道
1.3-兩大法則瞭解一
1.4-項目經歷怎麼寫?
1.5-專業技能該怎麼寫?
1.6-開源程序員簡歷模板分享
1.7 其餘的一些小tipslinux
二 計算機網絡常見面試點總結程序員
計算機網絡常見問題回顧
2.1 TCP、UDP 協議的區別
2.2 在瀏覽器中輸入url地址 ->> 顯示主頁的過程
2.3 各類協議與HTTP協議之間的關係
2.4 HTTP長鏈接、短鏈接
2.5 TCP 三次握手和四次揮手面試
三 Linuxredis
3.1-簡單介紹一下-linux-文件系統?
3.2 一些常見的 Linux 命令瞭解嗎?算法
四 MySQLspring
4.1 說說本身對於 MySQL 常見的兩種存儲引擎:MyISAM與InnoDB的理解
4.2 數據庫索引瞭解嗎?
4.3 對於大表的常見優化手段說一下sql
五 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 如何保證緩存與數據庫雙寫時的數據一致性?vim
六 Java
6.1 Java 基礎知識
6.2 Java 集合框架
6.3 Java多線程
6.4 Java虛擬機
6.5 設計模式
七 數據結構
八 算法
九 Spring
9.1 Spring Bean 的做用域
9.2 Spring 事務中的隔離級別
9.3 Spring 事務中的事務傳播行爲
9.4 AOP
9.5 IOC
十 實際場景題
寫在最後
前言
不管是校招仍是社招都避免不了各類面試、筆試,如何去準備這些東西就顯得格外重要。不管是筆試仍是面試都是有章可循的,我這個「有章可循」說的意思只是說應對技術面試是能夠提早準備。 我其實特別不喜歡那種臨近考試就提早背啊記啊各類題的行爲,很是反對!我以爲這種方法特別極端,並且在稍有一點經驗的面試官面前是根本沒有用的。建議你們仍是一步一個腳印踏踏實實地走。
指揮若定以後,決勝千里以外!不打毫無準備的仗,我以爲你們能夠先從下面幾個方面來準備面試:
自我介紹。(你可千萬這樣介紹:「我叫某某,性別,來自哪裏,學校是那個,本身愛幹什麼」,記住:多說點簡歷上沒有的,多說點本身哪裏比別人強!)
本身面試中可能涉及哪些知識點、那些知識點是重點。
面試中哪些問題會被常常問到、面試中本身改如何回答。(強烈不推薦背題,第一:經過背這種方式你能記住多少?能記住多久?第二:背題的方式的學習很難堅持下去!)
本身的簡歷該如何寫。
「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.7 其餘的一些小tips
儘可能避免主觀表述,少一點語義模糊的形容詞,儘可能要簡潔明瞭,邏輯結構清晰。
注意排版(不須要花花綠綠的),儘可能使用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協議,會在響應頭加入這行代碼:
Connection:keep-alive
複製代碼在使用長鏈接的狀況下,當一個網頁打開完成後,客戶端和服務器之間用於傳輸HTTP數據的TCP鏈接不會關閉,客戶端再次訪問這個服務器時,會繼續使用這一條已經創建的鏈接。Keep-Alive不會永久保持鏈接,它有一個保持時間,能夠在不一樣的服務器軟件(如Apache)中設定這個時間。實現長鏈接須要客戶端和服務端都支持長鏈接。
HTTP協議的長鏈接和短鏈接,實質上是TCP協議的長鏈接和短鏈接。
—— 《HTTP長鏈接、短鏈接到底是什麼?》
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個問題!後兩個問題,暫未更新!若有須要,能夠關注個人 Github 或者微信公衆號:「Java面試通關手冊」獲取後續更新內容。
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 scripting), LRU驅動事件(LRU eviction),事務(transactions) 和不一樣級別的 磁盤持久化(persistence), 並經過 Redis哨兵(Sentinel)和自動 分區(Cluster)提供高可用性(high availability)。
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 常見數據結構以及使用場景分析
經常使用命令: 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 中有相關注釋,我這裏就不貼了,你們能夠自行查閱或者經過這個網址查看: 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配置文件中默認有此下配置:
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文件重寫操做
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
abstract class AbstractStringBuilder implements Appendable, CharSequence {
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% 左右的性能提高,但卻要冒多線程不安全的風險。
對於三者使用的總結:
操做少許的數據 = String
單線程操做字符串緩衝區下操做大量數據 = StringBuilder
多線程操做字符串緩衝區下操做大量數據 = 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關鍵字主要用在三個地方:變量、方法、類。
對於一個final變量,若是是基本數據類型的變量,則其數值一旦在初始化以後便不能更改;若是是引用類型的變量,則在對其初始化以後便不能再讓其指向另外一個對象。
當用final修飾一個類時,代表這個類不能被繼承。final類中的全部成員方法都會被隱式地指定爲final方法。
使用final方法的緣由有兩個。第一個緣由是把方法鎖定,以防任何繼承類修改它的含義;第二個緣由是效率。在早期的Java實現版本中,會將final方法轉爲內嵌調用。可是若是方法過於龐大,可能看不到內嵌調用帶來的任何性能提高(如今的Java版本已經不須要使用final方法進行這些優化了)。類中全部的private方法都隱式地指定爲fianl。
6.2 Java 集合框架
Arraylist 與 LinkedList 異同
補充:數據結構基礎之雙向鏈表
雙向鏈表也叫雙鏈表,是鏈表的一種,它的每一個數據結點中都有兩個指針,分別指向直接後繼和直接前驅。因此,從雙向鏈表中的任意一個結點開始,均可以很方便地訪問它的前驅結點和後繼結點。通常咱們都構造雙向循環鏈表,以下圖所示,同時下圖也是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 方法更加簡化,可是原理不變。
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以後在解決哈希衝突時有了較大的變化,當鏈表長度大於閾值(默認爲8)時,將鏈表轉化爲紅黑樹,以減小搜索時間。
TreeMap、TreeSet以及JDK1.8以後的HashMap底層都用到了紅黑樹。紅黑樹就是爲了解決二叉查找樹的缺陷,由於二叉查找樹在某些狀況下會退化成一個線性結構。
HashMap 和 Hashtable 的區別
線程是否安全: HashMap 是非線程安全的,HashTable 是線程安全的;HashTable 內部的方法基本都通過 synchronized 修飾。(若是你要保證線程安全的話就使用 ConcurrentHashMap 吧!);
效率: 由於線程安全的問題,HashMap 要比 HashTable 效率高一點。另外,HashTable 基本被淘汰,不要在代碼中使用它;
對Null key 和Null value的支持: HashMap 中,null 能夠做爲鍵,這樣的鍵只有一個,能夠有一個或多個鍵所對應的值爲 null。。可是在 HashTable 中 put 進的鍵值只要有一個 null,直接拋出 NullPointerException。
初始容量大小和每次擴充容量大小的不一樣 : ①建立時若是不指定容量初始值,Hashtable 默認的初始大小爲11,以後每次擴充,容量變爲原來的2n+1。HashMap 默認的初始化大小爲16。以後每次擴充,容量變爲原來的2倍。②建立時若是給定了容量初始值,那麼 Hashtable 會直接使用你給定的大小,而 HashMap 會將其擴充爲2的冪次方大小。也就是說 HashMap 老是使用2的冪做爲哈希表的大小,後面會介紹到爲何是2的冪次方。
底層數據結構: 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 中的方法。)
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,競爭會愈來愈激烈效率越低。
二者的對比圖:
圖片來源:www.cnblogs.com/chengxiao/p…
HashTable:
JDK1.7的ConcurrentHashMap:
JDK1.8的ConcurrentHashMap(TreeBin: 紅黑二叉樹節點
Node: 鏈表節點):
ConcurrentHashMap線程安全的具體實現方式/底層具體實現
①JDK1.7(上面有示意圖)
首先將數據分爲一段一段的存儲,而後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據時,其餘段的數據也能被其餘線程訪問。
ConcurrentHashMap 是由 Segment 數組結構和 HahEntry 數組結構組成。
Segment 實現了 ReentrantLock,因此 Segment 是一種可重入鎖,扮演鎖的角色。HashEntry 用於存儲鍵值對數據。
static class Segment<K,V> extends 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調優這些問題了。
6.5 設計模式
設計模式比較常見的就是讓你手寫一個單例模式(注意單例模式的幾種不一樣的實現方法)或者讓你說一下某個常見的設計模式在你的項目中是如何使用的,另外面試官還有可能問你抽象工廠和工廠方法模式的區別、工廠模式的思想這樣的問題。
建議把代理模式、觀察者模式、(抽象)工廠模式好好看一下,這三個設計模式也很重要。
七 數據結構
數據結構比較常問的就是:二叉樹、紅黑樹(極可能讓你手繪一個紅黑樹出來哦!)、二叉查找樹(BST)、平衡二叉樹(Self-balancing binary search tree)、B-樹,B+樹與B*樹的優缺點比較、 LSM 樹這些知識點。
數據結構很重要,並且學起來也相對要難一些。建議學習數據結構必定要按部就班的來,一步一個腳印的走好。必定要搞懂原理,最好本身能用代碼實現一遍。
八 算法
常見的加密算法、排序算法都須要本身提早了解一下,排序算法最好本身可以獨立手寫出來。
我以爲面試中最刺激、最有壓力或者說最有挑戰的一個環節就是手撕算法了。面試中大部分算法題目都是來自於Leetcode、劍指offer上面,建議你們能夠天天擠出一點時間刷一下算法題。
推薦兩個刷題必備網站:
LeetCode:
LeetCode(中國)官網
如何高效地使用 LeetCode
牛客網:
牛客網首頁
九 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的初始化過程:
IOC源碼閱讀
javadoop.com/post/spring…
十 實際場景題
我以爲實際場景題就是對你的知識運用能力以及思惟能力的考察。建議你們在平時養成多思考問題的習慣,這樣面試的時候碰到這樣的問題就不至於慌了。另外,若是本身實在不會就給面試官委婉的說一下,面試官可能會給你提醒一下。切忌不懂裝懂,亂答一氣。
面試官可能會問你相似這樣的問題:①假設你要作一個銀行app,有可能碰到多我的同時向一個帳戶打錢的狀況,有可能碰到什麼問題,如何解決(鎖)②你是怎麼保證你的代碼質量和正確性的?③下單過程當中是下訂單減庫存仍是付款減庫存,分析一下二者的優劣;④同時給10萬我的發工資,怎麼樣設計併發方案,能確保在1分鐘內所有發完。⑤若是讓你設計xxx系統的話,你會如何設計。
寫在最後
最後,再強調幾點:
必定要謹慎對待寫在簡歷上的東西,必定要對簡歷上的東西很是熟悉。由於通常狀況下,面試官都是會根據你的簡從來問的;
能有一個上得了檯面的項目也很是重要,這極可能是面試官會大量發問的地方,因此在面試以前好好回顧一下本身所作的項目;
和麪試官聊基礎知識好比設計模式的使用、多線程的使用等等,能夠結合具體的項目場景或者是本身在平時是如何使用的;
注意本身開源的Github項目,面試官可能會挖你的Github項目提問;
建議提早了解一下本身想要面試的公司的價值觀,判斷一下本身到底是否適合這個公司。
另外,我我的以爲面試也像是一場全新的征程,失敗和勝利都是日常之事。因此,勸各位不要由於面試失敗而灰心、喪失鬥志。也不要由於面試經過而沾沾自喜,等待你的將是更美好的將來,繼續加油!