秋招提前批開始了!
共 10323字,需瀏覽 21分鐘
·
2024-06-25 15:24
圖解學(xué)習(xí)網(wǎng)站:https://xiaolincoding.com
大家好,我是小林。
最近看到不少公司都開始秋招提前批了,很多同學(xué)都傻眼了,我都還在找實(shí)習(xí),咋提前批就開始了?
目前已經(jīng)開展秋招提前批的公司,有深信服、tplink 等公司,互聯(lián)網(wǎng)大廠還沒開始,預(yù)計(jì)7 月份中旬左右,各大互聯(lián)網(wǎng)公司就會陸陸續(xù)續(xù)開展秋招提前批的公告了。
同學(xué)們多關(guān)注這些公司的招聘公眾號,一個(gè)小技巧,通常它們的公眾號都是公司名+招聘兩個(gè)字,比如「騰訊招聘」、「字節(jié)跳動招聘」根據(jù)這樣去搜索,就能找到官方的招聘公眾號了。
那問題來了,秋招提前批適合什么人去面呢?
企業(yè)開展秋招提前批的目的是為了提前招到優(yōu)秀的人才,針對優(yōu)秀的同學(xué)會發(fā) sp offer,注意是優(yōu)秀的人才,所以你必須是有過人的經(jīng)歷,比如是名校畢業(yè)、有互聯(lián)網(wǎng)中大廠實(shí)習(xí)、有 acm 獎項(xiàng),這類同學(xué)就可以嘗試面面看,你獲得面試的機(jī)會才會比較大。
本身別人就是以高標(biāo)準(zhǔn)的方式去招聘優(yōu)秀的同學(xué),只適合少數(shù)優(yōu)秀的同學(xué),不適合大多數(shù)普通同學(xué),普通同學(xué)在這段時(shí)間多積累項(xiàng)目,實(shí)習(xí)可以繼續(xù)找,準(zhǔn)備 9 月份的正式秋招就可以了,這時(shí)候湊熱鬧去投提前批,可能容易被打擊你的自信心,影響了復(fù)習(xí)的心態(tài)。
當(dāng)然,并不說提前批拿到的 offer 一定是 sp offer,也見過提前批面試通過的同學(xué),拿到的是白菜檔,也不是說正式秋招就沒有 sp offer 了,也見過秋招末尾 12 月份的時(shí)候,不少同學(xué)拿到 sp、ssp 檔的 offer。
那最近看到不少同學(xué)已經(jīng)去提前批面試了,今天就來分享深信服的提前批 Java 后端開發(fā)面經(jīng),給大家感受一下提前批面試的氛圍。
考察的知識點(diǎn),主要圍繞Redis+MySQL+項(xiàng)目+數(shù)據(jù)結(jié)構(gòu)+算法。Java 語言問的不多,因?yàn)樯钚欧蠖鄶?shù)面試官的技術(shù)棧是 C++
Redis
為什么使用redis?
主要是因?yàn)?Redis 具備「高性能」和「高并發(fā)」兩種特性。
1、Redis 具備高性能
假如用戶第一次訪問 MySQL 中的某些數(shù)據(jù)。這個(gè)過程會比較慢,因?yàn)槭菑挠脖P上讀取的。將該用戶訪問的數(shù)據(jù)緩存在 Redis 中,這樣下一次再訪問這些數(shù)據(jù)的時(shí)候就可以直接從緩存中獲取了,操作 Redis 緩存就是直接操作內(nèi)存,所以速度相當(dāng)快。
如果 MySQL 中的對應(yīng)數(shù)據(jù)改變的之后,同步改變 Redis 緩存中相應(yīng)的數(shù)據(jù)即可,不過這里會有 Redis 和 MySQL 雙寫一致性的問題。
2、 Redis 具備高并發(fā)
單臺設(shè)備的 Redis 的 QPS(Query Per Second,每秒鐘處理完請求的次數(shù)) 是 MySQL 的 10 倍,Redis 單機(jī)的 QPS 能輕松破 10w,而 MySQL 單機(jī)的 QPS 很難破 1w。
所以,直接訪問 Redis 能夠承受的請求是遠(yuǎn)遠(yuǎn)大于直接訪問 MySQL 的,所以我們可以考慮把數(shù)據(jù)庫中的部分?jǐn)?shù)據(jù)轉(zhuǎn)移到緩存中去,這樣用戶的一部分請求會直接到緩存這里而不用經(jīng)過數(shù)據(jù)庫。
為什么redis比mysql要快?
-
內(nèi)存存儲:Redis 是基于內(nèi)存存儲的 NoSQL 數(shù)據(jù)庫,而 MySQL 是基于磁盤存儲的關(guān)系型數(shù)據(jù)庫。由于內(nèi)存存儲速度快,Redis 能夠更快地讀取和寫入數(shù)據(jù),而無需像 MySQL 那樣頻繁進(jìn)行磁盤 I/O 操作。 -
簡單數(shù)據(jù)結(jié)構(gòu):Redis 是基于鍵值對存儲數(shù)據(jù)的,支持簡單的數(shù)據(jù)結(jié)構(gòu)(字符串、哈希、列表、集合、有序集合)。相比之下,MySQL 需要定義表結(jié)構(gòu)、索引等復(fù)雜的關(guān)系型數(shù)據(jù)結(jié)構(gòu),因此在某些場景下 Redis 的數(shù)據(jù)操作更為簡單高效,比如 Redis 用哈希表查詢, 只需要O1 時(shí)間復(fù)雜度,而MySQL引擎的底層實(shí)現(xiàn)是B+Tree,時(shí)間復(fù)雜度是O(logn) -
線程模型:Redis 采用單線程模型可以避免了多線程之間的競爭,省去了多線程切換帶來的時(shí)間和性能上的開銷,而且也不會導(dǎo)致死鎖問題。
redis掛了怎么辦?
可以通過構(gòu)建 Redis 集群,提高Redis 高可用,比如 Redis 的主從復(fù)制、哨兵模式、切片集群。
主從復(fù)制
主從復(fù)制是 Redis 高可用服務(wù)的最基礎(chǔ)的保證,實(shí)現(xiàn)方案就是將從前的一臺 Redis 服務(wù)器,同步數(shù)據(jù)到多臺從 Redis 服務(wù)器上,即一主多從的模式,且主從服務(wù)器之間采用的是「讀寫分離」的方式。
主服務(wù)器可以進(jìn)行讀寫操作,當(dāng)發(fā)生寫操作時(shí)自動將寫操作同步給從服務(wù)器,而從服務(wù)器一般是只讀,并接受主服務(wù)器同步過來寫操作命令,然后執(zhí)行這條命令。
也就是說,所有的數(shù)據(jù)修改只在主服務(wù)器上進(jìn)行,然后將最新的數(shù)據(jù)同步給從服務(wù)器,這樣就使得主從服務(wù)器的數(shù)據(jù)是一致的。
注意,主從服務(wù)器之間的命令復(fù)制是異步進(jìn)行的。
具體來說,在主從服務(wù)器命令傳播階段,主服務(wù)器收到新的寫命令后,會發(fā)送給從服務(wù)器。
但是,主服務(wù)器并不會等到從服務(wù)器實(shí)際執(zhí)行完命令后,再把結(jié)果返回給客戶端,而是主服務(wù)器自己在本地執(zhí)行完命令后,就會向客戶端返回結(jié)果了。如果從服務(wù)器還沒有執(zhí)行主服務(wù)器同步過來的命令,主從服務(wù)器間的數(shù)據(jù)就不一致了。
所以,無法實(shí)現(xiàn)強(qiáng)一致性保證(主從數(shù)據(jù)時(shí)時(shí)刻刻保持一致),數(shù)據(jù)不一致是難以避免的。
哨兵模式
在使用 Redis 主從服務(wù)的時(shí)候,會有一個(gè)問題,就是當(dāng) Redis 的主從服務(wù)器出現(xiàn)故障宕機(jī)時(shí),需要手動進(jìn)行恢復(fù)。
為了解決這個(gè)問題,Redis 增加了哨兵模式(Redis Sentinel),因?yàn)樯诒J阶龅搅丝梢员O(jiān)控主從服務(wù)器,并且提供主從節(jié)點(diǎn)故障轉(zhuǎn)移的功能。
切片集群模式
當(dāng) Redis 緩存數(shù)據(jù)量大到一臺服務(wù)器無法緩存時(shí),就需要使用 Redis 切片集群(Redis Cluster )方案,它將數(shù)據(jù)分布在不同的服務(wù)器上,以此來降低系統(tǒng)對單主節(jié)點(diǎn)的依賴,從而提高 Redis 服務(wù)的讀寫性能。
Redis Cluster 方案采用哈希槽(Hash Slot),來處理數(shù)據(jù)和節(jié)點(diǎn)之間的映射關(guān)系。在 Redis Cluster 方案中,一個(gè)切片集群共有 16384 個(gè)哈希槽,這些哈希槽類似于數(shù)據(jù)分區(qū),每個(gè)鍵值對都會根據(jù)它的 key,被映射到一個(gè)哈希槽中,具體執(zhí)行過程分為兩大步:
-
根據(jù)鍵值對的 key,按照 CRC16 算法計(jì)算一個(gè) 16 bit 的值。 -
再用 16bit 值對 16384 取模,得到 0~16383 范圍內(nèi)的模數(shù),每個(gè)模數(shù)代表一個(gè)相應(yīng)編號的哈希槽。
接下來的問題就是,這些哈希槽怎么被映射到具體的 Redis 節(jié)點(diǎn)上的呢?有兩種方案:
-
平均分配: 在使用 cluster create 命令創(chuàng)建 Redis 集群時(shí),Redis 會自動把所有哈希槽平均分布到集群節(jié)點(diǎn)上。比如集群中有 9 個(gè)節(jié)點(diǎn),則每個(gè)節(jié)點(diǎn)上槽的個(gè)數(shù)為 16384/9 個(gè)。 -
手動分配: 可以使用 cluster meet 命令手動建立節(jié)點(diǎn)間的連接,組成集群,再使用 cluster addslots 命令,指定每個(gè)節(jié)點(diǎn)上的哈希槽個(gè)數(shù)。
為了方便你的理解,我通過一張圖來解釋數(shù)據(jù)、哈希槽,以及節(jié)點(diǎn)三者的映射分布關(guān)系。
上圖中的切片集群一共有 2 個(gè)節(jié)點(diǎn),假設(shè)有 4 個(gè)哈希槽(Slot 0~Slot 3)時(shí),我們就可以通過命令手動分配哈希槽,比如節(jié)點(diǎn) 1 保存哈希槽 0 和 1,節(jié)點(diǎn) 2 保存哈希槽 2 和 3。
redis-cli -h 192.168.1.10 –p 6379 cluster addslots 0,1
redis-cli -h 192.168.1.11 –p 6379 cluster addslots 2,3
然后在集群運(yùn)行的過程中,key1 和 key2 計(jì)算完 CRC16 值后,對哈希槽總個(gè)數(shù) 4 進(jìn)行取模,再根據(jù)各自的模數(shù)結(jié)果,就可以被映射到哈希槽 1(對應(yīng)節(jié)點(diǎn)1) 和 哈希槽 2(對應(yīng)節(jié)點(diǎn)2)。
需要注意的是,在手動分配哈希槽時(shí),需要把 16384 個(gè)槽都分配完,否則 Redis 集群無法正常工作。
有沒有測過加了redis和沒加redis的qps ?
單臺設(shè)備的 Redis 的 QPS(Query Per Second,每秒鐘處理完請求的次數(shù)) 是 MySQL 的 10 倍,Redis 單機(jī)的 QPS 能輕松破 10w,而 MySQL 單機(jī)的 QPS 很難破 1w。
如何實(shí)現(xiàn)redis 原子性?
redis 執(zhí)行一條命令的時(shí)候是具備原子性的,因?yàn)?redis 執(zhí)行命令的時(shí)候是單線程來處理的,不存在多線程安全的問題。
如果要保證 2 條命令的原子性的話,可以考慮用 lua 腳本,將多個(gè)操作寫到一個(gè) Lua 腳本中,Redis 會把整個(gè) Lua 腳本作為一個(gè)整體執(zhí)行,在執(zhí)行的過程中不會被其他命令打斷,從而保證了 Lua 腳本中操作的原子性。
比如說,在用 redis 實(shí)現(xiàn)分布式鎖的場景下,解鎖期間涉及 2 個(gè)操作,分別是先判斷鎖是不是自己的,是自己的才能刪除鎖,為了保證這 2 個(gè)操作的原子性,會通過 lua 腳本來保證原子性。
// 釋放鎖時(shí),先比較 unique_value 是否相等,避免鎖的誤釋放
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
除了lua有沒有什么也能保證redis的原子性?
redis 事務(wù)也可以保證多個(gè)操作的原子性。
如果 redis 事務(wù)正常執(zhí)行,沒有發(fā)生任何錯誤,那么使用 MULTI 和 EXEC 配合使用,就可以保證多個(gè)操作都完成。
但是,如果事務(wù)執(zhí)行發(fā)生錯誤了,就沒辦法保證原子性了。比如說 2 個(gè)操作,第一個(gè)操作執(zhí)行成果了,但是第二個(gè)操作執(zhí)行的時(shí)候,命令出錯了,那事務(wù)并不會回滾,因?yàn)镽edis 中并沒有提供回滾機(jī)制。
舉個(gè)小例子。事務(wù)中的 LPOP 命令對 String 類型數(shù)據(jù)進(jìn)行操作,入隊(duì)時(shí)沒有報(bào)錯,但是,在 EXEC 執(zhí)行時(shí)報(bào)錯了。LPOP 命令本身沒有執(zhí)行成功,但是事務(wù)中的 DECR 命令卻成功執(zhí)行了。
#開啟事務(wù)
127.0.0.1:6379> MULTI
OK
#發(fā)送事務(wù)中的第一個(gè)操作,LPOP命令操作的數(shù)據(jù)類型不匹配,此時(shí)并不報(bào)錯
127.0.0.1:6379> LPOP a:stock
QUEUED
#發(fā)送事務(wù)中的第二個(gè)操作
127.0.0.1:6379> DECR b:stock
QUEUED
#實(shí)際執(zhí)行事務(wù),事務(wù)第一個(gè)操作執(zhí)行報(bào)錯
127.0.0.1:6379> EXEC
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) (integer) 8
因此,Redis 對事務(wù)原子性屬性的保證情況:
-
Redis 事務(wù)正常執(zhí)行,可以保證原子性; -
Redis 事務(wù)執(zhí)行中某一個(gè)操作執(zhí)行失敗,不保證原子性;
MySQL
為什么要用索引?
索引可以減少掃描的數(shù)據(jù)量,提高查詢效率。
-
如果查詢的時(shí)候,沒有用到索引就會全表掃描,這時(shí)候查詢的時(shí)間復(fù)雜度是On -
如果用到了索引,那么查詢的時(shí)候,可以基于二分查找算法,通過索引快速定位到目標(biāo)數(shù)據(jù), mysql 索引的數(shù)據(jù)結(jié)構(gòu)一般是 b+樹,其搜索復(fù)雜度為O(logdN),其中 d 表示節(jié)點(diǎn)允許的最大子節(jié)點(diǎn)個(gè)數(shù)為 d 個(gè)。
為什么索引用b?樹
MySQL 是會將數(shù)據(jù)持久化在硬盤,而存儲功能是由 MySQL 存儲引擎實(shí)現(xiàn)的,所以討論 MySQL 使用哪種數(shù)據(jù)結(jié)構(gòu)作為索引,實(shí)際上是在討論存儲引使用哪種數(shù)據(jù)結(jié)構(gòu)作為索引,InnoDB 是 MySQL 默認(rèn)的存儲引擎,它就是采用了 B+ 樹作為索引的數(shù)據(jù)結(jié)構(gòu)。
要設(shè)計(jì)一個(gè) MySQL 的索引數(shù)據(jù)結(jié)構(gòu),不僅僅考慮數(shù)據(jù)結(jié)構(gòu)增刪改的時(shí)間復(fù)雜度,更重要的是要考慮磁盤 I/0 的操作次數(shù)。因?yàn)樗饕陀涗浂际谴娣旁谟脖P,硬盤是一個(gè)非常慢的存儲設(shè)備,我們在查詢數(shù)據(jù)的時(shí)候,最好能在盡可能少的磁盤 I/0 的操作次數(shù)內(nèi)完成。
二分查找樹雖然是一個(gè)天然的二分結(jié)構(gòu),能很好的利用二分查找快速定位數(shù)據(jù),但是它存在一種極端的情況,每當(dāng)插入的元素都是樹內(nèi)最大的元素,就會導(dǎo)致二分查找樹退化成一個(gè)鏈表,此時(shí)查詢復(fù)雜度就會從 O(logn)降低為 O(n)。
為了解決二分查找樹退化成鏈表的問題,就出現(xiàn)了自平衡二叉樹,保證了查詢操作的時(shí)間復(fù)雜度就會一直維持在 O(logn) 。但是它本質(zhì)上還是一個(gè)二叉樹,每個(gè)節(jié)點(diǎn)只能有 2 個(gè)子節(jié)點(diǎn),隨著元素的增多,樹的高度會越來越高。
而樹的高度決定于磁盤 I/O 操作的次數(shù),因?yàn)闃涫谴鎯υ诖疟P中的,訪問每個(gè)節(jié)點(diǎn),都對應(yīng)一次磁盤 I/O 操作,也就是說樹的高度就等于每次查詢數(shù)據(jù)時(shí)磁盤 IO 操作的次數(shù),所以樹的高度越高,就會影響查詢性能。
B 樹和 B+ 都是通過多叉樹的方式,會將樹的高度變矮,所以這兩個(gè)數(shù)據(jù)結(jié)構(gòu)非常適合檢索存于磁盤中的數(shù)據(jù)。
但是 MySQL 默認(rèn)的存儲引擎 InnoDB 采用的是 B+ 作為索引的數(shù)據(jù)結(jié)構(gòu),原因有:
-
B+ 樹的非葉子節(jié)點(diǎn)不存放實(shí)際的記錄數(shù)據(jù),僅存放索引,因此數(shù)據(jù)量相同的情況下,相比存儲即存索引又存記錄的 B 樹,B+樹的非葉子節(jié)點(diǎn)可以存放更多的索引,因此 B+ 樹可以比 B 樹更「矮胖」,查詢底層節(jié)點(diǎn)的磁盤 I/O次數(shù)會更少。 -
B+ 樹有大量的冗余節(jié)點(diǎn)(所有非葉子節(jié)點(diǎn)都是冗余索引),這些冗余索引讓 B+ 樹在插入、刪除的效率都更高,比如刪除根節(jié)點(diǎn)的時(shí)候,不會像 B 樹那樣會發(fā)生復(fù)雜的樹的變化; -
B+ 樹葉子節(jié)點(diǎn)之間用鏈表連接了起來,有利于范圍查詢,而 B 樹要實(shí)現(xiàn)范圍查詢,因此只能通過樹的遍歷來完成范圍查詢,這會涉及多個(gè)節(jié)點(diǎn)的磁盤 I/O 操作,范圍查詢效率不如 B+ 樹。
數(shù)據(jù)結(jié)構(gòu)
紅黑樹和b?的區(qū)別?
紅黑樹是一種自平衡的二叉查找樹
B+樹是一種多路平衡查找樹
對于有 N 個(gè)葉子節(jié)點(diǎn)的 B+Tree,其搜索復(fù)雜度為O(logdN),其中 d 表示節(jié)點(diǎn)允許的最大子節(jié)點(diǎn)個(gè)數(shù)為 d 個(gè)。
在實(shí)際的應(yīng)用當(dāng)中, d 值是大于100的,這樣就保證了,即使數(shù)據(jù)達(dá)到千萬級別時(shí),B+Tree 的高度依然維持在 3~4 層左右,也就是說一次數(shù)據(jù)查詢操作只需要做 3~4 次的磁盤 I/O 操作就能查詢到目標(biāo)數(shù)據(jù)。
而紅黑樹的每個(gè)父節(jié)點(diǎn)的兒子節(jié)點(diǎn)個(gè)數(shù)只能是 2 個(gè),意味著其搜索復(fù)雜度為 O(logN),這已經(jīng)比 B+Tree 高出不少,因此紅黑樹檢索到目標(biāo)數(shù)據(jù)所經(jīng)歷的磁盤 I/O 次數(shù)要更多。
二叉樹搜索最壞的時(shí)間復(fù)雜度,為什么會這樣?以及用什么結(jié)果解決?
**當(dāng)每次插入的元素都是二叉查找樹中最大的元素,二叉查找樹就會退化成了一條鏈表,查找數(shù)據(jù)的時(shí)間復(fù)雜度變成了 O(n)**,如下動圖演示:
二叉查找樹由于存在退化成鏈表的可能性,會使得查詢操作的時(shí)間復(fù)雜度從 O(logn) 升為 O(n)。
為了解決二叉查找樹會在極端情況下退化成鏈表的問題,后面就有人提出平衡二叉查找樹(AVL 樹)。
主要是在二叉查找樹的基礎(chǔ)上增加了一些條件約束:每個(gè)節(jié)點(diǎn)的左子樹和右子樹的高度差不能超過 1。也就是說節(jié)點(diǎn)的左子樹和右子樹仍然為平衡二叉樹,這樣查詢操作的時(shí)間復(fù)雜度就會一直維持在 O(logn) 。
下圖是每次插入的元素都是平衡二叉查找樹中最大的元素,可以看到,它會維持自平衡:
除了平衡二叉查找樹,還有很多自平衡的二叉樹,比如紅黑樹,它也是通過一些約束條件來達(dá)到自平衡,不過紅黑樹的約束條件比較復(fù)雜。下面是紅黑樹插入節(jié)點(diǎn)的過程,這左旋右旋的操作,就是為了自平衡。
Java
為什么使用springboot
-
簡化開發(fā):Spring Boot通過提供一系列的開箱即用的組件和自動配置,簡化了項(xiàng)目的配置和開發(fā)過程,開發(fā)人員可以更專注于業(yè)務(wù)邏輯的實(shí)現(xiàn),而不需要花費(fèi)過多時(shí)間在繁瑣的配置上。 -
快速啟動:Spring Boot提供了快速的應(yīng)用程序啟動方式,可通過內(nèi)嵌的Tomcat、Jetty或Undertow等容器快速啟動應(yīng)用程序,無需額外的部署步驟,方便快捷。 -
自動化配置:Spring Boot通過自動配置功能,根據(jù)項(xiàng)目中的依賴關(guān)系和約定俗成的規(guī)則來配置應(yīng)用程序,減少了配置的復(fù)雜性,使開發(fā)者更容易實(shí)現(xiàn)應(yīng)用的最佳實(shí)踐。
手撕
用棧實(shí)現(xiàn)隊(duì)列
可以用 2 個(gè)棧來實(shí)現(xiàn)隊(duì)列,其中一個(gè)棧用于入隊(duì)操作,另一個(gè)棧用于出隊(duì)操作:
class MyQueue {
private Stack<Integer> stack1; // 第一個(gè)棧用于入隊(duì)
private Stack<Integer> stack2; // 第二個(gè)棧用于出隊(duì)
public MyQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if (stack2.isEmpty()) { // 如果第二個(gè)棧為空,將第一個(gè)棧中的元素依次彈出并壓入第二個(gè)棧
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop(); // 然后從第二個(gè)棧彈出元素
}
public int peek() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.peek(); // 查看第二個(gè)棧的棧頂元素
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty(); // 判斷隊(duì)列是否為空
}
}
其他
跟項(xiàng)目關(guān)系比較大,根據(jù)具體的項(xiàng)目的回答。
-
從項(xiàng)目中學(xué)到了什么 -
redis 存儲什么數(shù)據(jù)? -
mysql存儲什么數(shù)據(jù)? -
項(xiàng)目里面怎么優(yōu)化sql的
