過于簡單的就不說了胯杭,比如“equals()
和==
”有什么區(qū)別,相信大家都會受啥。
Java與JVM知識
JVM的內(nèi)存結(jié)構(gòu)
- 程序計數(shù)器(Program Counter Register):是線程私有的一塊內(nèi)存做个,記錄當前線程執(zhí)行的字節(jié)碼指令位置鸽心。
- Java虛擬機棧(Java Virtual Machine Stacks):也是線程私有的,用于存儲局部變量表居暖、操作數(shù)棧顽频、動態(tài)鏈接、方法出口等信息太闺。每個方法的調(diào)用都會在Java虛擬機棧中生成一個棧幀糯景,用于存儲方法的局部變量等信息。
- 本地方法棧(Native Method Stack):也是線程私有的省骂,為Java虛擬機調(diào)用Native方法服務(wù)蟀淮。
- Java堆(Java Heap):是所有線程共享的一塊內(nèi)存區(qū)域,用于存儲對象實例钞澳。Java堆還可以分為新生代和老年代怠惶,分別用于存儲不同生命周期的對象實例。
- 方法區(qū)(Method Area):也是所有線程共享的轧粟,用于存儲類信息策治、常量池、靜態(tài)變量兰吟、即時編譯器編譯后的代碼等數(shù)據(jù)通惫。
對于線程私有的內(nèi)存結(jié)構(gòu)(程序計數(shù)器、Java虛擬機棧揽祥、本地方法棧)讽膏,不同線程之間互不干擾,是各自獨立的拄丰。而對于所有線程共享的內(nèi)存結(jié)構(gòu)(Java堆府树、方法區(qū)),需要進行線程同步料按,保證線程安全奄侠。
JVM的內(nèi)存一般分為五個區(qū)域。
區(qū)域 | 線程擁有 | 作用 | 大小 |
---|---|---|---|
程序計數(shù)器 | 線程私有 | 記錄下一條指令的地址 | 很小 |
虛擬機棧 | 線程私有 | 存放方法的調(diào)用 | 一般 |
本地方法棧 | 線程私有 | C/C++寫的native方法 | 一般 |
虛擬機堆 | 共享 | 創(chuàng)建的對象等數(shù)據(jù) | 大 |
方法區(qū) | 共享 | 常量载矿、靜態(tài)變量等 | 一般 |
注意:
- 程序計數(shù)器很小垄潮,不會溢出內(nèi)存(Out Of Memory)。
- 虛擬機堆是垃圾回收的重點照顧區(qū)域闷盔。
- 諸如int等8種基本變量不在堆里弯洗,而是在棧里;而常量逢勾、靜態(tài)變量也不在堆里牡整,在方法區(qū)。
JVM虛擬機堆的內(nèi)存結(jié)構(gòu)
JVM虛擬機堆主要包括老生代溺拱、新生代逃贝、元空間三部分谣辞。其中新生代又包括了 Eden、S0沐扳、S1三個區(qū)域泥从。
(編者注:Eden 是英文里“伊甸園”的含義,大家可以理解為產(chǎn)房沪摄。Survivor 0躯嫉、Survivor 1 兩個區(qū)域意思是“幸存者0”“幸存者1”,簡稱為 S0卓起、S1)
堆的唯一目的就是存放對象實例和敬,幾乎所有的對象實例以及數(shù)組都在這里分配內(nèi)存。
大部分情況戏阅,對象都會首先在 Eden 區(qū)域分配昼弟,在一次新生代垃圾回收后,如果對象還存活奕筐,則會進入 S0 或者 S1舱痘,并且對象的年齡還會加 1(Eden 區(qū)->Survivor 區(qū)后對象的初始年齡變?yōu)?1),當它的年齡增加到一定程度(默認為 15 歲)离赫,就會被晉升到老年代中芭逝。對象晉升到老年代的年齡閾值,可以通過參數(shù) -XX:MaxTenuringThreshold
來設(shè)置渊胸。
一個對象誕生于 Eden 區(qū)旬盯。當 Eden 區(qū)人滿為患時,JVM 使用 minor gc翎猛,把 Eden 區(qū)已經(jīng)死掉的對象清理掉胖翰。然后把 S0 中的對象、Eden 區(qū)幸存的對象切厘,移動到 S1 區(qū)萨咳。(當然,也可以是從 S1 移動到 S0 區(qū))這個時候所有的對象“年齡”都 +1疫稿。當年齡達到一定程度時培他,就會被移動到老生區(qū)。
死亡對象判斷方法
引用計數(shù)法
如果別的地方有對這個對象的引用遗座,那么引用計數(shù) +1舀凛。如果沒有別的地方引用了,就判斷這個對象死亡途蒋。
這個算法不是很好猛遍,因為如果兩個對象相互引用,而不被其它的地方引用,那么他們并不會被判斷死亡螃壤。
可達性分析法
把對象之間的引用關(guān)系看做一張圖(或一棵樹),從垃圾回收根節(jié)點(GC Roots)出發(fā)筋帖,能夠達到的對象奸晴,就是還存活的。無法達到的對象日麸,就是已經(jīng)死亡的寄啼。
Java的垃圾回收是怎樣的機制
Java中的垃圾回收是自動化的,它會在程序運行時自行管理內(nèi)存的分配和回收代箭。Java的垃圾回收機制會定期掃描堆內(nèi)存中的對象墩划,標記出無法回收的對象并清除掉不再被引用的對象以釋放內(nèi)存。Java采用了可達性分析算法來判斷對象是否可以回收嗡综。如果一個對象再也無法通過任何引用達到乙帮,那么它就可以被回收。此外极景,Java還提供了finalize()
方法讓開發(fā)人員在對象被回收之前做一些清理工作察净。
因為垃圾回收機制和內(nèi)存管理是由虛擬機完成的,所以Java程序員無需自己手動釋放內(nèi)存盼樟,這是Java的自動化垃圾回收機制帶來的好處氢卡。
Java垃圾回收常用的算法包括標記-清除、標記-整理晨缴、復(fù)制算法和分代算法译秦。
標記-清除算法會標記不需要回收的對象,然后清除掉需要回收的對象击碗;
標記-整理算法會對所有存活的對象進行整理以減少內(nèi)存碎片筑悴;
標記-復(fù)制算法會將存活的對象復(fù)制到新的空間中,然后清除舊空間延都;
而分代算法則是將對象按照生命周期的長短分為不同的代雷猪,在不同的代中采用不同的回收算法來提升垃圾回收效率。
Java的異常/錯誤及其分類
Java中的異常主要分為三類:受檢異常晰房、運行時異常和錯誤求摇。
- 受檢異常(Checked Exception):這類異常在編譯期就需要被檢查到并進行處理,否則編譯器會報錯殊者。如IOException和SQLException等与境。這類異常在處理時需要使用try-catch語句或者throws聲明拋出。
- 運行時異常(Unchecked Exception):這類異常在程序運行時才會拋出猖吴,如空指針異常(NullPointerException)和數(shù)組下標越界異常(ArrayIndexOutOfBoundsException)等摔刁。這種異常通常是由于程序邏輯錯誤或者意外情況導(dǎo)致的。程序員通常不需要對此類異常進行特別處理海蔽。
- 錯誤(Error):這類異常通常是由于系統(tǒng)錯誤或者資源耗盡等嚴重問題導(dǎo)致的共屈,如OutOfMemoryError等绑谣。與運行時異常類似,錯誤也不需要特別處理拗引,通常會導(dǎo)致程序崩潰借宵,而需要排查和修復(fù)錯誤產(chǎn)生的根本原因。
所以矾削,處理異常需要根據(jù)異常類型和情況選擇合適的方式進行處理壤玫,通常我們會對受檢異常進行捕獲和處理,對于運行時異常和錯誤則不需要特別處理哼凯。
Java的反射
反射是Java程序運行時獲取程序結(jié)構(gòu)(如類欲间、接口、變量断部、方法猎贴、注解等)信息的一種能力。它允許程序在運行時獲取一個類的完整構(gòu)造家坎,并進行操作嘱能,比如實例化對象、獲取屬性虱疏、調(diào)用方法等惹骂。通過反射,程序可以動態(tài)地創(chuàng)建對象做瞪、讀取和修改對象的屬性值和調(diào)用對象的方法对粪,同時也可以操作類的元數(shù)據(jù),例如泛型信息装蓬、注解等著拭。
用途:
- 動態(tài)地創(chuàng)建對象:通過反射可以在運行時根據(jù)類名動態(tài)地創(chuàng)建對象。
- 訪問私有變量和方法:反射可以訪問和修改類中的私有變量和方法牍帚,這在某些情況下是十分必要的儡遮。
- 動態(tài)代理:通過反射可以動態(tài)地生成代理類,從而實現(xiàn)AOP編程暗赶。
- 編寫通用代碼:反射可以使代碼更加通用鄙币,從而減少代碼量。
使用JDBC時蹂随,為什么要反射
動態(tài)加載JDBC驅(qū)動程序的類十嘿,從而使得JDBC驅(qū)動程序能夠被使用。因為JDBC驅(qū)動程序不是由Java語言編寫的岳锁,它們是通過本地代碼實現(xiàn)的绩衷,因此需要使用本地庫。而在Java中,通過反射機制可以動態(tài)獲取并加載本地庫咳燕,使得JDBC驅(qū)動程序能夠被正確加載和使用勿决。
Java類加載的過程
Java類加載的過程可分為三個階段:
- 加載:通過類加載器將class文件加載到JVM中,并生成對應(yīng)的Class對象招盲。
- 鏈接:
2.1. 驗證:確保加載的類符合Java語言規(guī)范和JVM規(guī)范剥险,避免安全問題。
2.2. 準備:為類變量分配內(nèi)存空間宪肖,并設(shè)置默認值。
2.3. 解析:將符號引用轉(zhuǎn)換為直接引用健爬,如將變量或方法的名字轉(zhuǎn)換為內(nèi)存地址控乾。 - 初始化:為類變量賦初始值,并執(zhí)行靜態(tài)代碼塊娜遵。
如果有父類蜕衡,則先進行父類加載。如果這些階段中存在錯誤设拟,便會拋出ClassNotFoundException
慨仿、NoClassDefFoundError
等異常。
(編者注:其它書籍纳胧、文檔镰吆、博客可能會使用別的說法,例如把2.鏈接中的三個步驟拆開來跑慕。)
Java中和class有關(guān)的Exception
-
ClassNotFoundException
万皿,表示在運行時找不到指定的類,通常是因為類路徑或者類名稱不正確核行。檢查型牢硅。 -
NoClassDefFoundError
,表示無法找到類定義芝雪,通常是因為代碼試圖使用某個類减余,但找不到該類的定義。非檢查型惩系,而且是Error
位岔。 -
NoSuchMethodError
,表示代碼試圖調(diào)用不存在的方法蛆挫,通常是因為當前版本的類庫與代碼編譯時使用的類庫不同赃承。 -
IllegalAccessException
,表示試圖訪問私有成員或受保護的成員悴侵,但沒有足夠的權(quán)限瞧剖。 -
InstantiationException
,表示試圖創(chuàng)建抽象類或接口的實例,或試圖創(chuàng)建沒有無參構(gòu)造函數(shù)的類的實例抓于。
Class.forName()做粤、ClassLoader.loadClass()有什么區(qū)別
-
Class.forName()
在加載類的過程中,會對類進行初始化捉撮。 -
ClassLoader.loadClass()
不會自動初始化類怕品,需要顯式調(diào)用Class.newInstance()
或Class.getDeclaredConstructor()
創(chuàng)建實例對象來啟動初始化。
Java并發(fā)編程知識
進程與線程的概念是什么巾遭?有什么區(qū)別肉康?
進程是程序在執(zhí)行過程中分配和管理資源的基本單位。包含了代碼灼舍、數(shù)據(jù)以及執(zhí)行的上下文等相關(guān)信息吼和。操作系統(tǒng)會為每一個進程分配一定的資源,例如內(nèi)存骑素、CPU時間片等炫乓。
線程是進程中的一個執(zhí)行單元,一個進程可以包含多個線程献丑。線程共享進程的資源末捣,包括代碼、數(shù)據(jù)和上下文等创橄,因此在同一個進程中的多個線程之間可以直接共享數(shù)據(jù)尖啡。同時闹击,線程也有自己的椥ǎ空間和寄存器數(shù)據(jù)岔乔。
進程和線程的主要區(qū)別在于:
- 進程之間相互隔離,不能直接訪問和修改對方的內(nèi)存空間咖熟;而線程之間可以共享同一個進程的資源圃酵;
- 每個進程有獨立的內(nèi)存空間,但是進程內(nèi)的多個線程共享同一個內(nèi)存空間馍管;
- 創(chuàng)建和銷毀線程的開銷比創(chuàng)建和銷毀進程的開銷小郭赐,因此多用于并發(fā)環(huán)境下的處理。
(編者注:可能在其它的博客确沸、文檔捌锭、書籍里,有說得更清楚的罗捎。)
為什么要多線程編程观谦?什么場景下使用多線程編程?
多線程編程可以提高程序的執(zhí)行效率和響應(yīng)速度桨菜。當程序需要處理多個任務(wù)時豁状,使用多線程可以讓不同的任務(wù)并行執(zhí)行捉偏,避免單線程阻塞導(dǎo)致程序響應(yīng)緩慢。
多線程編程適用于以下場景:
- 當程序需要執(zhí)行一些耗時的操作時泻红,使用多線程可以將這些操作放到另一個線程中執(zhí)行夭禽,讓主線程可以繼續(xù)響應(yīng)用戶的操作,提高程序的響應(yīng)速度谊路。
- 當程序需要執(zhí)行多個任務(wù)時讹躯,使用多線程可以同時執(zhí)行這些任務(wù),提高了程序的并發(fā)能力缠劝。
- 當需要使用某些第三方庫或API時潮梯,這些庫可能是阻塞式的,也就是說在執(zhí)行過程中會阻塞當前線程惨恭,影響程序的響應(yīng)速度酷麦。使用多線程可以將這些操作放到另一個線程中執(zhí)行,保持主線程的響應(yīng)速度喉恋。
有哪些并發(fā)問題?
競態(tài)條件:多個線程(或進程)同時訪問共享數(shù)據(jù)母廷,導(dǎo)致數(shù)據(jù)的狀態(tài)不確定性和不一致性轻黑。
死鎖和活鎖:多個線程持有互斥鎖,但是它們彼此需要對方釋放鎖才能繼續(xù)執(zhí)行琴昆,導(dǎo)致線程無法前進氓鄙。
資源不足:多個線程競爭資源,但是資源數(shù)限制业舍,導(dǎo)致某些進程無法獲取所需資源抖拦。
數(shù)據(jù)同步問題:多線程訪問同一數(shù)據(jù)源可能會導(dǎo)致數(shù)據(jù)不一致,需要使用同步技術(shù)來保證數(shù)據(jù)的一致性舷暮。
上下文切換開銷:多個線程在并發(fā)執(zhí)行時态罪,會頻繁地發(fā)生上下文切換,導(dǎo)致系統(tǒng)開銷增加下面。
進程間通信复颈、線程間通信的方法有哪些?
進程間通信的方式包括管道沥割、命名管道耗啦、共享內(nèi)存、消息隊列机杜、信號量和套接字等帜讲。其中,管道和命名管道用于單向通信椒拗,共享內(nèi)存和消息隊列用于數(shù)據(jù)共享似将,信號量用于進程間進行同步和互斥获黔,套接字則可以實現(xiàn)不同機器間的通信。(筆者注:這套說辭僅適用于Java玩郊,應(yīng)該不是隨便兩個進程都可以這樣的肢执。)
線程間通信的方式包括鎖、條件變量译红、信號量预茄、讀寫鎖和原子變量等。其中侦厚,鎖用于實現(xiàn)線程之間的互斥耻陕,條件變量用于線程之間的通信,信號量也可以用于線程間同步和互斥刨沦。讀寫鎖則用于讀取和修改共享數(shù)據(jù)的場景诗宣,原子變量則可以保證某些操作的原子性。
線程安全是什么含義想诅?
"線程安全"是指在多線程應(yīng)用程序中召庞,當多個線程同時訪問共享資源時,不會發(fā)生不確定的来破、意外的結(jié)果篮灼。
線程安全要求:原子性、可見性徘禁、有序性诅诱。
原子性(atomicity)指的是一個操作是不可被中斷的整體,在對多個數(shù)據(jù)進行操作的時候送朱,要么所有數(shù)據(jù)同時發(fā)生變化娘荡,要么所有數(shù)據(jù)都不發(fā)生變化。
可見性(visibility)指的是一個線程修改的變量能否被其他線程及時感知到驶沼。在多線程編程中炮沐,如果不保證可見性,可能會出現(xiàn)一個線程修改了共享變量的值回怜,但是其他線程仍然讀取的是舊值的情況央拖。
有序性(ordering)指的是一個線程中的操作與其他線程中的操作發(fā)生的先后順序可能會存在差異,需要通過特定的機制來保證一定的順序鹉戚,以及保證在一個線程中的操作順序鲜戒。
引申:用synchronized
上鎖保證三個特性。用volatile
保證可見性抹凳、有序性遏餐,不保證原子性。
守護線程是什么赢底?
守護線程(daemon thread)失都,是服務(wù)其他的線程柏蘑。當所有的非守護線程結(jié)束運行時,守護線程會隨著虛擬機的關(guān)閉而結(jié)束運行粹庞。
守護線程主要用于在后臺執(zhí)行支持性任務(wù)咳焚,比如Java垃圾回收線程就是一個典型的守護線程。
start()方法和 run()方法有什么區(qū)別庞溜?
調(diào)用 start()
方法會創(chuàng)建一個新的線程并在新的線程中執(zhí)行 run()
方法革半。
而調(diào)用 run()
方法則只是在當前線程中直接執(zhí)行 run()
方法。如果直接調(diào)用 run()
方法流码,那么就不會創(chuàng)建新的線程又官,而是在當前線程中直接執(zhí)行,這可能會阻塞主線程漫试。
Java有哪些常見的線程安全的容器六敬?
Java中的線程安全容器主要有以下幾種:
- ConcurrentHashMap:適用于高并發(fā)環(huán)境的哈希表,支持高效的并發(fā)讀寫操作驾荣。
- CopyOnWriteArrayList:一個線程安全的ArrayList外构,它采用了一種寫時復(fù)制的思想,在寫操作時播掷,會進行數(shù)據(jù)的復(fù)制审编,因此讀操作不會阻塞寫操作。
- ConcurrentLinkedQueue:基于鏈表實現(xiàn)的線程安全隊列叮趴,適用于高并發(fā)的生產(chǎn)者消費者模型。
- BlockingQueue:Java中提供的阻塞隊列接口权烧,提供了
put
眯亦、take
等阻塞方法,能夠很好地支持生產(chǎn)者消費者模式般码。
還有一些其他的線程安全容器妻率,如ThreadLocal等,不過它們的作用和上述容器不太相同板祝。
ConcurrentHashMap是怎么實現(xiàn)線程安全的宫静?
ConcurrentHashMap實現(xiàn)了分段鎖,將整個Map的存儲空間分成了若干個小段券时,每一小段都是一個獨立的鎖孤里。不同的線程可以同時訪問這些小段上的不同數(shù)據(jù),從而提高了并發(fā)性能橘洞。
分段鎖的另一個好處是捌袜,當一個線程訪問其中一個小段時,其他線程可以并行地訪問其他小段炸枣,不會被阻塞虏等。這種機制在高并發(fā)的場景下非常有效弄唧,可以極大提升ConcurrentHashMap的性能。
CopyOnWriteArrayList是怎么實現(xiàn)線程安全的霍衫?
CopyOnWriteArrayList是通過在修改元素的時候不直接修改原始數(shù)組候引,而是先將原始數(shù)組復(fù)制一份,并在副本上進行修改的方式實現(xiàn)線程安全的敦跌。當有線程要修改CopyOnWriteArrayList時澄干,先復(fù)制一份當前的元素數(shù)組,在副本上進行修改操作峰髓,并最終將修改后的副本替換掉原始的元素數(shù)組傻寂,這樣就避免了多線程并發(fā)修改同一個數(shù)組時可能會出現(xiàn)的問題。
在讀取元素時携兵,CopyOnWriteArrayList會直接從原始的數(shù)組中讀取數(shù)據(jù)疾掰,因為在多線程并發(fā)訪問的情況下,讀取數(shù)據(jù)是線程安全的徐紧。
因此静檬,CopyOnWriteArrayList適用于讀多寫少的場景,因為每次寫入數(shù)據(jù)都需要復(fù)制一份原始數(shù)組并级,這會占用一定的內(nèi)存空間拂檩,加重GC負擔,并且會造成數(shù)據(jù)不一致的問題嘲碧。
Java線程池的核心參數(shù)
- corePoolSize:線程池中保持的最小線程數(shù)量稻励。
- maximumPoolSize:線程池中允許的最大線程數(shù)量。
- keepAliveTime:當線程池中線程數(shù)量超過corePoolSize時愈涩,多余的空閑線程的存活時間望抽。
- unit:keepAliveTime的時間單位。
- workQueue:用來保存等待執(zhí)行的任務(wù)的阻塞隊列履婉。
- threadFactory:創(chuàng)建新線程的工廠煤篙。
- handler:當線程池中的工作隊列已滿且無法再添加新線程時,線程池將用handler來拒絕新提交的任務(wù)毁腿。
corePoolSize和maximumPoolSize的區(qū)別
corePoolSize表示核心線程池大小辑奈,也就是線程池中保持活動狀態(tài)的線程數(shù),即使這些線程處于空閑狀態(tài)已烤,也不會被回收鸠窗。
maximumPoolSize則是線程池最大的線程數(shù),如果隊列滿了胯究,且當前線程數(shù)小于最大線程數(shù)塌鸯,那就會創(chuàng)建新的線程來處理任務(wù)。
關(guān)于線程池的demo程序
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 定義一個線程池唐片,其中最多有5個線程并行執(zhí)行
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 向線程池提交10個任務(wù)
for (int i = 0; i < 10; i++) {
executorService.execute(new Task(i + 1));
}
// 關(guān)閉線程池
executorService.shutdown();
}
/**
* 自定義的任務(wù)類丙猬,實現(xiàn)Runnable接口
*/
static class Task implements Runnable {
private int taskNum;
public Task(int taskNum) {
this.taskNum = taskNum;
}
@Override
public void run() {
// 執(zhí)行任務(wù)涨颜,打印任務(wù)編號和當前線程名稱
System.out.println("Task " + taskNum + " is running. Thread name: " + Thread.currentThread().getName());
// 睡眠一段時間,模擬任務(wù)的執(zhí)行時間
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 任務(wù)執(zhí)行完畢茧球,打印結(jié)束信息
System.out.println("Task " + taskNum + " is finished. Thread name: " + Thread.currentThread().getName());
}
}
}
死鎖的4個條件
- 互斥條件:一個資源每次只能被一個進程使用庭瑰。
- 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放抢埋。
- 不剝奪條件:進程已獲得的資源弹灭,在未完成使用之前,不能被強行剝奪揪垄,只能由進程自己釋放穷吮。
- 循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
悲觀鎖和樂觀鎖的含義
(概念)悲觀鎖默認其他線程可能會修改它饥努,因此在訪問前會先對資源進行加鎖捡鱼,以防止其他線程的干擾。(應(yīng)用場景)悲觀鎖一般采用互斥鎖酷愧、讀寫鎖等機制來實現(xiàn)驾诈。悲觀鎖適用于寫操作比較頻繁的場景,如數(shù)據(jù)庫的更新操作溶浴。
(概念)樂觀鎖默認認為對共享資源的修改是少數(shù)乍迄,因此不用加鎖。而是在更新數(shù)據(jù)時比較數(shù)據(jù)版本號等信息士败,如果版本號匹配闯两,就執(zhí)行更新操作,否則認為數(shù)據(jù)沖突谅将,需要回滾或者重試漾狼。樂觀鎖一般采用版本號、時間戳等機制來實現(xiàn)戏自。(應(yīng)用場景)樂觀鎖適用于讀操作比較頻繁而寫操作比較少的場景邦投,如多用戶訪問同一筆數(shù)據(jù)的場景伤锚。
樂觀鎖的CAS算法
CAS(Compare And Swap)算法通過比較內(nèi)存中的值是否與期望值相同擅笔,如果相同,則將新值寫入該內(nèi)存地址屯援;如果不同猛们,則表明有其他線程已經(jīng)修改過內(nèi)存中的值,此時需要重新讀取內(nèi)存中的值并再次比較和嘗試修改狞洋,直到修改成功弯淘。這種算法避免了多線程同時寫入數(shù)據(jù)時出現(xiàn)數(shù)據(jù)混亂的問題,提高了程序的并發(fā)性能吉懊。
算法
編者按:算法這里就不多說了庐橙,就聊聊排序算法吧假勿,被問到的概率比較大。
快速排序算法
快速排序的思路是選取一個基準元素态鳖,將數(shù)組中小于該元素的放在左邊转培,大于該元素的放在右邊,然后遞歸地對左邊和右邊重復(fù)這個過程浆竭,直到排序完成浸须。
以下是Java實現(xiàn)的快速排序函數(shù):
public static void quickSort(int[] arr, int left, int right) {
if (left >= right) { // 如果左右指針相遇或交錯,則返回
return;
}
int pivot = partition(arr, left, right); // 選取基準元素
quickSort(arr, left, pivot - 1); // 對左半部分進行快速排序
quickSort(arr, pivot + 1, right); // 對右半部分進行快速排序
}
public static int partition(int[] arr, int left, int right) {
int pivot = arr[right]; // 基準元素選擇最右邊的一個
int i = left - 1; // 定義i為小于基準元素的區(qū)間的末尾
for (int j = left; j < right; j++) {
if (arr[j] < pivot) { // 如果當前元素比基準元素小
i++; // i擴大區(qū)間
swap(arr, i, j); // 交換i和j所指的元素
}
}
swap(arr, i+1, right); // 交換i+1和基準元素
return i + 1;
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
快速排序最好邦泄、平均和最壞情況下的時間復(fù)雜度分別為删窒、
和
。
Java自帶的排序算法
Java自帶的排序顺囊,例如Arrays.sort()
肌索,并不是快速排序算法。而是Tim算法包蓝。
Arrays.sort()
使用了一種名為TimSort
的排序算法驶社。它是一種基于歸并排序和插入排序的混合排序算法,在處理小數(shù)組時使用插入排序测萎,在處理大數(shù)組時使用歸并排序亡电,并嘗試通過充分利用已排序的子序列來提高性能。
常見的排序算法
排序算法 | 最好時間復(fù)雜度 | 最壞時間復(fù)雜度 | 平均時間復(fù)雜度 | 是否穩(wěn)定 |
---|---|---|---|---|
冒泡排序 | 穩(wěn)定 | |||
選擇排序 | 不穩(wěn)定 | |||
插入排序 | 穩(wěn)定 | |||
快速排序 | 不穩(wěn)定 | |||
歸并排序 | 穩(wěn)定 | |||
堆排序 | 不穩(wěn)定 |
MySQL數(shù)據(jù)庫
MySQL的引擎
- InnoDB是MySQL默認的引擎硅瞧,支持事務(wù)處理和行級鎖份乒,適合數(shù)據(jù)更新較頻繁的應(yīng)用。只支持BTREE索引腕唧,不支持HASH索引或辖。
- MyISAM不支持事務(wù)處理,但對讀操作性能好枣接,適合于數(shù)據(jù)量大但更新不頻繁的應(yīng)用颂暇。
- Memory引擎將表存儲在內(nèi)存中,讀寫速度非车蹋快耳鸯,但數(shù)據(jù)不是持久的,重啟后數(shù)據(jù)將會丟失膀曾。
BTREE索引和HASH索引對比
BTREE索引使用B-tree數(shù)據(jù)結(jié)構(gòu)進行存儲县爬,它可以對索引列進行排序,支持范圍查找和順序遍歷添谊,適合處理范圍查詢财喳,比如大于、小于、比較等耳高。但是在進行等值查詢時扎瓶,BTREE索引的查詢性能可能會有些許劣化。
HASH索引使用哈希表進行存儲泌枪,它可以快速進行等值查詢栗弟,查找時間近乎恒定,適合處理等值查詢工闺,比如"="和"IN"等乍赫。但是HASH索引對于范圍查詢和ORDER BY排序操作較為困難,而且因為哈希表的特性陆蟆,散列值沖突的情況較多雷厂,可能會降低其查詢效率。
總之叠殷,BTREE索引適合處理復(fù)雜的查詢改鲫,而HASH索引適合處理簡單的等值查詢。
MySQL數(shù)據(jù)庫中 索引的作用
MySQL數(shù)據(jù)庫的索引是用來加速數(shù)據(jù)庫表中的數(shù)據(jù)檢索和查詢的林束。通過在表中創(chuàng)建索引像棘,可以大大提高查詢的速度。
(編者注:具體到MySQL數(shù)據(jù)庫壶冒,索引index同時就是鍵key缕题,可以創(chuàng)建唯一鍵來讓某個字段的內(nèi)容互不相同,從而幫助實現(xiàn)數(shù)據(jù)約束胖腾。)
MySQL索引失效的情況
- 使用了
NOT IN
烟零。使用了OR
。 - 對索引列進行了函數(shù)操作咸作,如使用了函數(shù)锨阿、類型轉(zhuǎn)換或者運算符。
- 對索引列使用了LIKE操作符记罚,并且通配符在開頭(如 LIKE '%abc')墅诡。
- 對于多列索引,只使用了索引中部分列進行查詢桐智。
- 數(shù)據(jù)量過大末早,導(dǎo)致MySQL優(yōu)化器選擇全表掃描而非使用索引。
- 對于InnoDB存儲引擎酵使,在高并發(fā)環(huán)境下荐吉,若使用的是非主鍵索引焙糟,則可能會發(fā)生死鎖而導(dǎo)致索引失效口渔。
(編者注:不止這些。但是面試時也不必全部答出來穿撮。其它的博客缺脉、文檔痪欲、書籍可能會有更豐富的答案。)
MySQL的索引最左匹配原則
MySQL的索引最左匹配原則是指攻礼,在使用復(fù)合索引進行查詢時业踢,MySQL會優(yōu)先匹配索引最左邊的列,只有當查詢條件包含索引最左邊的列時礁扮,才能充分利用索引知举。如果查詢條件不包含最左邊的列,即使后面有匹配的列也不能利用索引太伊,查詢效率會降低雇锡。
例如,有一個復(fù)合索引 (a,b,c)僚焦,如果查詢條件為 a=1锰提,那么MySQL會利用該索引查找到所有a=1的行,再在結(jié)果中進行b芳悲、c列的篩選立肘。如果查詢條件為 b=2,那么最左列a并沒有被用到名扛,MySQL無法利用該索引進行優(yōu)化谅年,會掃描整個表進行查找,查詢會變得非常慢肮韧。
數(shù)據(jù)庫的ACID屬性
ACID屬性是指數(shù)據(jù)庫在事務(wù)處理中需要滿足的一些基本性質(zhì)踢故,其中:
- 原子性(Atomicity):事務(wù)是一個不可分割的操作序列,要么全部執(zhí)行成功惹苗,要么全部失敗回滾殿较,不允許部分執(zhí)行。
- 一致性(Consistency):事務(wù)執(zhí)行前后桩蓉,數(shù)據(jù)庫的狀態(tài)必須保持一致淋纲,不會因為操作錯誤或者其他異常情況導(dǎo)致數(shù)據(jù)的邏輯錯誤或者不一致。
- 隔離性(Isolation):事務(wù)執(zhí)行時院究,對其他事務(wù)是隔離的洽瞬,每個事務(wù)都認為自己是唯一的操作,不受其他事務(wù)的干擾业汰。
- 持久性(Durability):事務(wù)執(zhí)行成功后伙窃,對數(shù)據(jù)庫的影響必須是永久性的,即使在系統(tǒng)崩潰或者重啟的情況下样漆,數(shù)據(jù)也能夠得到恢復(fù)为障。
臟讀、不可重復(fù)讀、幻讀
臟讀是指當一個事務(wù)讀取了另一個事務(wù)未提交的數(shù)據(jù)鳍怨,然后這些數(shù)據(jù)被回滾或修改呻右,導(dǎo)致第一個事務(wù)讀取了不正確的數(shù)據(jù)。
不可重復(fù)讀是指在一個事務(wù)的執(zhí)行過程中鞋喇,由于其他事務(wù)修改或刪除了已經(jīng)查詢的數(shù)據(jù)声滥,導(dǎo)致第一個事務(wù)后續(xù)的查詢結(jié)果和前面的查詢結(jié)果不同。
幻讀是指在一個事務(wù)的執(zhí)行過程中侦香,由于其他事務(wù)插入了新的數(shù)據(jù)落塑,導(dǎo)致第一個事務(wù)后續(xù)查詢時出現(xiàn)了一些新的行,使得之前的結(jié)果變得不合法罐韩。
數(shù)據(jù)庫的隔離級別
MySQL處理并發(fā)問題的隔離級別如下:
- 讀未提交(READ UNCOMMITTED):在該級別下芜赌,一個事務(wù)可以讀取另一個事務(wù)未提交的數(shù)據(jù)(臟讀),容易出現(xiàn)幻讀和不可重復(fù)讀的問題伴逸。
- 讀已提交(READ COMMITTED):在該級別下缠沈,一個事務(wù)只能讀取已經(jīng)提交的數(shù)據(jù),相對于讀未提交級別错蝴,此級別可以避免臟讀問題洲愤,但是幻讀和不可重復(fù)讀的問題仍可能出現(xiàn)。
- 可重復(fù)讀(REPEATABLE READ):在該級別下顷锰,一個事務(wù)執(zhí)行期間多次讀取相同的數(shù)據(jù)柬赐,總是得到同樣的結(jié)果,即只能讀取到自己的操作官紫,不能讀取未提交的數(shù)據(jù)肛宋,可以解決臟讀和不可重復(fù)讀問題。
- 串行化(SERIALIZABLE):在該級別下束世,MySQL使用悲觀鎖酝陈,對所有數(shù)據(jù)進行鎖定,可以避免臟讀毁涉、不可重復(fù)讀和幻讀的出現(xiàn)沉帮,但是會造成大量的鎖等待,降低數(shù)據(jù)庫的并發(fā)性能贫堰。
CHAR穆壕,VARCHAR 和 Text 的比較
CHAR是固定長度的,而VARCHAR是可變長度的其屏。相比之下喇勋,CHAR處理固定長度的字符串時速度更快,但是浪費存儲空間偎行,而VARCHAR適用于處理可變長度的字符串川背,節(jié)省存儲空間贰拿,但是在索引和比較時速度稍慢一些。
Text則是一種特殊的VARCHAR類型渗常,可以存儲大量的文本數(shù)據(jù)。相對而言汗盘,Text類型能夠存儲更長的文本皱碘,但在處理較小的數(shù)據(jù)時相對會浪費存儲空間和一定的查詢效率。因此隐孽,當需要存儲大量文本數(shù)據(jù)時癌椿,應(yīng)使用Text類型。
設(shè)計數(shù)據(jù)表的第一菱阵、二踢俄、三范式
第一范式(1NF):表中的所有列都是不可分割的原子值,即每一列都只包含單一的數(shù)據(jù)項晴及,不可再分都办。
第二范式(2NF):表中的非主鍵列必須完全依賴于主鍵,而非部分依賴虑稼。也就是說琳钉,一個表必須有一個唯一的主鍵,并且所有的非主鍵列必須和主鍵列有完全依賴關(guān)系蛛倦。
第三范式(3NF):表中的所有列必須和主鍵列直接相關(guān)歌懒,而不能與依賴于其他列。換句話說溯壶,表中的每一列都應(yīng)該只和主鍵列直接相關(guān)及皂,而不是間接相關(guān)。
實現(xiàn)了第一范式且改,保證了數(shù)據(jù)不會出現(xiàn)多余的重復(fù)字段验烧。實現(xiàn)了第二范式,則保證數(shù)據(jù)的唯一性和正確性又跛。實現(xiàn)了第三范式噪窘,則保證了數(shù)據(jù)只存儲在一個地方,避免了數(shù)據(jù)的冗余效扫。
計算機網(wǎng)絡(luò)知識
域名解析的過程
用戶在瀏覽器中輸入網(wǎng)址倔监,瀏覽器會向本地域名解析器(通常為路由器)發(fā)起查詢。
如果本地解析器中已經(jīng)有了這個域名的緩存記錄菌仁,那么它會直接返回IP地址給瀏覽器浩习;否則,它會向上級DNS服務(wù)器發(fā)出查詢請求济丘。
上級DNS服務(wù)器也可能會有緩存記錄谱秽,如果有洽蛀,就返回給本地解析器;否則疟赊,它會向更高層級的DNS服務(wù)器發(fā)出請求吝羞。 這個過程會一直重復(fù)战坤,直到查詢到根域名服務(wù)器(Root Name Server)。根域名服務(wù)器會返回下一級DNS服務(wù)器的地址(通常是頂級域名服務(wù)器,比如“.com”)判族,本地解析器然后向下一級DNS服務(wù)器發(fā)出查詢請求就谜。
DNS服務(wù)器返回目標域名對應(yīng)的IP地址給本地解析器挺益,本地解析器再將結(jié)果緩存起來牲尺,同時將IP地址返回給瀏覽器。
瀏覽器通過IP地址向目標服務(wù)器發(fā)出請求戳玫,服務(wù)器響應(yīng)后將網(wǎng)頁內(nèi)容返回給瀏覽器熙掺,完成整個過程。
(編者注:不必分為什么第一步咕宿、第二步……等币绩。像講故事一樣講出來,言之有理即可府阀。)
TCP和UDP
聯(lián)系
都是用來將數(shù)據(jù)從一個應(yīng)用程序傳輸?shù)搅硪粋€應(yīng)用程序类浪。
都需要運用一些特定的協(xié)議來完成數(shù)據(jù)傳輸。
都支持面向連接和無連接的通信方式肌似。
區(qū)別
TCP是面向連接的協(xié)議费就,UDP是無連接的協(xié)議。
TCP提供可靠的數(shù)據(jù)傳輸川队,而UDP則不保證數(shù)據(jù)傳輸?shù)目煽啃浴?/p>
TCP使用流量控制力细、擁塞控制和錯誤校驗來確保可靠性固额,而UDP則只提供基本的錯誤檢測眠蚂。
TCP通信速度較慢,UDP通信速度較快斗躏。
TCP可以適應(yīng)不同的網(wǎng)絡(luò)狀況逝慧,而UDP則更適用于高速數(shù)據(jù)傳輸。
建立于TCP啄糙、UDP的應(yīng)用層協(xié)議
TCP協(xié)議對應(yīng)的應(yīng)用層協(xié)議有HTTP笛臣、FTP、SMTP隧饼、TELNET沈堡、SSH等;
UDP協(xié)議對應(yīng)的應(yīng)用層協(xié)議有DNS燕雁、DHCP诞丽、TFTP等鲸拥。
TCP的三次握手、四次揮手
(編者按僧免,老掉牙了刑赶,去看別的書籍、博客懂衩,這里就不寫了撞叨。別人寫得很詳細了。)
TCP的滑動窗口
TCP協(xié)議的滑動窗口(Sliding Window)是一種流量控制機制勃痴,用于在發(fā)送端和接收端之間進行數(shù)據(jù)傳輸時的數(shù)據(jù)包確認和窗口大小調(diào)整谒所。
滑動窗口的基本原理是热康,在發(fā)送數(shù)據(jù)包之后沛申,發(fā)送方維護一個大小為N的窗口。N是指可以一次性發(fā)送多少個數(shù)據(jù)包未收到接收方的確認信息姐军。接收方收到數(shù)據(jù)包后铁材,會發(fā)送確認信息給發(fā)送方,告訴發(fā)送方已經(jīng)收到了數(shù)據(jù)奕锌,以及下一個期望接收的數(shù)據(jù)包的序號(ACK)著觉。在發(fā)送方收到ACK之后,就可以將窗口向前滑動一位惊暴,發(fā)送下一個數(shù)據(jù)包饼丘。接收方也可以根據(jù)當前情況調(diào)整窗口大小,從而控制傳輸速率辽话。
滑動窗口的作用是通過動態(tài)調(diào)整窗口大小來適應(yīng)不同網(wǎng)絡(luò)條件下的數(shù)據(jù)傳輸肄鸽。通過控制發(fā)送方發(fā)送數(shù)據(jù)包的速率,可以防止數(shù)據(jù)擁塞油啤、傳輸錯誤等問題的發(fā)生典徘。同時,滑動窗口也提供了一種簡便的流量控制機制益咬,可以確保高效逮诲、可靠地進行數(shù)據(jù)傳輸。
TCP的擁塞控制
TCP通過擁塞窗口來實現(xiàn)擁塞控制幽告。擁塞窗口是TCP用來控制發(fā)送數(shù)據(jù)量的一個參數(shù)梅鹦。當擁塞窗口較小時,TCP發(fā)送數(shù)據(jù)包的速度也會相應(yīng)較慢冗锁,以此來避免網(wǎng)絡(luò)擁塞帘瞭。TCP還會根據(jù)網(wǎng)絡(luò)的擁塞情況不斷調(diào)整擁塞窗口的大小,以達到更為合理的擁塞控制蒿讥。此外蝶念,TCP還通過重傳機制來保證數(shù)據(jù)的可靠性抛腕,同時避免持續(xù)發(fā)送數(shù)據(jù)導(dǎo)致網(wǎng)絡(luò)擁塞的情況∶窖常總之担敌,TCP的擁塞控制機制使其能夠在網(wǎng)絡(luò)擁塞的情況下自適應(yīng)地調(diào)整發(fā)送數(shù)據(jù)的速度,從而保證網(wǎng)絡(luò)傳輸?shù)馁|(zhì)量和穩(wěn)定性廷蓉。
HTTP和HTTPS的區(qū)別
HTTPS 在傳輸數(shù)據(jù)時全封,使用了TLS/SSL協(xié)議進行加密處理,使得數(shù)據(jù)傳輸過程中被竊聽或篡改的可能性大大降低桃犬。因此刹悴,當用戶訪問一個使用 HTTPS 協(xié)議的網(wǎng)站時,他們的數(shù)據(jù)會更加安全攒暇。而 HTTP 則未加密土匀,數(shù)據(jù)傳輸過程中可能會被篡改、竊聽形用,對于涉及敏感信息(如密碼就轧、銀行卡信息等)的傳輸,使用 HTTP 是不安全的田度。
HTTP的GET和POST方法
GET方法一般用來請求服務(wù)器返回某個資源妒御,它會將請求的參數(shù)編碼放在URL的后面,可以在瀏覽器地址欄中看到镇饺,因此在請求時傳遞的參數(shù)有長度限制(一般是2048個字符)乎莉,傳遞的數(shù)據(jù)量較小。GET方法是一個冪等方法奸笤,多次調(diào)用不會對資源產(chǎn)生副作用惋啃。因為GET方法的特性,它適用于請求數(shù)據(jù)揭保,但不適用于提交數(shù)據(jù)肥橙。
POST方法一般用來提交數(shù)據(jù),它會將參數(shù)放在HTTP請求體中秸侣,參數(shù)不會出現(xiàn)在URL中存筏,因此可以傳遞大量數(shù)據(jù)。POST方法不是一個冪等方法味榛,多次調(diào)用會對資源產(chǎn)生副作用椭坚,比如在數(shù)據(jù)庫中創(chuàng)建或修改數(shù)據(jù)。因為POST方法的特性搏色,它適用于提交表單數(shù)據(jù)善茎、上傳文件等需要提交數(shù)據(jù)的情景。
HTTP的狀態(tài)碼
1XX
:信息響應(yīng)類频轿,表示服務(wù)器已經(jīng)接收到請求垂涯,正在處理中烁焙。
2XX
:成功響應(yīng)類,表示請求已經(jīng)被服務(wù)器成功地接收耕赘、理解骄蝇、并接受處理。
3XX
:重定向響應(yīng)類操骡,表示需要進一步操作以完成請求九火。
4XX
:客戶端錯誤響應(yīng)類,表示請求存在問題册招,服務(wù)器無法處理岔激。
5XX
:服務(wù)器錯誤響應(yīng)類,表示服務(wù)器在處理請求時發(fā)生了錯誤是掰。
常見的狀態(tài)碼有:200
OK(請求成功), 404
Not Found(請求的資源不存在), 500
Internal Server Error(服務(wù)器內(nèi)部錯誤)等虑鼎。
(編者注:有些書籍和文檔中有非常詳細的狀態(tài)碼解釋。但是狀態(tài)碼很多冀惭,不必要每個都十分了解震叙。)
HTTP請求頭的參數(shù)
(僅說明常見的)
Accept:指定客戶端能夠接收的內(nèi)容類型掀鹅。
Accept-Encoding:指定客戶端能夠接受的編碼方式散休,比如 gzip 或 deflate。
Authorization:在需要認證的請求中乐尊,包含認證信息戚丸。
Content-Length:請求體的長度。
Content-Type:請求體的類型扔嵌,比如 application/json限府。
Cookie:包含客戶端發(fā)送給服務(wù)器的 cookie 信息。
User-Agent:客戶端的瀏覽器信息痢缎,用來識別客戶端的瀏覽器類型胁勺。
Host:請求的主機名。
HTTP相應(yīng)頭的參數(shù)
(僅說明常見的)
- Content-Type:響應(yīng)體的傳輸類型独旷,比如
text/html
署穗、application/json
等喵; - Content-Length:響應(yīng)體的長度喵嵌洼;
- Cache-Control:控制緩存的行為案疲,比如
max-age
、no-cache
麻养、private
等喵褐啡; - Server:服務(wù)器的軟件和版本號喵;
- Date:響應(yīng)的時間戳喵鳖昌;
- Set-Cookie:設(shè)置Cookie信息喵备畦;
- Location:重定向的URL地址喵低飒。
cookie和session
Cookie是存儲在用戶計算機上的小文本文件,由瀏覽器維護懂盐。其中可以包含任何數(shù)據(jù)逸嘀,例如用戶信息、偏好設(shè)置等等允粤。網(wǎng)站可以在用戶訪問它們的時候設(shè)置cookie崭倘,并在后續(xù)的請求中讀取。比如用戶的語言偏好类垫、購物車內(nèi)容司光、頁面皮膚等,可以使用 Cookie 存儲悉患。
Session是將數(shù)據(jù)存儲在服務(wù)器上而不是客戶端計算機上残家。 在用戶與web服務(wù)器進行會話時,服務(wù)器會為每個會話創(chuàng)建一個session對象售躁,并為該會話提供一個唯一的session ID坞淮。 session ID隨后可以在每個迭代中通過cookie或URL參數(shù)發(fā)送回客戶端。在后續(xù)請求中陪捷,服務(wù)器可以根據(jù)session ID檢索session對象回窘,并使用其中存儲的數(shù)據(jù)進行請求處理。session經(jīng)常被用于存儲敏感數(shù)據(jù)市袖,例如用戶憑證等啡直,以防止不受信任的訪問。如登錄憑證苍碟、用戶權(quán)限酒觅、用戶信息等,采用 Session 機制比較保險微峰。
本文 90% 以上的內(nèi)容由 ChatGPT 撰寫舷丹。我(編者)只進行了簡單的勘誤、注釋蜓肆、整理颜凯。