圖解學(xué)習(xí)網(wǎng)站:https://xiaolincoding.com
大家好,我是小林。
騰訊的面試普遍都是 1 小時的左右面經(jīng),累計下來可能有 30 多個面試題,也有同學(xué)跟我反饋,他面騰訊的時候,也經(jīng)歷過 2 小時+ 的面試,寫了 3 個算法之后,就來 30 多個面試題,面完都滿頭大汗了。
不過,有時候也有可能遇到比較簡單騰訊面試,比如今天這位同學(xué)的騰訊的Java后端面經(jīng),相比其他同學(xué)的騰訊面經(jīng),這次的面經(jīng)問題都還算比較簡單和基礎(chǔ)的,沒有太難的題目。
可惜同學(xué)沒有把握住機會,一面之后就涼了。掛了沒事,重在補齊自己不會的知識點,持續(xù)修正自己,迭代自己的知識體系,會慢慢越來越順利的。
這次來跟大家拆解一下,大家看看自己能回答出來嗎?
考察的知識點如下:
- Java:Spring、接口和抽象類、SpringBoot計網(wǎng):HTTP、HTTPSMySQL:索引、一條SQL語句的執(zhí)行過程、隔離級別、多表查詢算法:反轉(zhuǎn)字符串中的單詞 III
MySQL
MySQL聚簇索引和非聚簇索引的區(qū)別是什么?
數(shù)據(jù)存儲:在聚簇索引中,數(shù)據(jù)行按照索引鍵值的順序存儲,也就是說,索引的葉子節(jié)點包含了實際的數(shù)據(jù)行。這意味著索引結(jié)構(gòu)本身就是數(shù)據(jù)的物理存儲結(jié)構(gòu)。非聚簇索引的葉子節(jié)點不包含完整的數(shù)據(jù)行,而是包含指向數(shù)據(jù)行的指針或主鍵值。數(shù)據(jù)行本身存儲在聚簇索引中。
索引與數(shù)據(jù)關(guān)系:由于數(shù)據(jù)與索引緊密相連,當通過聚簇索引查找數(shù)據(jù)時,可以直接從索引中獲得數(shù)據(jù)行,而不需要額外的步驟去查找數(shù)據(jù)所在的位置。當通過非聚簇索引查找數(shù)據(jù)時,首先在非聚簇索引中找到對應(yīng)的主鍵值,然后通過這個主鍵值回溯到聚簇索引中查找實際的數(shù)據(jù)行,這個過程稱為“回表”。
唯一性:聚簇索引通常是基于主鍵構(gòu)建的,因此每個表只能有一個聚簇索引,因為數(shù)據(jù)只能有一種物理排序方式。一個表可以有多個非聚簇索引,因為它們不直接影響數(shù)據(jù)的物理存儲位置。
效率:對于范圍查詢和排序查詢,聚簇索引通常更有效率,因為它避免了額外的尋址開銷。非聚簇索引在使用覆蓋索引進行查詢時效率更高,因為它不需要讀取完整的數(shù)據(jù)行。但是需要進行回表的操作,使用非聚簇索引效率比較低,因為需要進行額外的回表操作。
MySQL主鍵是聚簇索引嗎?
在MySQL的InnoDB存儲引擎中,主鍵確實是以聚簇索引的形式存儲的。
InnoDB將數(shù)據(jù)存儲在B+樹的結(jié)構(gòu)中,其中主鍵索引的B+樹就是所謂的聚簇索引。這意味著表中的數(shù)據(jù)行在物理上是按照主鍵的順序排列的,聚簇索引的葉節(jié)點包含了實際的數(shù)據(jù)行。
InnoDB 在創(chuàng)建聚簇索引時,會根據(jù)不同的場景選擇不同的列作為索引:
- 如果有主鍵,默認會使用主鍵作為聚簇索引的索引鍵;如果沒有主鍵,就選擇第一個不包含 NULL 值的唯一列作為聚簇索引的索引鍵;在上面兩個都沒有的情況下,InnoDB 將自動生成一個隱式自增 id 列作為聚簇索引的索引鍵;
一張表只能有一個聚簇索引,那為了實現(xiàn)非主鍵字段的快速搜索,就引出了二級索引(非聚簇索引/輔助索引),它也是利用了 B+ 樹的數(shù)據(jù)結(jié)構(gòu),但是二級索引的葉子節(jié)點存放的是主鍵值,不是實際數(shù)據(jù)。
執(zhí)行一條SQL請求的過程
先來一個上帝視角圖,下面就是 MySQL 執(zhí)行一條 SQL 查詢語句的流程,也從圖中可以看到 MySQL 內(nèi)部架構(gòu)里的各個功能模塊。
-
- 連接器:建立連接,管理連接、校驗用戶身份;查詢緩存:查詢語句如果命中查詢緩存則直接返回,否則繼續(xù)往下執(zhí)行。MySQL 8.0 已刪除該模塊;解析 SQL,通過解析器對 SQL 查詢語句進行詞法分析、語法分析,然后構(gòu)建語法樹,方便后續(xù)模塊讀取表名、字段、語句類型;執(zhí)行 SQL:執(zhí)行 SQL 共有三個階段:
-
-
- 預(yù)處理階段:檢查表或字段是否存在;將select *中的*符號擴展為表上的所有列。優(yōu)化階段:基于查詢成本的考慮, 選擇查詢成本最小的執(zhí)行計劃;執(zhí)行階段:根據(jù)執(zhí)行計劃執(zhí)行 SQL 查詢語句,從存儲引擎讀取記錄,返回給客戶端;
-
-
MySQL如何避免重復(fù)插入數(shù)據(jù)?
使用UNIQUE約束
- :在表的相關(guān)列上添加UNIQUE約束,確保每個值在該列中唯一。例如:
CREATE?TABLE?users?(
????id?INT?PRIMARY?KEY?AUTO_INCREMENT,
????email?VARCHAR(255)?UNIQUE,
????name?VARCHAR(255)
);
如果嘗試插入重復(fù)的email,MySQL會返回錯誤。
使用INSERT ... ON DUPLICATE KEY UPDATE:這種語句允許在插入記錄時處理重復(fù)鍵的情況。
如果插入的記錄與現(xiàn)有記錄沖突,可以選擇更新現(xiàn)有記錄:
INSERT?INTO?users?(email,?name)?
VALUES?('example@example.com',?'John?Doe')
ON?DUPLICATE?KEY?UPDATE?name?=?VALUES(name);
使用INSERT IGNORE:該語句會在插入記錄時忽略那些因重復(fù)鍵而導(dǎo)致的插入錯誤。
例如:
INSERT?IGNORE?INTO?users?(email,?name)?
VALUES?('example@example.com',?'John?Doe');
如果email已經(jīng)存在,這條插入語句將被忽略而不會返回錯誤。
選擇哪種方法取決于具體的需求:
-
-
- 如果需要保證全局唯一性,使用UNIQUE約束是最佳做法。如果需要插入和更新結(jié)合可以使用ON DUPLICATE KEY UPDATE。對于快速忽略重復(fù)插入,INSERT IGNORE是合適的選擇。
-
MySQL的隔離級別
讀未提交(read uncommitted),指一個事務(wù)還沒提交時,它做的變更就能被其他事務(wù)看到;
讀提交(read committed),指一個事務(wù)提交之后,它做的變更才能被其他事務(wù)看到;
可重復(fù)讀(repeatable read),指一個事務(wù)執(zhí)行過程中看到的數(shù)據(jù),一直跟這個事務(wù)啟動時看到的數(shù)據(jù)是一致的,
MySQL InnoDB 引擎的默認隔離級別;
串行化(serializable);會對記錄加上讀寫鎖,在多個事務(wù)對這條記錄進行讀寫操作時,如果發(fā)生了讀寫沖突的時候,后訪問的事務(wù)必須等前一個事務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行;
按隔離水平高低排序如下:
針對不同的隔離級別,并發(fā)事務(wù)時可能發(fā)生的現(xiàn)象也會不同。
也就是說:
- 在「讀未提交」隔離級別下,可能發(fā)生臟讀、不可重復(fù)讀和幻讀現(xiàn)象;在「讀提交」隔離級別下,可能發(fā)生不可重復(fù)讀和幻讀現(xiàn)象,但是不可能發(fā)生臟讀現(xiàn)象;在「可重復(fù)讀」隔離級別下,可能發(fā)生幻讀現(xiàn)象,但是不可能臟讀和不可重復(fù)讀現(xiàn)象;在「串行化」隔離級別下,臟讀、不可重復(fù)讀和幻讀現(xiàn)象都不可能會發(fā)生。
MySQL多表查詢
數(shù)據(jù)庫有以下幾種聯(lián)表查詢類型:
內(nèi)連接 (INNER JOIN)左外連接 (LEFT JOIN)右外連接 (RIGHT JOIN)全外連接 (FULL JOIN)
1. 內(nèi)連接 (INNER JOIN)內(nèi)連接返回兩個表中有匹配關(guān)系的行。
示例:
SELECT?employees.name,?departments.name
FROM?employees
INNER?JOIN?departments
ON?employees.department_id?=?departments.id;
這個查詢返回每個員工及其所在的部門名稱。
2. 左外連接 (LEFT JOIN)左外連接返回左表中的所有行,即使在右表中沒有匹配的行。未匹配的右表列會包含NULL。
示例:
SELECT?employees.name,?departments.name
FROM?employees
LEFT?JOIN?departments
ON?employees.department_id?=?departments.id;
這個查詢返回所有員工及其部門名稱,包括那些沒有分配部門的員工。
3. 右外連接 (RIGHT JOIN)右外連接返回右表中的所有行,即使左表中沒有匹配的行。未匹配的左表列會包含NULL。
示例:
SELECT?employees.name,?departments.name
FROM?employees
RIGHT?JOIN?departments
ON?employees.department_id?=?departments.id;
這個查詢返回所有部門及其員工,包括那些沒有分配員工的部門。
4. 全外連接 (FULL JOIN)全外連接返回兩個表中所有行,包括非匹配行,在MySQL中,F(xiàn)ULL JOIN 需要使用 UNION 來實現(xiàn),因為 MySQL 不直接支持 FULL JOIN。
示例:
SELECT?employees.name,?departments.name
FROM?employees
LEFT?JOIN?departments
ON?employees.department_id?=?departments.id
UNION
SELECT?employees.name,?departments.name
FROM?employees
RIGHT?JOIN?departments
ON?employees.department_id?=?departments.id;
這個查詢返回所有員工和所有部門,包括沒有匹配行的記錄。
計網(wǎng)
Http請求包含哪些部分?
請求行(Request Line):包含請求方法(如 GET, POST, PUT, DELETE 等),請求的URL(統(tǒng)一資源定位符)或資源路徑,使用的HTTP協(xié)議版本(如 HTTP/1.1 或 HTTP/2)。
請求頭(Request Headers):包含一系列鍵值對,提供客戶端和請求的附加信息,如:
Host
:指定請求的目標服務(wù)器,
User-Agent
:描述發(fā)起請求的用戶代理(瀏覽器或其他客戶端),
Accept
:列出客戶端可以接受的媒體類型,
Content-Type
:指定請求體中的數(shù)據(jù)類型,如application/json
,
Content-Length
:如果請求體存在,此頭指定其長度。
空行(Blank Line):這是一個必要的換行符,用于分隔請求頭和請求體。它標記了請求頭的結(jié)束以及請求體的開始。
請求體(Request Body):可選部分,通常在POST、PUT等請求中出現(xiàn),用于傳輸數(shù)據(jù)到服務(wù)器。如果請求方法不需要或不支持數(shù)據(jù)傳輸,則可能不存在請求體。
Https是如何交換協(xié)議的?
SSL/TLS 協(xié)議基本流程:
- 客戶端向服務(wù)器索要并驗證服務(wù)器的公鑰。雙方協(xié)商生產(chǎn)「會話秘鑰」。雙方采用「會話秘鑰」進行加密通信。
前兩步也就是 SSL/TLS 的建立過程,也就是 TLS 握手階段。TLS 的「握手階段」涉及四次通信,使用不同的密鑰交換算法,TLS 握手流程也會不一樣的,現(xiàn)在常用的密鑰交換算法有兩種:RSA 算法(opens new window)和 ECDHE 算法(opens new window)?;?RSA 算法的 TLS 握手過程比較容易理解,所以這里先用這個給大家展示 TLS 握手過程,如下圖:
TLS 協(xié)議建立的詳細流程:
1. ClientHello
首先,由客戶端向服務(wù)器發(fā)起加密通信請求,也就是 ClientHello 請求。在這一步,客戶端主要向服務(wù)器發(fā)送以下信息:(1)客戶端支持的 TLS 協(xié)議版本,如 TLS 1.2 版本。(2)客戶端生產(chǎn)的隨機數(shù)(Client Random),后面用于生成「會話秘鑰」條件之一。(3)客戶端支持的密碼套件列表,如 RSA 加密算法。
2. SeverHello
服務(wù)器收到客戶端請求后,向客戶端發(fā)出響應(yīng),也就是 SeverHello。服務(wù)器回應(yīng)的內(nèi)容有如下內(nèi)容:(1)確認 TLS 協(xié)議版本,如果瀏覽器不支持,則關(guān)閉加密通信。(2)服務(wù)器生產(chǎn)的隨機數(shù)(Server Random),也是后面用于生產(chǎn)「會話秘鑰」條件之一。(3)確認的密碼套件列表,如 RSA 加密算法。(4)服務(wù)器的數(shù)字證書。
3.客戶端回應(yīng)
客戶端收到服務(wù)器的回應(yīng)之后,首先通過瀏覽器或者操作系統(tǒng)中的 CA 公鑰,確認服務(wù)器的數(shù)字證書的真實性。
如果證書沒有問題,客戶端會從數(shù)字證書中取出服務(wù)器的公鑰,然后使用它加密報文,向服務(wù)器發(fā)送如下信息:(1)一個隨機數(shù)(pre-master key)。該隨機數(shù)會被服務(wù)器公鑰加密。(2)加密通信算法改變通知,表示隨后的信息都將用「會話秘鑰」加密通信。(3)客戶端握手結(jié)束通知,表示客戶端的握手階段已經(jīng)結(jié)束。這一項同時把之前所有內(nèi)容的發(fā)生的數(shù)據(jù)做個摘要,用來供服務(wù)端校驗。
上面第一項的隨機數(shù)是整個握手階段的第三個隨機數(shù),會發(fā)給服務(wù)端,所以這個隨機數(shù)客戶端和服務(wù)端都是一樣的。
服務(wù)器和客戶端有了這三個隨機數(shù)(Client Random、Server Random、pre-master key),接著就用雙方協(xié)商的加密算法,各自生成本次通信的「會話秘鑰」。
4. 服務(wù)器的最后回應(yīng)服務(wù)器收到客戶端的第三個隨機數(shù)(pre-master key)之后,通過協(xié)商的加密算法,計算出本次通信的「會話秘鑰」。
然后,向客戶端發(fā)送最后的信息:(1)加密通信算法改變通知,表示隨后的信息都將用「會話秘鑰」加密通信。(2)服務(wù)器握手結(jié)束通知,表示服務(wù)器的握手階段已經(jīng)結(jié)束。這一項同時把之前所有內(nèi)容的發(fā)生的數(shù)據(jù)做個摘要,用來供客戶端校驗。
至此,整個 TLS 的握手階段全部結(jié)束。接下來,客戶端與服務(wù)器進入加密通信,就完全是使用普通的 HTTP 協(xié)議,只不過用「會話秘鑰」加密內(nèi)容。
Http返回狀態(tài)301 302分別是什么?
3xx 類狀態(tài)碼表示客戶端請求的資源發(fā)生了變動,需要客戶端用新的 URL 重新發(fā)送請求獲取資源,也就是重定向。
「301 Moved Permanently」表示永久重定向,說明請求的資源已經(jīng)不存在了,需改用新的 URL 再次訪問。
「302 Found」表示臨時重定向,說明請求的資源還在,但暫時需要用另一個 URL 來訪問。
301 和 302 都會在響應(yīng)頭里使用字段 Location,指明后續(xù)要跳轉(zhuǎn)的 URL,瀏覽器會自動重定向新的 URL。
Java
簡述spring IoC和AOP
IoC(控制反轉(zhuǎn)) 是一種設(shè)計思想,而不是一個具體的技術(shù)實現(xiàn)。IoC 的思想就是將對象之間的相互依賴關(guān)系交給 IoC 容器來管理,并由 IoC 容器完成對象的注入。這樣可以很大程度上簡化應(yīng)用的開發(fā),把應(yīng)用從復(fù)雜的依賴關(guān)系中解放出來。IoC 容器就像是一個工廠一樣,當我們需要創(chuàng)建一個對象的時候,只需要配置好配置文件/注解即可,完全不用考慮對象是如何被創(chuàng)建出來的。
AOP(面向切面編程)能夠?qū)⒛切┡c業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯封裝起來,以減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度。Spring AOP 就是基于動態(tài)代理的,如果要代理的對象,實現(xiàn)了某個接口,那么 Spring AOP 會使用 JDK Proxy,去創(chuàng)建代理對象,而對于沒有實現(xiàn)接口的對象,就無法使用 JDK Proxy 去進行代理了,這時候 Spring AOP 會使用 Cglib 生成一個被代理對象的子類來作為代理。
在 Spring 框架中,IOC 和 AOP 結(jié)合使用,可以更好地實現(xiàn)代碼的模塊化和分層管理。例如,通過 IOC 容器管理對象的依賴關(guān)系,然后通過 AOP 將橫切關(guān)注點統(tǒng)一切入到需要的業(yè)務(wù)邏輯中。
例如:使用 IOC 容器管理 Service 層和 DAO 層的依賴關(guān)系,然后通過 AOP 在 Service 層實現(xiàn)事務(wù)管理、日志記錄等橫切功能,使得業(yè)務(wù)邏輯更加清晰和可維護。
Java抽象類和接口的區(qū)別是什么?
- 一個子類只能繼承一個抽象類, 但能實現(xiàn)多個接口抽象類可以有構(gòu)造方法, 接口沒有構(gòu)造方法抽象類可以有普通成員變量, 接口沒有普通成員變量抽象類和接口都可有靜態(tài)成員變量, 抽象類中靜態(tài)成員變量訪問類型任意,接口只能public static final(默認)抽象類可以沒有抽象方法, 抽象類可以有普通方法;接口在JDK8之前都是抽象方法,在JDK8可以有default方法,在JDK9中允許有私有普通方法抽象類可以有靜態(tài)方法;接口在JDK8之前不能有靜態(tài)方法,在JDK8中可以有靜態(tài)方法,且只能被接口類直接調(diào)用(不能被實現(xiàn)類的對象調(diào)用)抽象類中的方法可以是public、protected; 接口方法在JDK8之前只有public abstract,在JDK8可以有default方法,在JDK9中允許有private方法
抽象類可以被實例化嗎
在Java中,抽象類本身不能被實例化。
這意味著不能使用new
關(guān)鍵字直接創(chuàng)建一個抽象類的對象。抽象類的存在主要是為了被繼承,它通常包含一個或多個抽象方法(由abstract
關(guān)鍵字修飾且無方法體的方法),這些方法需要在子類中被實現(xiàn)。
抽象類可以有構(gòu)造器,這些構(gòu)造器在子類實例化時會被調(diào)用,以便進行必要的初始化工作。然而,這個過程并不是直接實例化抽象類,而是創(chuàng)建了子類的實例,間接地使用了抽象類的構(gòu)造器。
例如:
public?abstract?class?AbstractClass?{
????public?AbstractClass()?{
????????//?構(gòu)造器代碼
????}
????
????public?abstract?void?abstractMethod();
}
public?class?ConcreteClass?extends?AbstractClass?{
????public?ConcreteClass()?{
????????super();?//?調(diào)用抽象類的構(gòu)造器
????}
????
????@Override
????public?void?abstractMethod()?{
????????//?實現(xiàn)抽象方法
????}
}
//?下面的代碼可以運行
ConcreteClass?obj?=?new?ConcreteClass();
在這個例子中,ConcreteClass
繼承了AbstractClass
并實現(xiàn)了抽象方法abstractMethod()
。當我們創(chuàng)建ConcreteClass
的實例時,AbstractClass
的構(gòu)造器被調(diào)用,但這并不意味著AbstractClass
被實例化;實際上,我們創(chuàng)建的是ConcreteClass
的一個對象。
簡而言之,抽象類不能直接實例化,但通過繼承抽象類并實現(xiàn)所有抽象方法的子類是可以被實例化的。
接口可以包含構(gòu)造函數(shù)嗎?
在接口中,不可以有構(gòu)造方法,在接口里寫入構(gòu)造方法時,編譯器提示:Interfaces cannot have constructors,因為接口不會有自己的實例的,所以不需要有構(gòu)造函數(shù)。
為什么呢?構(gòu)造函數(shù)就是初始化class的屬性或者方法,在new的一瞬間自動調(diào)用,那么問題來了Java的接口,都不能new 那么要構(gòu)造函數(shù)干嘛呢?根本就沒法調(diào)用
SpringBoot的項目結(jié)構(gòu)是怎么樣的?
一個正常的企業(yè)項目里一種通用的項目結(jié)構(gòu)和代碼層級劃分的指導(dǎo)意見。按這《阿里巴巴Java開發(fā)手冊》時本書上說的,一般分為如下幾層:
開放接口層:可直接封裝 Service 接口暴露成 RPC 接口;通過 Web 封裝成 http 接口;網(wǎng)關(guān)控制層等。終端顯示層:各個端的模板渲染并執(zhí)行顯示的層。當前主要是 velocity 渲染,JS 渲染,JSP 渲染,移動端展示等。Web 層:主要是對訪問控制進行轉(zhuǎn)發(fā),各類基本參數(shù)校驗,或者不復(fù)用的業(yè)務(wù)簡單處理等。Service 層:相對具體的業(yè)務(wù)邏輯服務(wù)層。Manager 層:通用業(yè)務(wù)處理層,它有如下特征
1)對第三方平臺封裝的層,預(yù)處理返回結(jié)果及轉(zhuǎn)化異常信息,適配上層接口。
2)對 Service 層通用能力的下沉,如緩存方案、中間件通用處理。
3)與 DAO 層交互,對多個 DAO 的組合復(fù)用。
DAO 層:數(shù)據(jù)訪問層,與底層 MySQL、Oracle、Hbase、OceanBase 等進行數(shù)據(jù)交互。第三方服務(wù):包括其它部門 RPC 服務(wù)接口,基礎(chǔ)平臺,其它公司的 HTTP 接口,如淘寶開放平臺、支付寶付款服務(wù)、高德地圖服務(wù)等。外部接口:外部(應(yīng)用)數(shù)據(jù)存儲服務(wù)提供的接口,多見于數(shù)據(jù)遷移場景中。
如果從一個用戶訪問一個網(wǎng)站的情況來看,對應(yīng)著上面的項目代碼結(jié)構(gòu)來分析,可以貫穿整個代碼分層:
對應(yīng)代碼目錄的流轉(zhuǎn)邏輯就是:
所以,以后每當我們拿到一個新的項目到手時,只要按照這個思路去看別人項目的代碼,應(yīng)該基本都是能理得順的。
算法
反轉(zhuǎn)字符串中的單詞 III
開辟一個新字符串。然后從頭到尾遍歷原字符串,直到找到空格為止,此時找到了一個單詞,并能得到單詞的起止位置。隨后,根據(jù)單詞的起止位置,可以將該單詞逆序放到新字符串當中。如此循環(huán)多次,直到遍歷完原字符串,就能得到翻轉(zhuǎn)后的結(jié)果。
class?Solution?{
????public?String?reverseWords(String?s)?{
????????StringBuffer?ret?=?new?StringBuffer();
????????int?length?=?s.length();
????????int?i?=?0;
????????while?(i?<?length)?{
????????????int?start?=?i;
????????????while?(i?<?length?&&?s.charAt(i)?!=?'?')?{
????????????????i++;
????????????}
????????????for?(int?p?=?start;?p?<?i;?p++)?{
????????????????ret.append(s.charAt(start?+?i?-?1?-?p));
????????????}
????????????while?(i?<?length?&&?s.charAt(i)?==?'?')?{
????????????????i++;
????????????????ret.append('?');
????????????}
????????}
????????return?ret.toString();
????}
}
- 時間復(fù)雜度:O(N),其中 N 為字符串的長度。原字符串中的每個字符都會在 O(1) 的時間內(nèi)放入新字符串中??臻g復(fù)雜度:O(N)。我們開辟了與原字符串等大的空間。