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