偷偷盤點(diǎn)一下京東研發(fā)崗薪資
共 10792字,需瀏覽 22分鐘
·
2024-05-23 14:04
大家好,我是二哥呀。
京東這幾天的熱度真的非常高,據(jù)說零售部門開始嚴(yán)查考勤,并且調(diào)整了午休時(shí)間,整整縮短了一個(gè)小時(shí),從原來的 11:30-13:30 調(diào)整為 12:00-13:00。
更關(guān)鍵的是,晚上 6 點(diǎn)下班的員工要掂量掂量自己的工作飽和度。
我只能說,東哥這是不打算和兄弟們做兄弟了嗎?(??)
山雨欲來風(fēng)滿樓,這種舉措也就意味著新的風(fēng)暴即將來襲,這段時(shí)間京東的小伙伴們就要注意了,簡歷該更新要記得更新,八股、算法、項(xiàng)目該復(fù)盤的注意復(fù)盤,為下一步做好充足的準(zhǔn)備。
另外,我也統(tǒng)計(jì)了一波京東 24 屆的校招薪資,主要是后端開發(fā)、前端、測開和產(chǎn)品經(jīng)理,25 屆打算沖京東的小伙伴可以拿來作為一個(gè)參考。
能看得出,Java 后端開發(fā)主要集中在 23k*16 這個(gè)段位上,包括碩士 211、碩士 985、211 本。只能說,大廠香是香,卷也是真的卷~
這次我們就以《Java 面試指南-京東面經(jīng)》同學(xué) 2 的后端面試為例, 來看看京東的面試官都喜歡問哪些八股,好背的滾瓜爛熟,了然于胸~
題目不少,火箭造的飛起。主要還是圍繞著二哥強(qiáng)調(diào)的 Java 后端四大件為主,所以大家在準(zhǔn)備的時(shí)候一定要有的放矢,知道哪些是重點(diǎn)。
1、二哥的 Linux 速查備忘手冊.pdf 下載 2、三分惡面渣逆襲在線版:https://javabetter.cn/sidebar/sanfene/nixi.html
京東面經(jīng)(狠狠拷打)
如何保證redis和數(shù)據(jù)庫一致性?(答:延時(shí)雙刪)
在技術(shù)派實(shí)戰(zhàn)項(xiàng)目中,我采用的是先寫 MySQL,再刪除 Redis 的方式來保證緩存和數(shù)據(jù)庫的數(shù)據(jù)一致性。
對于第一次查詢,請求 B 查詢到的緩存數(shù)據(jù)是 10,但 MySQL 被請求 A 更新為了 11,此時(shí)數(shù)據(jù)庫和緩存不一致。
但也只存在這一次不一致的情況,對于不是強(qiáng)一致性的業(yè)務(wù),可以容忍。
當(dāng)請求 B 第二次查詢時(shí),因?yàn)檎埱?A 更新完數(shù)據(jù)庫把緩存刪除了,所以請求 B 這次不會(huì)命中緩存,會(huì)重新查一次 MySQL,然后回寫到 Redis。
緩存和數(shù)據(jù)庫又一致了。
那假如對一致性要求很高,該怎么辦呢?
延時(shí)雙刪防止臟數(shù)據(jù)
簡單說,就是在第一次刪除緩存之后,過一段時(shí)間之后,再次刪除緩存。
主要針對緩存不存在,但寫入了臟數(shù)據(jù)的情況。在先刪緩存,再寫數(shù)據(jù)庫的更新策略下發(fā)生的比較多。
這種方式的延時(shí)時(shí)間需要仔細(xì)考量和測試。
說說mq原理,怎么保證消息接受順序?
消息隊(duì)列(Message Queue, MQ)是一種非常重要的中間件技術(shù),廣泛應(yīng)用于分布式系統(tǒng)中,以提高系統(tǒng)的可用性、解耦能力和異步通信效率。
生產(chǎn)者將消息放入隊(duì)列,消費(fèi)者從隊(duì)列中取出消息,這樣一來,生產(chǎn)者和消費(fèi)者之間就不需要直接通信,生產(chǎn)者只管生產(chǎn)消息,消費(fèi)者只管消費(fèi)消息,這樣就實(shí)現(xiàn)了解耦。
與此同時(shí),系統(tǒng)可以將那些耗時(shí)的任務(wù)放在消息隊(duì)列中異步處理,從而快速響應(yīng)用戶的請求。
RocketMQ 實(shí)現(xiàn)順序消息的關(guān)鍵在于保證消息生產(chǎn)和消費(fèi)過程中嚴(yán)格的順序控制,即確保同一業(yè)務(wù)的消息按順序發(fā)送到同一個(gè)隊(duì)列中,并由同一個(gè)消費(fèi)者線程按順序消費(fèi)。
局部順序消息如何實(shí)現(xiàn)?
局部順序消息保證在某個(gè)邏輯分區(qū)或業(yè)務(wù)邏輯下的消息順序,例如同一個(gè)訂單或用戶的消息按順序消費(fèi),而不同訂單或用戶之間的順序不做保證。
全局順序消息如何實(shí)現(xiàn)?
全局順序消息保證消息在整個(gè)系統(tǒng)范圍內(nèi)的嚴(yán)格順序,即消息按照生產(chǎn)的順序被消費(fèi)。
可以將所有消息發(fā)送到一個(gè)單獨(dú)的隊(duì)列中,確保所有消息按生產(chǎn)順序發(fā)送和消費(fèi)。
項(xiàng)目壓測了嘛?(Jmeter)
我會(huì)使用 Jmeter 對項(xiàng)目進(jìn)行壓測,通過合理配置線程組、HTTP 請求和監(jiān)聽器,可以模擬真實(shí)的用戶負(fù)載,并分析項(xiàng)目在高負(fù)載下的表現(xiàn)。
arraylist,linkedlist,hashset區(qū)別和使用場景,線程安全?
多數(shù)情況下,ArrayList 更利于查找,LinkedList 更利于增刪
①、由于 ArrayList 是基于數(shù)組實(shí)現(xiàn)的,所以 get(int index) 可以直接通過數(shù)組下標(biāo)獲取,時(shí)間復(fù)雜度是 O(1);LinkedList 是基于鏈表實(shí)現(xiàn)的,get(int index) 需要遍歷鏈表,時(shí)間復(fù)雜度是 O(n)。
當(dāng)然,get(E element) 這種查找,兩種集合都需要遍歷通過 equals 比較獲取元素,所以時(shí)間復(fù)雜度都是 O(n)。
②、ArrayList 如果增刪的是數(shù)組的尾部,直接插入或者刪除就可以了,時(shí)間復(fù)雜度是 O(1);如果 add 的時(shí)候涉及到擴(kuò)容,時(shí)間復(fù)雜度會(huì)提升到 O(n)。
但如果插入的是中間的位置,就需要把插入位置后的元素向前或者向后移動(dòng),甚至還有可能觸發(fā)擴(kuò)容,效率就會(huì)低很多,O(n)。
LinkedList 因?yàn)槭擎湵斫Y(jié)構(gòu),插入和刪除只需要改變前置節(jié)點(diǎn)、后置節(jié)點(diǎn)和插入節(jié)點(diǎn)的引用就行了,不需要移動(dòng)元素。
如果是在鏈表的頭部插入或者刪除,時(shí)間復(fù)雜度是 O(1);如果是在鏈表的中間插入或者刪除,時(shí)間復(fù)雜度是 O(n),因?yàn)樾枰闅v鏈表找到插入位置;如果是在鏈表的尾部插入或者刪除,時(shí)間復(fù)雜度是 O(1)。
注意,這里有個(gè)陷阱,LinkedList 更利于增刪不是體現(xiàn)在時(shí)間復(fù)雜度上,因?yàn)槎咴鰟h的時(shí)間復(fù)雜度都是 O(n),都需要遍歷列表;而是體現(xiàn)在增刪的效率上,因?yàn)?LinkedList 的增刪只需要改變引用,而 ArrayList 的增刪可能需要移動(dòng)元素。
HashSet 其實(shí)是由 HashMap 實(shí)現(xiàn)的,只不過值由一個(gè)固定的 Object 對象填充,而鍵用于操作。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
// ……
}
實(shí)際開發(fā)中,HashSet 并不常用,比如,如果我們需要按照順序存儲(chǔ)一組元素,那么 ArrayList 和 LinkedList 可能更適合;如果我們需要存儲(chǔ)鍵值對并根據(jù)鍵進(jìn)行查找,那么 HashMap 可能更適合。
HashSet 主要用于去重,比如,我們需要統(tǒng)計(jì)一篇文章中有多少個(gè)不重復(fù)的單詞,就可以使用 HashSet 來實(shí)現(xiàn)。
// 創(chuàng)建一個(gè) HashSet 對象
HashSet<String> set = new HashSet<>();
// 添加元素
set.add("沉默");
set.add("王二");
set.add("陳清揚(yáng)");
set.add("沉默");
// 輸出 HashSet 的元素個(gè)數(shù)
System.out.println("HashSet size: " + set.size()); // output: 3
// 遍歷 HashSet
for (String s : set) {
System.out.println(s);
}
HashSet 會(huì)自動(dòng)去重,因?yàn)樗怯?HashMap 實(shí)現(xiàn)的,HashMap 的鍵是唯一的(哈希值),相同鍵的值會(huì)覆蓋掉原來的值,于是第二次 set.add("沉默") 的時(shí)候就覆蓋了第一次的 set.add("沉默")。
hashset為什么是隨機(jī)的?其他兩個(gè)是按存入順序的?
-
ArrayList 是基于動(dòng)態(tài)數(shù)組實(shí)現(xiàn)的,HashSet 是基于 HashMap 實(shí)現(xiàn)的。 -
LinkedList 是基于鏈表實(shí)現(xiàn)的,本身也可以拿來作為隊(duì)列。 -
ArrayList 允許重復(fù)元素和 null 值,可以有多個(gè)相同的元素;HashSet 保證每個(gè)元素唯一,不允許重復(fù)元素,基于元素的 hashCode 和 equals 方法來確定元素的唯一性。 -
ArrayList 保持元素的插入順序,可以通過索引訪問元素;HashSet 不保證元素的順序,元素的存儲(chǔ)順序依賴于哈希算法,并且可能隨著元素的添加或刪除而改變。
說說類加載過程(5步)
類加載過程有:載入、驗(yàn)證、準(zhǔn)備、解析、初始化。這 5 個(gè)階段一般是順序發(fā)生的,但在動(dòng)態(tài)綁定的情況下,解析階段會(huì)發(fā)生在初始化階段之后。
載入過程中,JVM 需要做三件事情:
-
1)通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。 -
2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。 -
3)在內(nèi)存中生成一個(gè)代表這個(gè)類的 java.lang.Class對象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。
載入階段結(jié)束后,JVM 外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所設(shè)定的格式存儲(chǔ)在方法區(qū)(邏輯概念)中了,方法區(qū)中的數(shù)據(jù)存儲(chǔ)格式完全由虛擬機(jī)自行實(shí)現(xiàn)。
JVM 會(huì)在驗(yàn)證階段對二進(jìn)制字節(jié)流進(jìn)行校驗(yàn),只有符合 JVM 字節(jié)碼規(guī)范的才能被 JVM 正確執(zhí)行。
JVM 會(huì)在準(zhǔn)備階段對類變量(也稱為靜態(tài)變量,static 關(guān)鍵字修飾的變量)分配內(nèi)存并初始化,初始化為數(shù)據(jù)類型的默認(rèn)值,如 0、0L、null、false 等。
解析階段是虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程。解析動(dòng)作主要針對類或接口、字段、類方法、接口方法、成員方法等。
初始化階段是類加載過程的最后一步。在準(zhǔn)備階段,類變量已經(jīng)被賦過默認(rèn)初始值了,而在初始化階段,類變量將被賦值為代碼期望賦的值。
換句話說,初始化階段是執(zhí)行類的構(gòu)造方法(javap 中看到的 <clinit>() 方法)的過程。
雙親委派模型好處?
可以為 Java 應(yīng)用程序的運(yùn)行提供一致性和安全性的保障。
①、保證 Java 核心類庫的類型安全
如果自定義類加載器優(yōu)先加載一個(gè)類,比如說自定義的 Object,那在 Java 運(yùn)行時(shí)環(huán)境中就存在多個(gè)版本的 java.lang.Object,雙親委派模型確保了 Java 核心類庫的類加載工作由啟動(dòng)類加載器統(tǒng)一完成,從而保證了 Java 應(yīng)用程序都是使用的同一份核心類庫。
②、避免類的重復(fù)加載
在雙親委派模型中,類加載器會(huì)先委托給父加載器嘗試加載類,這樣同一個(gè)類不會(huì)被加載多次。如果沒有這種模型,可能會(huì)導(dǎo)致同一個(gè)類被不同的類加載器重復(fù)加載到內(nèi)存中,造成浪費(fèi)和沖突。
new子類的時(shí)候,子類和父類靜態(tài)代碼塊,構(gòu)造方法的執(zhí)行順序
在 Java 中,當(dāng)創(chuàng)建一個(gè)子類對象時(shí),子類和父類的靜態(tài)代碼塊、構(gòu)造方法的執(zhí)行順序遵循一定的規(guī)則。這些規(guī)則主要包括以下幾個(gè)步驟:
-
首先執(zhí)行父類的靜態(tài)代碼塊(僅在類第一次加載時(shí)執(zhí)行)。 -
接著執(zhí)行子類的靜態(tài)代碼塊(僅在類第一次加載時(shí)執(zhí)行)。 -
再執(zhí)行父類的構(gòu)造方法。 -
最后執(zhí)行子類的構(gòu)造方法。
下面是一個(gè)詳細(xì)的代碼示例:
class Parent {
// 父類靜態(tài)代碼塊
static {
System.out.println("父類靜態(tài)代碼塊");
}
// 父類構(gòu)造方法
public Parent() {
System.out.println("父類構(gòu)造方法");
}
}
class Child extends Parent {
// 子類靜態(tài)代碼塊
static {
System.out.println("子類靜態(tài)代碼塊");
}
// 子類構(gòu)造方法
public Child() {
System.out.println("子類構(gòu)造方法");
}
}
public class Main {
public static void main(String[] args) {
new Child();
}
}
執(zhí)行上述代碼時(shí),輸出結(jié)果如下:
父類靜態(tài)代碼塊
子類靜態(tài)代碼塊
父類構(gòu)造方法
子類構(gòu)造方法
-
靜態(tài)代碼塊:在類加載時(shí)執(zhí)行,僅執(zhí)行一次,按父類-子類的順序執(zhí)行。 -
構(gòu)造方法:在每次創(chuàng)建對象時(shí)執(zhí)行,按父類-子類的順序執(zhí)行,先初始化塊后構(gòu)造方法。
synchronized和lock區(qū)別原理
synchronized 是一個(gè)關(guān)鍵字,而 Lock 屬于一個(gè)接口,其實(shí)現(xiàn)類主要有 ReentrantLock、ReentrantReadWriteLock。
①、使用方式不同
synchronized 可以直接在方法上加鎖,也可以在代碼塊上加鎖(無需手動(dòng)釋放鎖,鎖會(huì)自動(dòng)釋放),而 ReentrantLock 必須手動(dòng)聲明來加鎖和釋放鎖。
// synchronized 修飾方法
public synchronized void method() {
// 業(yè)務(wù)代碼
}
// synchronized 修飾代碼塊
synchronized (this) {
// 業(yè)務(wù)代碼
}
// ReentrantLock 加鎖
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 業(yè)務(wù)代碼
} finally {
lock.unlock();
}
隨著 JDK 版本的升級,synchronized 的性能已經(jīng)可以媲美 ReentrantLock 了,加入了偏向鎖、輕量級鎖和重量級鎖的自適應(yīng)優(yōu)化等,所以可以大膽地用。
②、功能特點(diǎn)不同
如果需要更細(xì)粒度的控制(如可中斷的鎖操作、嘗試非阻塞獲取鎖、超時(shí)獲取鎖或者使用公平鎖等),可以使用 Lock。
-
ReentrantLock 提供了一種能夠中斷等待鎖的線程的機(jī)制,通過 lock.lockInterruptibly()來實(shí)現(xiàn)這個(gè)機(jī)制。 -
ReentrantLock 可以指定是公平鎖還是非公平鎖。 -
ReentrantReadWriteLock 讀寫鎖,讀鎖是共享鎖,寫鎖是獨(dú)占鎖,讀鎖可以同時(shí)被多個(gè)線程持有,寫鎖只能被一個(gè)線程持有。這種鎖的設(shè)計(jì)可以提高性能,特別是在讀操作的數(shù)量遠(yuǎn)遠(yuǎn)超過寫操作的情況下。
Lock 還提供了newCondition()方法來創(chuàng)建等待通知條件Condition,比 synchronized 與 wait()、 notify()/notifyAll()方法的組合更強(qiáng)大。
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
tcp三次握手四次揮手工作在哪一層?
三次握手和四次揮手都是工作在傳輸層。傳輸層(Transport Layer)是 OSI 模型的第四層,負(fù)責(zé)提供端到端的通信服務(wù),包括數(shù)據(jù)傳輸?shù)慕?、維護(hù)和終止。
TCP 作為一種面向連接的協(xié)議,通過三次握手建立連接,通過四次揮手終止連接,確保數(shù)據(jù)傳輸?shù)目煽啃院屯暾浴?/p>
用過序列化和反序列化嗎?
序列化(Serialization)是指將對象轉(zhuǎn)換為字節(jié)流的過程,以便能夠?qū)⒃搶ο蟊4娴轿募?、?shù)據(jù)庫,或者進(jìn)行網(wǎng)絡(luò)傳輸。
反序列化(Deserialization)就是將字節(jié)流轉(zhuǎn)換回對象的過程,以便構(gòu)建原始對象。
Serializable 接口有什么用?
Serializable接口用于標(biāo)記一個(gè)類可以被序列化。
public class Person implements Serializable {
private String name;
private int age;
// 省略 getter 和 setter 方法
}
serialVersionUID 有什么用?
serialVersionUID 是 Java 序列化機(jī)制中用于標(biāo)識類版本的唯一標(biāo)識符。它的作用是確保在序列化和反序列化過程中,類的版本是兼容的。
import java.io.Serializable;
public class MyClass implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
// getters and setters
}
serialVersionUID 被設(shè)置為 1L 是一種比較省事的做法,也可以使用 Intellij IDEA 進(jìn)行自動(dòng)生成。
但只要 serialVersionUID 在序列化和反序列化過程中保持一致,就不會(huì)出現(xiàn)問題。
如果不顯式聲明 serialVersionUID,Java 運(yùn)行時(shí)會(huì)根據(jù)類的詳細(xì)信息自動(dòng)生成一個(gè) serialVersionUID。那么當(dāng)類的結(jié)構(gòu)發(fā)生變化時(shí),自動(dòng)生成的 serialVersionUID 就會(huì)發(fā)生變化,導(dǎo)致反序列化失敗。
內(nèi)容來源
-
星球嘉賓三分惡的面渣逆襲:https://javabetter.cn/sidebar/sanfene/nixi.html -
二哥的 Java 進(jìn)階之路(GitHub 已有 12000+star):https://javabetter.cn
ending
一個(gè)人可以走得很快,但一群人才能走得更遠(yuǎn)。二哥的編程星球已經(jīng)有 5300 多名球友加入了,如果你也需要一個(gè)良好的學(xué)習(xí)環(huán)境,戳鏈接 ?? 加入我們吧。這是一個(gè)編程學(xué)習(xí)指南 + Java 項(xiàng)目實(shí)戰(zhàn) + LeetCode 刷題的私密圈子,你可以閱讀星球?qū)?、向二哥提問、幫你制定學(xué)習(xí)計(jì)劃、和球友一起打卡成長。
兩個(gè)置頂帖「球友必看」和「知識圖譜」里已經(jīng)沉淀了非常多優(yōu)質(zhì)的學(xué)習(xí)資源,相信能幫助你走的更快、更穩(wěn)、更遠(yuǎn)。
歡迎點(diǎn)擊左下角閱讀原文了解二哥的編程星球,這可能是你學(xué)習(xí)求職路上最有含金量的一次點(diǎn)擊。
最后,把二哥的座右銘送給大家:沒有什么使我停留——除了目的,縱然岸旁有玫瑰、有綠蔭、有寧靜的港灣,我是不系之舟。共勉 ??。
