摘要:注意排版不需要花花綠綠的,盡量使用語法。協(xié)議的長連接和短連接,實質(zhì)上是協(xié)議的長連接和短連接。長連接短連接究竟是什么三次握手和四次揮手面試??蜑榱藴蚀_無誤地把數(shù)據(jù)送達目標處,協(xié)議采用了三次握手策略。
一 簡歷該如何寫
1.1 為什么說簡歷很重要?
1.2-這3點你必須知道
1.3-兩大法則了解一
1.4-項目經(jīng)歷怎么寫?
1.5-專業(yè)技能該怎么寫?
1.6-開源程序員簡歷模板分享
1.7 其他的一些小tips
二 計算機網(wǎng)絡常見面試點總結(jié)
計算機網(wǎng)絡常見問題回顧
2.1 TCP、UDP 協(xié)議的區(qū)別
2.2 在瀏覽器中輸入url地址 ->> 顯示主頁的過程
2.3 各種協(xié)議與HTTP協(xié)議之間的關系
2.4 HTTP長連接、短連接
2.5 TCP 三次握手和四次揮手
三 Linux
3.1-簡單介紹一下-linux-文件系統(tǒng)?
3.2 一些常見的 Linux 命令了解嗎?
四 MySQL
4.1 說說自己對于 MySQL 常見的兩種存儲引擎:MyISAM與InnoDB的理解
4.2 數(shù)據(jù)庫索引了解嗎?
4.3 對于大表的常見優(yōu)化手段說一下
五 Redis
5.1 redis 簡介
5.2 為什么要用 redis /為什么要用緩存
5.3 為什么要用 redis 而不用 map/guava 做緩存?
5.4 redis 和 memcached 的區(qū)別
5.5 redis 常見數(shù)據(jù)結(jié)構(gòu)以及使用場景分析
5.6 redis 設置過期時間
5.7 redis 內(nèi)存淘汰機制
5.8 redis 持久化機制(怎么保證 redis 掛掉之后再重啟數(shù)據(jù)可以進行恢復)
5.9 緩存雪崩和緩存穿透問題解決方案
5.10 如何解決 Redis 的并發(fā)競爭 Key 問題
5.11 如何保證緩存與數(shù)據(jù)庫雙寫時的數(shù)據(jù)一致性?
六 Java
6.1 Java 基礎知識
6.2 Java 集合框架
6.3 Java多線程
6.4 Java虛擬機
6.5 設計模式
七 數(shù)據(jù)結(jié)構(gòu)
八 算法
九 Spring
9.1 Spring Bean 的作用域
9.2 Spring 事務中的隔離級別
9.3 Spring 事務中的事務傳播行為
9.4 AOP
9.5 IOC
十 實際場景題
寫在最后
前言
不論是校招還是社招都避免不了各種面試、筆試,如何去準備這些東西就顯得格外重要。不論是筆試還是面試都是有章可循的,我這個“有章可循”說的意思只是說應對技術(shù)面試是可以提前準備。 我其實特別不喜歡那種臨近考試就提前背啊記啊各種題的行為,非常反對!我覺得這種方法特別極端,而且在稍有一點經(jīng)驗的面試官面前是根本沒有用的。建議大家還是一步一個腳印踏踏實實地走。
運籌帷幄之后,決勝千里之外!不打毫無準備的仗,我覺得大家可以先從下面幾個方面來準備面試:
自我介紹。(你可千萬這樣介紹:“我叫某某,性別,來自哪里,學校是那個,自己愛干什么”,記?。憾嗾f點簡歷上沒有的,多說點自己哪里比別人強?。?br>自己面試中可能涉及哪些知識點、那些知識點是重點。
面試中哪些問題會被經(jīng)常問到、面試中自己改如何回答。(強烈不推薦背題,第一:通過背這種方式你能記住多少?能記住多久?第二:背題的方式的學習很難堅持下去!)
自己的簡歷該如何寫。
“80%的offer掌握在20%的人手中” 這句話也不是不無道理的。決定你面試能否成功的因素中實力固然占有很大一部分比例,但是如果你的心態(tài)或者說運氣不好的話,依然無法拿到滿意的 offer。運氣暫且不談,就拿心態(tài)來說,千萬不要因為面試失敗而氣餒或者說懷疑自己的能力,面試失敗之后多總結(jié)一下失敗的原因,后面你就會發(fā)現(xiàn)自己會越來越強大。
另外,大家要明確的很重要的幾點是:
寫在簡歷上的東西一定要慎重,這可能是面試官大量提問的地方;
大部分應屆生找工作的硬傷是沒有工作經(jīng)驗或?qū)嵙暯?jīng)歷;
將自己的項目經(jīng)歷完美的展示出來非常重要。
一 簡歷該如何寫
俗話說的好:“工欲善其事,必先利其器”。準備一份好的簡歷對于能不能找到一份好工作起到了至關重要的作用。
1.1 為什么說簡歷很重要?
假如你是網(wǎng)申,你的簡歷必然會經(jīng)過HR的篩選,一張簡歷HR可能也就花費10秒鐘看一下,然后HR就會決定你這一關是Fail還是Pass。
假如你是內(nèi)推,如果你的簡歷沒有什么優(yōu)勢的話,就算是內(nèi)推你的人再用心,也無能為力。
另外,就算你通過了篩選,后面的面試中,面試官也會根據(jù)你的簡歷來判斷你究竟是否值得他花費很多時間去面試。
1.2 這3點你必須知道
大部分應屆生找工作的硬傷是沒有工作經(jīng)驗或?qū)嵙暯?jīng)歷;
寫在簡歷上的東西一定要慎重,這可能是面試官大量提問的地方;
將自己的項目經(jīng)歷完美的展示出來非常重要。
1.3 兩大法則了解一下
目前寫簡歷的方式有兩種普遍被認可,一種是 STAR, 一種是 FAB。
STAR法則(Situation Task Action Result):
Situation: 事情是在什么情況下發(fā)生;
Task:: 你是如何明確你的任務的;
Action: 針對這樣的情況分析,你采用了什么行動方式;
Result: 結(jié)果怎樣,在這樣的情況下你學習到了什么。
FAB 法則(Feature Advantage Benefit):
Feature: 是什么;
Advantage: 比別人好在哪些地方;
Benefit: 如果雇傭你,招聘方會得到什么好處。
1.4 項目經(jīng)歷怎么寫?
簡歷上有一兩個項目經(jīng)歷很正常,但是真正能把項目經(jīng)歷很好的展示給面試官的非常少。對于項目經(jīng)歷大家可以考慮從如下幾點來寫:
對項目整體設計的一個感受
在這個項目中你負責了什么、做了什么、擔任了什么角色
從這個項目中你學會了那些東西,使用到了那些技術(shù),學會了那些新技術(shù)的使用
另外項目描述中,最好可以體現(xiàn)自己的綜合素質(zhì),比如你是如何協(xié)調(diào)項目組成員協(xié)同開發(fā)的或者在遇到某一個棘手的問題的時候你是如何解決的。
1.5 專業(yè)技能該怎么寫?
先問一下你自己會什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技術(shù),所以他在篩選簡歷的時候可能就盯著你專業(yè)技能的關鍵詞來看。對于公司有要求而你不會的技能,你可以花幾天時間學習一下,然后在簡歷上可以寫上自己了解這個技能。比如你可以這樣寫:
Dubbo:精通
Spring:精通
Docker:掌握
SOA分布式開發(fā) :掌握
Spring Cloud:了解
1.7 其他的一些小tips
盡量避免主觀表述,少一點語義模糊的形容詞,盡量要簡潔明了,邏輯結(jié)構(gòu)清晰。
注意排版(不需要花花綠綠的),盡量使用Markdown語法。
如果自己有博客或者個人技術(shù)棧點的話,寫上去會為你加分很多。
如果自己的Github比較活躍的話,寫上去也會為你加分很多。
注意簡歷真實性,一定不要寫自己不會的東西,或者帶有欺騙性的內(nèi)容
項目經(jīng)歷建議以時間倒序排序,另外項目經(jīng)歷不在于多,而在于有亮點。
如果內(nèi)容過多的話,不需要非把內(nèi)容壓縮到一頁,保持排版干凈整潔就可以了。
簡歷最后最好能加上:“感謝您花時間閱讀我的簡歷,期待能有機會和您共事?!边@句話,顯的你會很有禮貌。
二 計算機網(wǎng)絡常見面試點總結(jié)
計算機網(wǎng)絡常見問題回顧
TCP三次握手和四次揮手、
在瀏覽器中輸入url地址->>顯示主頁的過程
TCP 協(xié)議如何保證可靠傳輸
HTTP和HTTPS的區(qū)別
TCP、UDP協(xié)議的區(qū)別
常見的狀態(tài)碼。
下面列舉幾個常見問題的回答!
2.1 TCP、UDP 協(xié)議的區(qū)別
UDP 在傳送數(shù)據(jù)之前不需要先建立連接,遠地主機在收到 UDP 報文后,不需要給出任何確認。雖然 UDP 不提供可靠交付,但在某些情況下 UDP 確是一種最有效的工作方式(一般用于即時通信),比如: QQ 語音、 QQ 視頻 、直播等等
TCP 提供面向連接的服務。在傳送數(shù)據(jù)之前必須先建立連接,數(shù)據(jù)傳送結(jié)束后要釋放連接。 TCP 不提供廣播或多播服務。由于 TCP 要提供可靠的,面向連接的運輸服務(TCP的可靠體現(xiàn)在TCP在傳遞數(shù)據(jù)之前,會有三次握手來建立連接,而且在數(shù)據(jù)傳遞時,有確認、窗口、重傳、擁塞控制機制,在數(shù)據(jù)傳完后,還會斷開連接用來節(jié)約系統(tǒng)資源),這一難以避免增加了許多開銷,如確認,流量控制,計時器以及連接管理等。這不僅使協(xié)議數(shù)據(jù)單元的首部增大很多,還要占用許多處理機資源。TCP 一般用于文件傳輸、發(fā)送和接收郵件、遠程登錄等場景。
2.2 在瀏覽器中輸入url地址 ->> 顯示主頁的過程
百度好像最喜歡問這個問題。
打開一個網(wǎng)頁,整個過程會使用哪些協(xié)議
總體來說分為以下幾個過程:
DNS解析
TCP連接
發(fā)送HTTP請求
服務器處理請求并返回HTTP報文
瀏覽器解析渲染頁面
連接結(jié)束
2.3 各種協(xié)議與HTTP協(xié)議之間的關系
一般面試官會通過這樣的問題來考察你對計算機網(wǎng)絡知識體系的理解。
2.4 HTTP長連接、短連接
在HTTP/1.0中默認使用短連接。也就是說,客戶端和服務器每進行一次HTTP操作,就建立一次連接,任務結(jié)束就中斷連接。當客戶端瀏覽器訪問的某個HTML或其他類型的Web頁中包含有其他的Web資源(如JavaScript文件、圖像文件、CSS文件等),每遇到這樣一個Web資源,瀏覽器就會重新建立一個HTTP會話。
而從HTTP/1.1起,默認使用長連接,用以保持連接特性。使用長連接的HTTP協(xié)議,會在響應頭加入這行代碼:
Connection:keep-alive
復制代碼在使用長連接的情況下,當一個網(wǎng)頁打開完成后,客戶端和服務器之間用于傳輸HTTP數(shù)據(jù)的TCP連接不會關閉,客戶端再次訪問這個服務器時,會繼續(xù)使用這一條已經(jīng)建立的連接。Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。實現(xiàn)長連接需要客戶端和服務端都支持長連接。
HTTP協(xié)議的長連接和短連接,實質(zhì)上是TCP協(xié)議的長連接和短連接。
—— 《HTTP長連接、短連接究竟是什么?》
2.5 TCP 三次握手和四次揮手(面試常客)
為了準確無誤地把數(shù)據(jù)送達目標處,TCP協(xié)議采用了三次握手策略。
客戶端–發(fā)送帶有 SYN 標志的數(shù)據(jù)包–一次握手–服務端
服務端–發(fā)送帶有 SYN/ACK 標志的數(shù)據(jù)包–二次握手–客戶端
客戶端–發(fā)送帶有帶有 ACK 標志的數(shù)據(jù)包–三次握手–服務端
為什么要三次握手?
三次握手的目的是建立可靠的通信信道,說到通訊,簡單來說就是數(shù)據(jù)的發(fā)送與接收,而三次握手最主要的目的就是雙方確認自己與對方的發(fā)送與接收是正常的。
第一次握手:Client 什么都不能確認;Server 確認了對方發(fā)送正常
第二次握手:Client 確認了:自己發(fā)送、接收正常,對方發(fā)送、接收正常;Server 確認了:自己接收正常,對方發(fā)送正常
第三次握手:Client 確認了:自己發(fā)送、接收正常,對方發(fā)送、接收正常;Server 確認了:自己發(fā)送、接收正常,對方發(fā)送接收正常
所以三次握手就能確認雙發(fā)收發(fā)功能都正常,缺一不可。
為什么要傳回 SYN
接收端傳回發(fā)送端所發(fā)送的 SYN 是為了告訴發(fā)送端,我接收到的信息確實就是你所發(fā)送的信號了。
SYN 是 TCP/IP 建立連接時使用的握手信號。在客戶機和服務器之間建立正常的 TCP 網(wǎng)絡連接時,客戶機首先發(fā)出一個 SYN 消息,服務器使用 SYN-ACK 應答表示接收到了這個消息,最后客戶機再以 ACK(Acknowledgement[漢譯:確認字符 ,在數(shù)據(jù)通信傳輸中,接收站發(fā)給發(fā)送站的一種傳輸控制字符。它表示確認發(fā)來的數(shù)據(jù)已經(jīng)接受無誤。 ])消息響應。這樣在客戶機和服務器之間才能建立起可靠的TCP連接,數(shù)據(jù)才可以在客戶機和服務器之間傳遞。
傳了 SYN,為啥還要傳 ACK
雙方通信無誤必須是兩者互相發(fā)送信息都無誤。傳了 SYN,證明發(fā)送方到接收方的通道沒有問題,但是接收方到發(fā)送方的通道還需要 ACK 信號來進行驗證。
斷開一個 TCP 連接則需要“四次揮手”:
客戶端-發(fā)送一個 FIN,用來關閉客戶端到服務器的數(shù)據(jù)傳送
服務器-收到這個 FIN,它發(fā)回一 個 ACK,確認序號為收到的序號加1 。和 SYN 一樣,一個 FIN 將占用一個序號
服務器-關閉與客戶端的連接,發(fā)送一個FIN給客戶端
客戶端-發(fā)回 ACK 報文確認,并將確認序號設置為收到序號加1
為什么要四次揮手
任何一方都可以在數(shù)據(jù)傳送結(jié)束后發(fā)出連接釋放的通知,待對方確認后進入半關閉狀態(tài)。當另一方也沒有數(shù)據(jù)再發(fā)送的時候,則發(fā)出連接釋放通知,對方確認后就完全關閉了TCP連接。
舉個例子:A 和 B 打電話,通話即將結(jié)束后,A 說“我沒啥要說的了”,B回答“我知道了”,但是 B 可能還會有要說的話,A 不能要求 B 跟著自己的節(jié)奏結(jié)束通話,于是 B 可能又巴拉巴拉說了一通,最后 B 說“我說完了”,A 回答“知道了”,這樣通話才算結(jié)束。
三 Linux
3.1 簡單介紹一下 Linux 文件系統(tǒng)?
Linux文件系統(tǒng)簡介
在Linux操作系統(tǒng)中,所有被操作系統(tǒng)管理的資源,例如網(wǎng)絡接口卡、磁盤驅(qū)動器、打印機、輸入輸出設備、普通文件或是目錄都被看作是一個文件。
也就是說在LINUX系統(tǒng)中有一個重要的概念:一切都是文件。其實這是UNIX哲學的一個體現(xiàn),而Linux是重寫UNIX而來,所以這個概念也就傳承了下來。在UNIX系統(tǒng)中,把一切資源都看作是文件,包括硬件設備。UNIX系統(tǒng)把每個硬件都看成是一個文件,通常稱為設備文件,這樣用戶就可以用讀寫文件的方式實現(xiàn)對硬件的訪問。
文件類型與目錄結(jié)構(gòu)
Linux支持5種文件類型 :
Linux的目錄結(jié)構(gòu)如下:
Linux文件系統(tǒng)的結(jié)構(gòu)層次鮮明,就像一棵倒立的樹,最頂層是其根目錄:
常見目錄說明:
/bin: 存放二進制可執(zhí)行文件(ls,cat,mkdir等),常用命令一般都在這里;
/etc: 存放系統(tǒng)管理和配置文件;
/home: 存放所有用戶文件的根目錄,是用戶主目錄的基點,比如用戶user的主目錄就是/home/user,可以用~user表示;
/usr : 用于存放系統(tǒng)應用程序;
/opt: 額外安裝的可選應用程序包所放置的位置。一般情況下,我們可以把tomcat等都安裝到這里;
/proc: 虛擬文件系統(tǒng)目錄,是系統(tǒng)內(nèi)存的映射??芍苯釉L問這個目錄來獲取系統(tǒng)信息;
/root: 超級用戶(系統(tǒng)管理員)的主目錄(特權(quán)階級^o^);
/sbin: 存放二進制可執(zhí)行文件,只有root才能訪問。這里存放的是系統(tǒng)管理員使用的系統(tǒng)級別的管理命令和程序。如ifconfig等;
/dev: 用于存放設備文件;
/mnt: 系統(tǒng)管理員安裝臨時文件系統(tǒng)的安裝點,系統(tǒng)提供這個目錄是讓用戶臨時掛載其他的文件系統(tǒng);
/boot: 存放用于系統(tǒng)引導時使用的各種文件;
/lib : 存放著和系統(tǒng)運行相關的庫文件 ;
/tmp: 用于存放各種臨時文件,是公用的臨時文件存儲點;
/var: 用于存放運行時需要改變數(shù)據(jù)的文件,也是某些大文件的溢出區(qū),比方說各種服務的日志文件(系統(tǒng)啟動日志等。)等;
/lost+found: 這個目錄平時是空的,系統(tǒng)非正常關機而留下“無家可歸”的文件(windows下叫什么.chk)就在這里。
3.2 一些常見的 Linux 命令了解嗎?
目錄切換命令
cd usr: 切換到該目錄下usr目錄
cd ..(或cd../): 切換到上一層目錄
cd /: 切換到系統(tǒng)根目錄
cd ~: 切換到用戶主目錄
cd -: 切換到上一個所在目錄
目錄的操作命令(增刪改查)
mkdir 目錄名稱: 增加目錄
ls或者ll(ll是ls -l的縮寫,ll命令以看到該目錄下的所有目錄和文件的詳細信息):查看目錄信息
find 目錄 參數(shù): 尋找目錄(查)
mv 目錄名稱 新目錄名稱: 修改目錄的名稱(改)
注意:mv的語法不僅可以對目錄進行重命名而且也可以對各種文件,壓縮包等進行 重命名的操作。mv命令用來對文件或目錄重新命名,或者將文件從一個目錄移到另一個目錄中。后面會介紹到mv命令的另一個用法。
mv 目錄名稱 目錄的新位置: 移動目錄的位置---剪切(改)
注意:mv語法不僅可以對目錄進行剪切操作,對文件和壓縮包等都可執(zhí)行剪切操作。另外mv與cp的結(jié)果不同,mv好像文件“搬家”,文件個數(shù)并未增加。而cp對文件進行復制,文件個數(shù)增加了。
cp -r 目錄名稱 目錄拷貝的目標位置: 拷貝目錄(改),-r代表遞歸拷貝
注意:cp命令不僅可以拷貝目錄還可以拷貝文件,壓縮包等,拷貝文件和壓縮包時不 用寫-r遞歸
rm [-rf] 目錄: 刪除目錄(刪)
注意:rm不僅可以刪除目錄,也可以刪除其他文件或壓縮包,為了增強大家的記憶, 無論刪除任何目錄或文件,都直接使用rm -rf 目錄/文件/壓縮包
文件的操作命令(增刪改查)
touch 文件名稱: 文件的創(chuàng)建(增)
cat/more/less/tail 文件名稱 文件的查看(查)
cat: 只能顯示最后一屏內(nèi)容
more: 可以顯示百分比,回車可以向下一行, 空格可以向下一頁,q可以退出查看
less: 可以使用鍵盤上的PgUp和PgDn向上 和向下翻頁,q結(jié)束查看
tail-10 : 查看文件的后10行,Ctrl+C結(jié)束
注意:命令 tail -f 文件 可以對某個文件進行動態(tài)監(jiān)控,例如tomcat的日志文件, 會隨著程序的運行,日志會變化,可以使用tail -f catalina-2016-11-11.log 監(jiān)控 文 件的變化
vim 文件: 修改文件的內(nèi)容(改)
vim編輯器是Linux中的強大組件,是vi編輯器的加強版,vim編輯器的命令和快捷方式有很多,但此處不一一闡述,大家也無需研究的很透徹,使用vim編輯修改文件的方式基本會使用就可以了。
在實際開發(fā)中,使用vim編輯器主要作用就是修改配置文件,下面是一般步驟:
vim 文件------>進入文件----->命令模式------>按i進入編輯模式----->編輯文件 ------->按Esc進入底行模式----->輸入:wq/q! (輸入wq代表寫入內(nèi)容并退出,即保存;輸入q!代表強制退出不保存。)
rm -rf 文件: 刪除文件(刪)
同目錄刪除:熟記 rm -rf 文件 即可
壓縮文件的操作命令
1)打包并壓縮文件:
Linux中的打包文件一般是以.tar結(jié)尾的,壓縮的命令一般是以.gz結(jié)尾的。
而一般情況下打包和壓縮是一起進行的,打包并壓縮后的文件的后綴名一般.tar.gz。
命令:tar -zcvf 打包壓縮后的文件名 要打包壓縮的文件
其中:
z:調(diào)用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: 這兩個命令都是查看當前系統(tǒng)正在運行進程,兩者的區(qū)別是展示格式不同。如果想要查看特定的進程可以使用這樣的格式:ps aux|grep redis (查看包括redis字符串的進程)
注意:如果直接用ps((Process Status))命令,會顯示所有進程的狀態(tài),通常結(jié)合grep命令查看某進程的狀態(tài)。
kill -9 進程的pid: 殺死進程(-9 表示強制終止。)
先用ps查找進程,然后用kill殺掉
網(wǎng)絡通信命令:
查看當前系統(tǒng)的網(wǎng)卡信息:ifconfig
查看與某臺機器的連接情況:ping
查看當前系統(tǒng)的端口使用:netstat -an
shutdown: shutdown -h now: 指定現(xiàn)在立即關機;shutdown +5 "System will shutdown after 5 minutes":指定5分鐘后關機,同時送出警告信息給登入用戶。
reboot: reboot: 重開機。reboot -w: 做個重開機的模擬(只有紀錄并不會真的重開機)。
四 MySQL
4.1 說說自己對于 MySQL 常見的兩種存儲引擎:MyISAM與InnoDB的理解
關于二者的對比與總結(jié):
count運算上的區(qū)別:因為MyISAM緩存有表meta-data(行數(shù)等),因此在做COUNT(*)時對于一個結(jié)構(gòu)很好的查詢是不需要消耗多少資源的。而對于InnoDB來說,則沒有這種緩存。
是否支持事務和崩潰后的安全恢復: MyISAM 強調(diào)的是性能,每次查詢具有原子性,其執(zhí)行數(shù)度比InnoDB類型更快,但是不提供事務支持。但是InnoDB 提供事務支持事務,外部鍵等高級數(shù)據(jù)庫功能。 具有事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。
是否支持外鍵: MyISAM不支持,而InnoDB支持。
MyISAM更適合讀密集的表,而InnoDB更適合寫密集的的表。 在數(shù)據(jù)庫做主從分離的情況下,經(jīng)常選擇MyISAM作為主庫的存儲引擎。
一般來說,如果需要事務支持,并且有較高的并發(fā)讀取頻率(MyISAM的表鎖的粒度太大,所以當該表寫并發(fā)量較高時,要等待的查詢就會很多了),InnoDB是不錯的選擇。如果你的數(shù)據(jù)量很大(MyISAM支持壓縮特性可以減少磁盤的空間占用),而且不需要支持事務時,MyISAM是最好的選擇。
4.2 數(shù)據(jù)庫索引了解嗎?
Mysql索引使用的數(shù)據(jù)結(jié)構(gòu)主要有BTree索引 和 哈希索引 。對于哈希索引來說,底層的數(shù)據(jù)結(jié)構(gòu)就是哈希表,因此在絕大多數(shù)需求為單條記錄查詢的時候,可以選擇哈希索引,查詢性能最快;其余大部分場景,建議選擇BTree索引。
Mysql的BTree索引使用的是B數(shù)中的B+Tree,但對于主要的兩種存儲引擎的實現(xiàn)方式是不同的。
MyISAM: B+Tree葉節(jié)點的data域存放的是數(shù)據(jù)記錄的地址。在索引檢索的時候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其 data 域的值,然后以 data 域的值為地址讀取相應的數(shù)據(jù)記錄。這被稱為“非聚簇索引”。
InnoDB: 其數(shù)據(jù)文件本身就是索引文件。相比MyISAM,索引文件和數(shù)據(jù)文件是分離的,其表數(shù)據(jù)文件本身就是按B+Tree組織的一個索引結(jié)構(gòu),樹的葉節(jié)點data域保存了完整的數(shù)據(jù)記錄。這個索引的key是數(shù)據(jù)表的主鍵,因此InnoDB表數(shù)據(jù)文件本身就是主索引。這被稱為“聚簇索引(或聚集索引)”。而其余的索引都作為輔助索引(非聚集索引),輔助索引的data域存儲相應記錄主鍵的值而不是地址,這也是和MyISAM不同的地方。在根據(jù)主索引搜索時,直接找到key所在的節(jié)點即可取出數(shù)據(jù);在根據(jù)輔助索引查找時,則需要先取出主鍵的值,在走一遍主索引。 因此,在設計表的時候,不建議使用過長的字段作為主鍵,也不建議使用非單調(diào)的字段作為主鍵,這樣會造成主索引頻繁分裂。 PS:整理自《Java工程師修煉之道》
4.3 對于大表的常見優(yōu)化手段說一下
當MySQL單表記錄數(shù)過大時,數(shù)據(jù)庫的CRUD性能會明顯下降,一些常見的優(yōu)化措施如下:
限定數(shù)據(jù)的范圍: 務必禁止不帶任何限制數(shù)據(jù)范圍條件的查詢語句。比如:我們當用戶在查詢訂單歷史的時候,我們可以控制在一個月的范圍內(nèi)。;
讀/寫分離: 經(jīng)典的數(shù)據(jù)庫拆分方案,主庫負責寫,從庫負責讀;
緩存: 使用MySQL的緩存,另外對重量級、更新少的數(shù)據(jù)可以考慮使用應用級別的緩存;
垂直分區(qū):
根據(jù)數(shù)據(jù)庫里面數(shù)據(jù)表的相關性進行拆分。 例如,用戶表中既有用戶的登錄信息又有用戶的基本信息,可以將用戶表拆分成兩個多帶帶的表,甚至放到多帶帶的庫做分庫。
簡單來說垂直拆分是指數(shù)據(jù)表列的拆分,把一張列比較多的表拆分為多張表。 如下圖所示,這樣來說大家應該就更容易理解了。
垂直拆分的優(yōu)點: 可以使得行數(shù)據(jù)變小,在查詢時減少讀取的Block數(shù),減少I/O次數(shù)。此外,垂直分區(qū)可以簡化表的結(jié)構(gòu),易于維護。
垂直拆分的缺點: 主鍵會出現(xiàn)冗余,需要管理冗余列,并會引起Join操作,可以通過在應用層進行Join來解決。此外,垂直分區(qū)會讓事務變得更加復雜;
水平分區(qū):
保持數(shù)據(jù)表結(jié)構(gòu)不變,通過某種策略存儲數(shù)據(jù)分片。這樣每一片數(shù)據(jù)分散到不同的表或者庫中,達到了分布式的目的。 水平拆分可以支撐非常大的數(shù)據(jù)量。
水平拆分是指數(shù)據(jù)表行的拆分,表的行數(shù)超過200萬行時,就會變慢,這時可以把一張的表的數(shù)據(jù)拆成多張表來存放。舉個例子:我們可以將用戶信息表拆分成多個用戶信息表,這樣就可以避免單一表數(shù)據(jù)量過大對性能造成影響。
水品拆分可以支持非常大的數(shù)據(jù)量。需要注意的一點是:分表僅僅是解決了單一表數(shù)據(jù)過大的問題,但由于表的數(shù)據(jù)還是在同一臺機器上,其實對于提升MySQL并發(fā)能力沒有什么意義,所以 水品拆分最好分庫 。
水平拆分能夠 支持非常大的數(shù)據(jù)量存儲,應用端改造也少,但 分片事務難以解決 ,跨界點Join性能較差,邏輯復雜?!禞ava工程師修煉之道》的作者推薦 盡量不要對數(shù)據(jù)進行分片,因為拆分會帶來邏輯、部署、運維的各種復雜度 ,一般的數(shù)據(jù)表在優(yōu)化得當?shù)那闆r下支撐千萬以下的數(shù)據(jù)量是沒有太大問題的。如果實在要分片,盡量選擇客戶端分片架構(gòu),這樣可以減少一次和中間件的網(wǎng)絡I/O。
下面補充一下數(shù)據(jù)庫分片的兩種常見方案:
客戶端代理: 分片邏輯在應用端,封裝在jar包中,通過修改或者封裝JDBC層來實現(xiàn)。 當當網(wǎng)的 Sharding-JDBC 、阿里的TDDL是兩種比較常用的實現(xiàn)。
中間件代理: 在應用和數(shù)據(jù)中間加了一個代理層。分片邏輯統(tǒng)一維護在中間件服務中。 我們現(xiàn)在談的 Mycat 、360的Atlas、網(wǎng)易的DDB等等都是這種架構(gòu)的實現(xiàn)。
五 Redis
關于 redis 必知必會的11個問題!后兩個問題,暫未更新!如有需要,可以關注我的 Github 或者微信公眾號:“Java面試通關手冊”獲取后續(xù)更新內(nèi)容。
redis 簡介
為什么要用 redis /為什么要用緩存
為什么要用 redis 而不用 map/guava 做緩存?
redis 和 memcached 的區(qū)別
redis 常見數(shù)據(jù)結(jié)構(gòu)以及使用場景分析
redis 設置過期時間
redis 內(nèi)存淘汰機制
redis 持久化機制(怎么保證 redis 掛掉之后再重啟數(shù)據(jù)可以進行恢復)
緩存雪崩和緩存穿透問題解決方案
如何解決 Redis 的并發(fā)競爭 Key 問題
如何保證緩存與數(shù)據(jù)庫雙寫時的數(shù)據(jù)一致性?
5.1 redis 簡介
Redis 是一個開源(BSD許可)的,內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲系統(tǒng),它可以用作數(shù)據(jù)庫、緩存和消息中間件。 它支持多種類型的數(shù)據(jù)結(jié)構(gòu),如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 與范圍查詢, bitmaps, hyperloglogs 和 地理空間(geospatial) 索引半徑查詢。 Redis 內(nèi)置了 復制(replication),LUA腳本(Lua scripting), LRU驅(qū)動事件(LRU eviction),事務(transactions) 和不同級別的 磁盤持久化(persistence), 并通過 Redis哨兵(Sentinel)和自動 分區(qū)(Cluster)提供高可用性(high availability)。
5.2 為什么要用 redis /為什么要用緩存
主要從“高性能”和“高并發(fā)”這兩點來看待這個問題。
高性能:
假如用戶第一次訪問數(shù)據(jù)庫中的某些數(shù)據(jù)。這個過程會比較慢,因為是從硬盤上讀取的。將該用戶訪問的數(shù)據(jù)存在數(shù)緩存中,這樣下一次再訪問這些數(shù)據(jù)的時候就可以直接從緩存中獲取了。操作緩存就是直接操作內(nèi)存,所以速度相當快。如果數(shù)據(jù)庫中的對應數(shù)據(jù)改變的之后,同步改變緩存中相應的數(shù)據(jù)即可!
高并發(fā):
直接操作緩存能夠承受的請求是遠遠大于直接訪問數(shù)據(jù)庫的,所以我們可以考慮把數(shù)據(jù)庫中的部分數(shù)據(jù)轉(zhuǎn)移到緩存中去,這樣用戶的一部分請求會直接到緩存這里而不用經(jīng)過數(shù)據(jù)庫。
5.3 為什么要用 redis 而不用 map/guava 做緩存?
緩存分為本地緩存和分布式緩存。以java為例,使用自帶的map或者guava實現(xiàn)的是本地緩存,最主要的特點是輕量以及快速,生命周期隨著 jvm 的銷毀而結(jié)束,并且在多實例的情況下,每個實例都需要各自保存一份緩存,緩存不具有一致性。
使用 redis 或 memcached 之類的稱為分布式緩存,在多實例的情況下,各實例共用一份緩存數(shù)據(jù),緩存具有一致性。缺點是需要保持 redis 或 memcached服務的高可用,整個程序架構(gòu)上較為復雜。
5.4 redis 和 memcached 的區(qū)別
對于 redis 和 memcached 我總結(jié)了下面四點。現(xiàn)在公司一般都是用 redis 來實現(xiàn)緩存,而且 redis 自身也越來越強大了!
redis支持更豐富的數(shù)據(jù)類型(支持更復雜的應用場景):Redis不僅僅支持簡單的k/v類型的數(shù)據(jù),同時還提供list,set,zset,hash等數(shù)據(jù)結(jié)構(gòu)的存儲。memcache支持簡單的數(shù)據(jù)類型,String。
Redis支持數(shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤中,重啟的時候可以再次加載進行使用,而Memecache把數(shù)據(jù)全部存在內(nèi)存之中。
集群模式:memcached沒有原生的集群模式,需要依靠客戶端來實現(xiàn)往集群中分片寫入數(shù)據(jù);但是redis目前是原生支持cluster模式的,redis官方就是支持redis cluster集群模式的,比memcached來說要更好。
Memcached是多線程,非阻塞IO復用的網(wǎng)絡模型;Redis使用單線程的多路 IO 復用模型。
來自網(wǎng)絡上的一張圖,這里分享給大家!
5.5 redis 常見數(shù)據(jù)結(jié)構(gòu)以及使用場景分析
String
常用命令: set,get,decr,incr,mget 等。
String數(shù)據(jù)結(jié)構(gòu)是簡單的key-value類型,value其實不僅可以是String,也可以是數(shù)字。
常規(guī)key-value緩存應用;
常規(guī)計數(shù):微博數(shù),粉絲數(shù)等。
2.Hash
常用命令: hget,hset,hgetall 等。
Hash 是一個 string 類型的 field 和 value 的映射表,hash 特別適合用于存儲對象,后續(xù)操作的時候,你可以直接僅僅修改這個對象中的某個字段的值。 比如我們可以Hash數(shù)據(jù)結(jié)構(gòu)來存儲用戶信息,商品信息等等。比如下面我就用 hash 類型存放了我本人的一些信息:
key=JavaUser293847
value={
“id”: 1,
“name”: “SnailClimb”,
“age”: 22,
“l(fā)ocation”: “Wuhan, Hubei”
}
復制代碼3.List
常用命令: lpush,rpush,lpop,rpop,lrange等
list就是鏈表,Redis list的應用場景非常多,也是Redis最重要的數(shù)據(jù)結(jié)構(gòu)之一,比如微博的關注列表,粉絲列表,消息列表等功能都可以用Redis的 list 結(jié)構(gòu)來實現(xiàn)。
Redis list 的實現(xiàn)為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷。
另外可以通過 lrange 命令,就是從某個元素開始讀取多少個元素,可以基于 list 實現(xiàn)分頁查詢,這個很棒的一個功能,基于 redis 實現(xiàn)簡單的高性能分頁,可以做類似微博那種下拉不斷分頁的東西(一頁一頁的往下走),性能高。
4.Set
常用命令:
sadd,spop,smembers,sunion 等
set對外提供的功能與list類似是一個列表的功能,特殊之處在于set是可以自動排重的。
當你需要存儲一個列表數(shù)據(jù),又不希望出現(xiàn)重復數(shù)據(jù)時,set是一個很好的選擇,并且set提供了判斷某個成員是否在一個set集合內(nèi)的重要接口,這個也是list所不能提供的。可以基于 set 輕易實現(xiàn)交集、并集、差集的操作。
比如:在微博應用中,可以將一個用戶所有的關注人存在一個集合中,將其所有粉絲存在一個集合。Redis可以非常方便的實現(xiàn)如共同關注、共同粉絲、共同喜好等功能。這個過程也就是求交集的過程,具體命令如下:
sinterstore key1 key2 key3 將交集存在key1內(nèi)
復制代碼5.Sorted Set
常用命令: zadd,zrange,zrem,zcard等
和set相比,sorted set增加了一個權(quán)重參數(shù)score,使得集合中的元素能夠按score進行有序排列。
舉例: 在直播系統(tǒng)中,實時排行信息包含直播間在線用戶列表,各種禮物排行榜,彈幕消息(可以理解為按消息維度的消息排行榜)等信息,適合使用 Redis 中的 SortedSet 結(jié)構(gòu)進行存儲。
5.6 redis 設置過期時間
Redis中有個設置時間過期的功能,即對存儲在 redis 數(shù)據(jù)庫中的值可以設置一個過期時間。作為一個緩存數(shù)據(jù)庫,這是非常實用的。如我們一般項目中的token或者一些登錄信息,尤其是短信驗證碼都是有時間限制的,按照傳統(tǒng)的數(shù)據(jù)庫處理方式,一般都是自己判斷過期,這樣無疑會嚴重影響項目性能。
我們set key的時候,都可以給一個expire time,就是過期時間,通過過期時間我們可以指定這個 key 可以存貨的時間。
如果假設你設置一個一批 key 只能存活1個小時,那么接下來1小時后,redis是怎么對這批key進行刪除的?
定期刪除+惰性刪除。
通過名字大概就能猜出這兩個刪除方式的意思了。
定期刪除:redis默認是每隔 100ms 就隨機抽取一些設置了過期時間的key,檢查其是否過期,如果過期就刪除。注意這里是隨機抽取的。為什么要隨機呢?你想一想假如 redis 存了幾十萬個 key ,每隔100ms就遍歷所有的設置過期時間的 key 的話,就會給 CPU 帶來很大的負載!
惰性刪除 :定期刪除可能會導致很多過期 key 到了時間并沒有被刪除掉。所以就有了惰性刪除。假如你的過期 key,靠定期刪除沒有被刪除掉,還停留在內(nèi)存里,除非你的系統(tǒng)去查一下那個 key,才會被redis給刪除掉。這就是所謂的惰性刪除,也是夠懶的哈!
但是僅僅通過設置過期時間還是有問題的。我們想一下:如果定期刪除漏掉了很多過期 key,然后你也沒及時去查,也就沒走惰性刪除,此時會怎么樣?如果大量過期key堆積在內(nèi)存里,導致redis內(nèi)存塊耗盡了。怎么解決這個問題呢?
redis 內(nèi)存淘汰機制。
5.7 redis 內(nèi)存淘汰機制(MySQL里有2000w數(shù)據(jù),Redis中只存20w的數(shù)據(jù),如何保證Redis中的數(shù)據(jù)都是熱點數(shù)據(jù)?)
redis 配置文件 redis.conf 中有相關注釋,我這里就不貼了,大家可以自行查閱或者通過這個網(wǎng)址查看: download.redis.io/redis-stabl…
redis 提供 6種數(shù)據(jù)淘汰策略:
volatile-lru:從已設置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
volatile-ttl:從已設置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰
volatile-random:從已設置過期時間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
allkeys-lru:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中,移除最近最少使用的key(這個是最常用的).
allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
no-enviction:禁止驅(qū)逐數(shù)據(jù),也就是說當內(nèi)存不足以容納新寫入數(shù)據(jù)時,新寫入操作會報錯。這個應該沒人使用吧!
備注: 關于 redis 設置過期時間以及內(nèi)存淘汰機制,我這里只是簡單的總結(jié)一下,后面會專門寫一篇文章來總結(jié)!
5.8 redis 持久化機制(怎么保證 redis 掛掉之后再重啟數(shù)據(jù)可以進行恢復)
很多時候我們需要持久化數(shù)據(jù)也就是將內(nèi)存中的數(shù)據(jù)寫入到硬盤里面,大部分原因是為了之后重用數(shù)據(jù)(比如重啟機器、機器故障之后回復數(shù)據(jù)),或者是為了防止系統(tǒng)故障而將數(shù)據(jù)備份到一個遠程位置。
Redis不同于Memcached的很重一點就是,Redis支持持久化,而且支持兩種不同的持久化操作。Redis的一種持久化方式叫快照(snapshotting,RDB),另一種方式是只追加文件(append-only file,AOF).這兩種方法各有千秋,下面我會詳細這兩種持久化方法是什么,怎么用,如何選擇適合自己的持久化方法。
快照(snapshotting)持久化(RDB)
Redis可以通過創(chuàng)建快照來獲得存儲在內(nèi)存里面的數(shù)據(jù)在某個時間點上的副本。Redis創(chuàng)建快照之后,可以對快照進行備份,可以將快照復制到其他服務器從而創(chuàng)建具有相同數(shù)據(jù)的服務器副本(Redis主從結(jié)構(gòu),主要用來提高Redis性能),還可以將快照留在原地以便重啟服務器的時候使用。
快照持久化是Redis默認采用的持久化方式,在redis.conf配置文件中默認有此下配置:
save 900 1 #在900秒(15分鐘)之后,如果至少有1個key發(fā)生變化,Redis就會自動觸發(fā)BGSAVE命令創(chuàng)建快照。
save 300 10 #在300秒(5分鐘)之后,如果至少有10個key發(fā)生變化,Redis就會自動觸發(fā)BGSAVE命令創(chuàng)建快照。
save 60 10000 #在60秒(1分鐘)之后,如果至少有10000個key發(fā)生變化,Redis就會自動觸發(fā)BGSAVE命令創(chuàng)建快照。
復制代碼AOF(append-only file)持久化
與快照持久化相比,AOF持久化 的實時性更好,因此已成為主流的持久化方案。默認情況下Redis沒有開啟AOF(append only file)方式的持久化,可以通過appendonly參數(shù)開啟:
appendonly yes
復制代碼開啟AOF持久化后每執(zhí)行一條會更改Redis中的數(shù)據(jù)的命令,Redis就會將該命令寫入硬盤中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數(shù)設置的,默認的文件名是appendonly.aof。
在Redis的配置文件中存在三種不同的 AOF 持久化方式,它們分別是:
appendfsync always #每次有數(shù)據(jù)修改發(fā)生時都會寫入AOF文件,這樣會嚴重降低Redis的速度
appendfsync everysec #每秒鐘同步一次,顯示地將多個寫命令同步到硬盤
appendfsync no #讓操作系統(tǒng)決定何時進行同步
復制代碼為了兼顧數(shù)據(jù)和寫入性能,用戶可以考慮 appendfsync everysec選項 ,讓Redis每秒同步一次AOF文件,Redis性能幾乎沒受到任何影響。而且這樣即使出現(xiàn)系統(tǒng)崩潰,用戶最多只會丟失一秒之內(nèi)產(chǎn)生的數(shù)據(jù)。當硬盤忙于執(zhí)行寫入操作的時候,Redis還會優(yōu)雅的放慢自己的速度以便適應硬盤的最大寫入速度。
補充內(nèi)容:AOF 重寫
AOF重寫可以產(chǎn)生一個新的AOF文件,這個新的AOF文件和原有的AOF文件所保存的數(shù)據(jù)庫狀態(tài)一樣,但體積更小。
AOF重寫是一個有歧義的名字,該功能是通過讀取數(shù)據(jù)庫中的鍵值對來實現(xiàn)的,程序無須對現(xiàn)有AOF文件進行任伺讀入、分析或者寫人操作。
在執(zhí)行 BGREWRITEAOF 命令時,Redis 服務器會維護一個 AOF 重寫緩沖區(qū),該緩沖區(qū)會在子進程創(chuàng)建新AOF文件期間,記錄服務器執(zhí)行的所有寫命令。當子進程完成創(chuàng)建新AOF文件的工作之后,服務器會將重寫緩沖區(qū)中的所有內(nèi)容追加到新AOF文件的末尾,使得新舊兩個AOF文件所保存的數(shù)據(jù)庫狀態(tài)一致。最后,服務器用新的AOF文件替換舊的AOF文件,以此來完成AOF文件重寫操作
5.9 緩存雪崩和緩存穿透問題解決方案
緩存雪崩
簡介:緩存同一時間大面積的失效,所以,后面的請求都會落到數(shù)據(jù)庫上,造成數(shù)據(jù)庫短時間內(nèi)承受大量請求而崩掉。
解決辦法(中華石杉老師在他的視頻中提到過):
事前:盡量保證整個 redis 集群的高可用性,發(fā)現(xiàn)機器宕機盡快補上。選擇合適的內(nèi)存淘汰策略。
事中:本地ehcache緩存 + hystrix限流&降級,避免MySQL崩掉
事后:利用 redis 持久化機制保存的數(shù)據(jù)盡快恢復緩存
緩存穿透
簡介:一般是黑客故意去請求緩存中不存在的數(shù)據(jù),導致所有的請求都落到數(shù)據(jù)庫上,造成數(shù)據(jù)庫短時間內(nèi)承受大量請求而崩掉。
解決辦法: 有很多種方法可以有效地解決緩存穿透問題,最常見的則是采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個足夠大的bitmap中,一個一定不存在的數(shù)據(jù)會被 這個bitmap攔截掉,從而避免了對底層存儲系統(tǒng)的查詢壓力。另外也有一個更為簡單粗暴的方法(我們采用的就是這種),如果一個查詢返回的數(shù)據(jù)為空(不管是數(shù) 據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀话堰@個空結(jié)果進行緩存,但它的過期時間會很短,最長不超過五分鐘。
六 Java
6.1 Java 基礎知識
重載和重寫的區(qū)別
重載: 發(fā)生在同一個類中,方法名必須相同,參數(shù)類型不同、個數(shù)不同、順序不同,方法返回值和訪問修飾符可以不同,發(fā)生在編譯時。 ??
重寫: 發(fā)生在父子類中,方法名、參數(shù)列表必須相同,返回值范圍小于等于父類,拋出的異常范圍小于等于父類,訪問修飾符范圍大于等于父類;如果父類方法訪問修飾符為 private 則子類就不能重寫該方法。
String 和 StringBuffer、StringBuilder 的區(qū)別是什么?String 為什么是不可變的?
可變性
?
簡單的來說:String 類中使用 final 關鍵字字符數(shù)組保存字符串,private?final?char?value[],所以 String 對象是不可變的。而StringBuilder 與 StringBuffer 都繼承自 AbstractStringBuilder 類,在 AbstractStringBuilder 中也是使用字符數(shù)組保存字符串char[]value 但是沒有用 final 關鍵字修飾,所以這兩種對象都是可變的。
StringBuilder 與 StringBuffer 的構(gòu)造方法都是調(diào)用父類構(gòu)造方法也就是 AbstractStringBuilder 實現(xiàn)的,大家可以自行查閱源碼。
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 對方法加了同步鎖或者對調(diào)用的方法加了同步鎖,所以是線程安全的。StringBuilder 并沒有對方法進行加同步鎖,所以是非線程安全的。
??
性能
每次對 String 類型進行改變的時候,都會生成一個新的 String 對象,然后將指針指向新的 String 對象。StringBuffer 每次都會對 StringBuffer 對象本身進行操作,而不是生成新的對象并改變對象引用。相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的性能提升,但卻要冒多線程不安全的風險。
對于三者使用的總結(jié):
操作少量的數(shù)據(jù) = String
單線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù) = StringBuilder
多線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù) = StringBuffer
自動裝箱與拆箱
裝箱:將基本類型用它們對應的引用類型包裝起來;
拆箱:將包裝類型轉(zhuǎn)換為基本數(shù)據(jù)類型;
== 與 equals
== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象。(基本數(shù)據(jù)類型==比較的是值,引用數(shù)據(jù)類型==比較的是內(nèi)存地址)
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:
情況1:類沒有覆蓋 equals() 方法。則通過 equals() 比較該類的兩個對象時,等價于通過“==”比較這兩個對象。
情況2:類覆蓋了 equals() 方法。一般,我們都覆蓋 equals() 方法來兩個對象的內(nèi)容相等;若它們的內(nèi)容相等,則返回 true (即,認為這兩個對象相等)。
舉個例子:
public class test1 {
public static void main(String[] args) { String a = new String("ab"); // a 為一個引用 String b = new String("ab"); // b為另一個引用,對象的內(nèi)容一樣 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 方法是比較的對象的內(nèi)存地址,而 String 的 equals 方法比較的是對象的值。
當創(chuàng)建 String 類型的對象時,虛擬機會在常量池中查找有沒有已經(jīng)存在的值和要創(chuàng)建的值相同的對象,如果有就把它賦給當前引用。如果沒有就在常量池中重新創(chuàng)建一個 String 對象。
關于 final 關鍵字的一些總結(jié)
final關鍵字主要用在三個地方:變量、方法、類。
對于一個final變量,如果是基本數(shù)據(jù)類型的變量,則其數(shù)值一旦在初始化之后便不能更改;如果是引用類型的變量,則在對其初始化之后便不能再讓其指向另一個對象。
當用final修飾一個類時,表明這個類不能被繼承。final類中的所有成員方法都會被隱式地指定為final方法。
使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實現(xiàn)版本中,會將final方法轉(zhuǎn)為內(nèi)嵌調(diào)用。但是如果方法過于龐大,可能看不到內(nèi)嵌調(diào)用帶來的任何性能提升(現(xiàn)在的Java版本已經(jīng)不需要使用final方法進行這些優(yōu)化了)。類中所有的private方法都隱式地指定為fianl。
6.2 Java 集合框架
Arraylist 與 LinkedList 異同
是否保證線程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保證線程安全;
底層數(shù)據(jù)結(jié)構(gòu): Arraylist 底層使用的是Object數(shù)組;LinkedList 底層使用的是雙向循環(huán)鏈表數(shù)據(jù)結(jié)構(gòu);
插入和刪除是否受元素位置的影響: ① ArrayList 采用數(shù)組存儲,所以插入和刪除元素的時間復雜度受元素位置的影響。 比如:執(zhí)行add(E e)方法的時候, ArrayList 會默認在將指定的元素追加到此列表的末尾,這種情況時間復雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element))時間復雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之后的(n-i)個元素都要執(zhí)行向后位/向前移一位的操作。 ② LinkedList 采用鏈表存儲,所以插入,刪除元素時間復雜度不受元素位置的影響,都是近似 O(1)而數(shù)組為近似 O(n)。
是否支持快速隨機訪問: LinkedList 不支持高效的隨機元素訪問,而ArrayList 實現(xiàn)了RandmoAccess 接口,所以有隨機訪問功能??焖匐S機訪問就是通過元素的序號快速獲取元素對象(對應于get(int index)方法)。
內(nèi)存空間占用: ArrayList的空 間浪費主要體現(xiàn)在在list列表的結(jié)尾會預留一定的容量空間,而LinkedList的空間花費則體現(xiàn)在它的每一個元素都需要消耗比ArrayList更多的空間(因為要存放直接后繼和直接前驅(qū)以及數(shù)據(jù))。
補充:數(shù)據(jù)結(jié)構(gòu)基礎之雙向鏈表
雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數(shù)據(jù)結(jié)點中都有兩個指針,分別指向直接后繼和直接前驅(qū)。所以,從雙向鏈表中的任意一個結(jié)點開始,都可以很方便地訪問它的前驅(qū)結(jié)點和后繼結(jié)點。一般我們都構(gòu)造雙向循環(huán)鏈表,如下圖所示,同時下圖也是LinkedList 底層使用的是雙向循環(huán)鏈表數(shù)據(jù)結(jié)構(gòu)。
ArrayList 與 Vector 區(qū)別
Vector類的所有方法都是同步的??梢杂蓛蓚€線程安全地訪問一個Vector對象、但是一個線程訪問Vector的話代碼要在同步操作上耗費大量的時間。
Arraylist不是同步的,所以在不需要保證線程安全時時建議使用Arraylist。
HashMap的底層實現(xiàn)
①JDK1.8之前
JDK1.8 之前 HashMap 底層是 數(shù)組和鏈表 結(jié)合在一起使用也就是 鏈表散列。HashMap 通過 key 的 hashCode 經(jīng)過擾動函數(shù)處理過后得到 hash 值,然后通過 (n - 1) & hash 判斷當前元素存放的位置(這里的 n 指的時數(shù)組的長度),如果當前位置存在元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,直接覆蓋,不相同就通過拉鏈法解決沖突。
所謂擾動函數(shù)指的就是 HashMap 的 hash 方法。使用 hash 方法也就是擾動函數(shù)是為了防止一些實現(xiàn)比較差的 hashCode() 方法 換句話說使用擾動函數(shù)之后可以減少碰撞。
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 次。
所謂 “拉鏈法” 就是:將鏈表和數(shù)組相結(jié)合。也就是說創(chuàng)建一個鏈表數(shù)組,數(shù)組中每一格就是一個鏈表。若遇到哈希沖突,則將沖突的值加到鏈表中即可。
②JDK1.8之后
相比于之前的版本, JDK1.8之后在解決哈希沖突時有了較大的變化,當鏈表長度大于閾值(默認為8)時,將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時間。
TreeMap、TreeSet以及JDK1.8之后的HashMap底層都用到了紅黑樹。紅黑樹就是為了解決二叉查找樹的缺陷,因為二叉查找樹在某些情況下會退化成一個線性結(jié)構(gòu)。
HashMap 和 Hashtable 的區(qū)別
線程是否安全: HashMap 是非線程安全的,HashTable 是線程安全的;HashTable 內(nèi)部的方法基本都經(jīng)過 synchronized 修飾。(如果你要保證線程安全的話就使用 ConcurrentHashMap 吧?。?br>效率: 因為線程安全的問題,HashMap 要比 HashTable 效率高一點。另外,HashTable 基本被淘汰,不要在代碼中使用它;
對Null key 和Null value的支持: HashMap 中,null 可以作為鍵,這樣的鍵只有一個,可以有一個或多個鍵所對應的值為 null。。但是在 HashTable 中 put 進的鍵值只要有一個 null,直接拋出 NullPointerException。
初始容量大小和每次擴充容量大小的不同 : ①創(chuàng)建時如果不指定容量初始值,Hashtable 默認的初始大小為11,之后每次擴充,容量變?yōu)樵瓉淼?n+1。HashMap 默認的初始化大小為16。之后每次擴充,容量變?yōu)樵瓉淼?倍。②創(chuàng)建時如果給定了容量初始值,那么 Hashtable 會直接使用你給定的大小,而 HashMap 會將其擴充為2的冪次方大小。也就是說 HashMap 總是使用2的冪作為哈希表的大小,后面會介紹到為什么是2的冪次方。
底層數(shù)據(jù)結(jié)構(gòu): JDK1.8 以后的 HashMap 在解決哈希沖突時有了較大的變化,當鏈表長度大于閾值(默認為8)時,將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時間。Hashtable 沒有這樣的機制。
HashMap 的長度為什么是2的冪次方
為了能讓 HashMap 存取高效,盡量較少碰撞,也就是要盡量把數(shù)據(jù)分配均勻。我們上面也講到了過了,Hash 值的范圍值-2147483648到2147483648,前后加起來大概40億的映射空間,只要哈希函數(shù)映射得比較均勻松散,一般應用是很難出現(xiàn)碰撞的。但問題是一個40億長度的數(shù)組,內(nèi)存是放不下的。所以這個散列值是不能直接拿來用的。用之前還要先做對數(shù)組的長度取模運算,得到的余數(shù)才能用來要存放的位置也就是對應的數(shù)組下標。
這個算法應該如何設計呢?
我們首先可能會想到采用%取余的操作來實現(xiàn)。但是,重點來了:“取余(%)操作中如果除數(shù)是2的冪次則等價于與其除數(shù)減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)?!?并且 采用二進制位操作 &,相對于%能夠提高運算效率,這就解釋了 HashMap 的長度為什么是2的冪次方。
HashMap 多線程操作導致死循環(huán)問題
在多線程下,進行 put 操作會導致 HashMap 死循環(huán),原因在于 HashMap 的擴容 resize()方法。由于擴容是新建一個數(shù)組,復制原數(shù)據(jù)到數(shù)組。由于數(shù)組下標掛有鏈表,所以需要復制鏈表,但是多線程操作有可能導致環(huán)形鏈表。復制鏈表過程如下:
以下模擬2個線程同時擴容。假設,當前 HashMap 的空間為2(臨界值為1),hashcode 分別為 0 和 1,在散列地址 0 處有元素 A 和 B,這時候要添加元素 C,C 經(jīng)過 hash 運算,得到散列地址為 1,這時候由于超過了臨界值,空間不夠,需要調(diào)用 resize 方法進行擴容,那么在多線程條件下,會出現(xiàn)條件競爭,模擬過程如下:
線程一:讀取到當前的 HashMap 情況,在準備擴容時,線程二介入
線程二:讀取 HashMap,進行擴容
線程一:繼續(xù)執(zhí)行
這個過程為,先將 A 復制到新的 hash 表中,然后接著復制 B 到鏈頭(A 的前邊:B.next=A),本來 B.next=null,到此也就結(jié)束了(跟線程二一樣的過程),但是,由于線程二擴容的原因,將 B.next=A,所以,這里繼續(xù)復制A,讓 A.next=B,由此,環(huán)形鏈表出現(xiàn):B.next=A; A.next=B
HashSet 和 HashMap 區(qū)別
如果你看過 HashSet 源碼的話就應該知道:HashSet 底層就是基于 HashMap 實現(xiàn)的。(HashSet 的源碼非常非常少,因為除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不實現(xiàn)之外,其他方法都是直接調(diào)用 HashMap 中的方法。)
ConcurrentHashMap 和 Hashtable 的區(qū)別
ConcurrentHashMap 和 Hashtable 的區(qū)別主要體現(xiàn)在實現(xiàn)線程安全的方式上不同。
底層數(shù)據(jù)結(jié)構(gòu): JDK1.7的 ConcurrentHashMap 底層采用 分段的數(shù)組+鏈表 實現(xiàn),JDK1.8 采用的數(shù)據(jù)結(jié)構(gòu)跟HashMap1.8的結(jié)構(gòu)一樣,數(shù)組+鏈表/紅黑二叉樹。Hashtable 和 JDK1.8 之前的 HashMap 的底層數(shù)據(jù)結(jié)構(gòu)類似都是采用 數(shù)組+鏈表 的形式,數(shù)組是 HashMap 的主體,鏈表則是主要為了解決哈希沖突而存在的;
實現(xiàn)線程安全的方式(重要): ① 在JDK1.7的時候,ConcurrentHashMap(分段鎖) 對整個桶數(shù)組進行了分割分段(Segment),每一把鎖只鎖容器其中一部分數(shù)據(jù),多線程訪問容器里不同數(shù)據(jù)段的數(shù)據(jù),就不會存在鎖競爭,提高并發(fā)訪問率。(默認分配16個Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的時候已經(jīng)摒棄了Segment的概念,而是直接用 Node 數(shù)組+鏈表+紅黑樹的數(shù)據(jù)結(jié)構(gòu)來實現(xiàn),并發(fā)控制使用 synchronized 和 CAS 來操作。(JDK1.6以后 對 synchronized鎖做了很多優(yōu)化) 整個看起來就像是優(yōu)化過且線程安全的 HashMap,雖然在JDK1.8中還能看到 Segment 的數(shù)據(jù)結(jié)構(gòu),但是已經(jīng)簡化了屬性,只是為了兼容舊版本;② Hashtable(同一把鎖) :使用 synchronized 來保證線程安全,效率非常低下。當一個線程訪問同步方法時,其他線程也訪問同步方法,可能會進入阻塞或輪詢狀態(tài),如使用 put 添加元素,另一個線程不能使用 put 添加元素,也不能使用 get,競爭會越來越激烈效率越低。
兩者的對比圖:
圖片來源:www.cnblogs.com/chengxiao/p…
HashTable:
JDK1.7的ConcurrentHashMap:
JDK1.8的ConcurrentHashMap(TreeBin: 紅黑二叉樹節(jié)點
Node: 鏈表節(jié)點):
ConcurrentHashMap線程安全的具體實現(xiàn)方式/底層具體實現(xiàn)
①JDK1.7(上面有示意圖)
首先將數(shù)據(jù)分為一段一段的存儲,然后給每一段數(shù)據(jù)配一把鎖,當一個線程占用鎖訪問其中一個段數(shù)據(jù)時,其他段的數(shù)據(jù)也能被其他線程訪問。
ConcurrentHashMap 是由 Segment 數(shù)組結(jié)構(gòu)和 HahEntry 數(shù)組結(jié)構(gòu)組成。
Segment 實現(xiàn)了 ReentrantLock,所以 Segment 是一種可重入鎖,扮演鎖的角色。HashEntry 用于存儲鍵值對數(shù)據(jù)。
static class Segment
}
復制代碼一個 ConcurrentHashMap 里包含一個 Segment 數(shù)組。Segment 的結(jié)構(gòu)和HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu),一個 Segment 包含一個 HashEntry 數(shù)組,每個 HashEntry 是一個鏈表結(jié)構(gòu)的元素,每個 Segment 守護著一個HashEntry數(shù)組里的元素,當對 HashEntry 數(shù)組的數(shù)據(jù)進行修改時,必須首先獲得對應的 Segment的鎖。
②JDK1.8 (上面有示意圖)
ConcurrentHashMap取消了Segment分段鎖,采用CAS和synchronized來保證并發(fā)安全。數(shù)據(jù)結(jié)構(gòu)跟HashMap1.8的結(jié)構(gòu)類似,數(shù)組+鏈表/紅黑二叉樹。
synchronized只鎖定當前鏈表或紅黑二叉樹的首節(jié)點,這樣只要hash不沖突,就不會產(chǎn)生并發(fā),效率又提升N倍。
集合框架底層數(shù)據(jù)結(jié)構(gòu)總結(jié)
Collection
1.List
Arraylist: Object數(shù)組
Vector: Object數(shù)組
LinkedList: 雙向循環(huán)鏈表
2.Set
HashSet(無序,唯一): 基于 HashMap 實現(xiàn)的,底層采用 HashMap 來保存元素
LinkedHashSet: LinkedHashSet 繼承與 HashSet,并且其內(nèi)部是通過 LinkedHashMap 來實現(xiàn)的。有點類似于我們之前說的LinkedHashMap 其內(nèi)部是基于 Hashmap 實現(xiàn)一樣,不過還是有一點點區(qū)別的。
TreeSet(有序,唯一): 紅黑樹(自平衡的排序二叉樹。)
Map
HashMap: JDK1.8之前HashMap由數(shù)組+鏈表組成的,數(shù)組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的(“拉鏈法”解決沖突).JDK1.8以后在解決哈希沖突時有了較大的變化,當鏈表長度大于閾值(默認為8)時,將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時間
LinkedHashMap: LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基于拉鏈式散列結(jié)構(gòu)即由數(shù)組和鏈表或紅黑樹組成。另外,LinkedHashMap 在上面結(jié)構(gòu)的基礎上,增加了一條雙向鏈表,使得上面的結(jié)構(gòu)可以保持鍵值對的插入順序。同時通過對鏈表進行相應的操作,實現(xiàn)了訪問順序相關邏輯。詳細可以查看:《LinkedHashMap 源碼詳細分析(JDK1.8)》
HashTable: 數(shù)組+鏈表組成的,數(shù)組是 HashMap 的主體,鏈表則是主要為了解決哈希沖突而存在的
TreeMap: 紅黑樹(自平衡的排序二叉樹)
6.3 Java多線程
關于 Java多線程,在面試的時候,問的比較多的就是①悲觀鎖和樂觀鎖( 具體可以看我的這篇文章:面試必備之樂觀鎖與悲觀鎖)、②synchronized和lock區(qū)別以及volatile和synchronized的區(qū)別,③可重入鎖與非可重入鎖的區(qū)別、④多線程是解決什么問題的、⑤線程池解決什么問題、⑥線程池的原理、⑦線程池使用時的注意事項、⑧AQS原理、⑨ReentranLock源碼,設計原理,整體過程 等等問題。
??面試官在多線程這一部分很可能會問你有沒有在項目中實際使用多線程的經(jīng)歷。所以,如果你在你的項目中有實際使用Java多線程的經(jīng)歷 的話,會為你加分不少哦!
6.4 Java虛擬機
??關于Java虛擬機,在面試的時候一般會問的大多就是①Java內(nèi)存區(qū)域、②虛擬機垃圾算法、③虛擬機垃圾收集器、④JVM內(nèi)存管理、⑤JVM調(diào)優(yōu)這些問題了。
6.5 設計模式
設計模式比較常見的就是讓你手寫一個單例模式(注意單例模式的幾種不同的實現(xiàn)方法)或者讓你說一下某個常見的設計模式在你的項目中是如何使用的,另外面試官還有可能問你抽象工廠和工廠方法模式的區(qū)別、工廠模式的思想這樣的問題。
建議把代理模式、觀察者模式、(抽象)工廠模式好好看一下,這三個設計模式也很重要。
七 數(shù)據(jù)結(jié)構(gòu)
??數(shù)據(jù)結(jié)構(gòu)比較常問的就是:二叉樹、紅黑樹(很可能讓你手繪一個紅黑樹出來哦!)、二叉查找樹(BST)、平衡二叉樹(Self-balancing binary search tree)、B-樹,B+樹與B*樹的優(yōu)缺點比較、 LSM 樹這些知識點。
??數(shù)據(jù)結(jié)構(gòu)很重要,而且學起來也相對要難一些。建議學習數(shù)據(jù)結(jié)構(gòu)一定要循序漸進的來,一步一個腳印的走好。一定要搞懂原理,最好自己能用代碼實現(xiàn)一遍。
八 算法
??常見的加密算法、排序算法都需要自己提前了解一下,排序算法最好自己能夠獨立手寫出來。
??我覺得面試中最刺激、最有壓力或者說最有挑戰(zhàn)的一個環(huán)節(jié)就是手撕算法了。面試中大部分算法題目都是來自于Leetcode、劍指offer上面,建議大家可以每天擠出一點時間刷一下算法題。
推薦兩個刷題必備網(wǎng)站:
LeetCode:
LeetCode(中國)官網(wǎng)
如何高效地使用 LeetCode
牛客網(wǎng):
牛
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77277.html
摘要:如何考察一個人是不是經(jīng)驗豐富我們需要在問答式的面試中,對其項目經(jīng)驗進行挖掘。如何設置筆試題現(xiàn)在網(wǎng)上有大量的面經(jīng)的存在,對于我們面試是一個巨大的挑戰(zhàn)。尊重應聘者我們要尊重每一個來應聘的人,不要輕視別人,或者故意刁難別人。 時光荏苒,2個月前,我才剛總結(jié)了如何應對面試官,現(xiàn)在的我開始總結(jié)如何面試別人了。笑哭.png 1.我們需要什么樣的人 招聘肯定要有標準,這樣我們才能更快的找到我們需要的...
閱讀 2683·2021-11-18 10:02
閱讀 3415·2021-09-28 09:35
閱讀 2594·2021-09-22 15:12
閱讀 753·2021-09-22 15:08
閱讀 3109·2021-09-07 09:58
閱讀 3475·2021-08-23 09:42
閱讀 735·2019-08-30 12:53
閱讀 2085·2019-08-29 13:51