前言
轉(zhuǎn)眼間2020年過去已經(jīng)有一段時(shí)間了棉安,相信大家在2020年年末的秋招已經(jīng)拿到了自己滿意的offer媳维。有沒有拿到的也不用著急酿雪,小編在這里為大家整理了一份2021春招java后端開發(fā)面試知識(shí)點(diǎn)總結(jié),大家只要把這份知識(shí)點(diǎn)背熟記牢侄刽,突擊一個(gè)面試還是沒有什么問題的指黎,這份知識(shí)點(diǎn)包括Spring,Dubbo州丹,MyBatis, RPC, 源碼分析醋安,高并發(fā)、高性能墓毒、分布式,性能優(yōu)化吓揪,微服務(wù) 高級(jí)架構(gòu)開發(fā)等等,滿滿的干貨給大家放在下面了所计!
語言特性
Java 語言的優(yōu)點(diǎn)柠辞?
① 平臺(tái)無關(guān)性,擺脫硬件束縛主胧,"一次編寫叭首,到處運(yùn)行"。
② 相對(duì)安全的內(nèi)存管理和訪問機(jī)制踪栋,避免大部分內(nèi)存泄漏和指針越界焙格。
③ 熱點(diǎn)代碼檢測(cè)和運(yùn)行時(shí)編譯及優(yōu)化,使程序隨運(yùn)行時(shí)間增長獲得更高性能夷都。
④ 完善的應(yīng)用程序接口眷唉,支持第三方類庫。
Java 如何實(shí)現(xiàn)平臺(tái)無關(guān)囤官?
JVM: Java 編譯器可生成與計(jì)算機(jī)體系結(jié)構(gòu)無關(guān)的字節(jié)碼指令厢破,字節(jié)碼文件不僅可以輕易地在任何機(jī)器上解釋執(zhí)行,還可以動(dòng)態(tài)地轉(zhuǎn)換成本地機(jī)器代碼治拿,轉(zhuǎn)換是由 JVM 實(shí)現(xiàn)的摩泪,JVM 是平臺(tái)相關(guān)的,屏蔽了不同操作系統(tǒng)的差異劫谅。
語言規(guī)范: 基本數(shù)據(jù)類型大小有明確規(guī)定见坑,例如 int 永遠(yuǎn)為 32 位嚷掠,而 C/C++ 中可能是 16 位、32 位荞驴,也可能是編譯器開發(fā)商指定的其他大小不皆。Java 中數(shù)值類型有固定字節(jié)數(shù),二進(jìn)制數(shù)據(jù)以固定格式存儲(chǔ)和傳輸熊楼,字符串采用標(biāo)準(zhǔn)的 Unicode 格式存儲(chǔ)霹娄。
JDK 和 JRE 的區(qū)別?
JDK: Java Development Kit鲫骗,開發(fā)工具包犬耻。提供了編譯運(yùn)行 Java 程序的各種工具,包括編譯器执泰、JRE 及常用類庫枕磁,是 JAVA 核心。
JRE: Java Runtime Environment术吝,運(yùn)行時(shí)環(huán)境计济,運(yùn)行 Java 程序的必要環(huán)境,包括 JVM排苍、核心類庫沦寂、核心配置工具。
Java 按值調(diào)用還是引用調(diào)用淘衙?
按值調(diào)用指方法接收調(diào)用者提供的值传藏,按引用調(diào)用指方法接收調(diào)用者提供的變量地址。
Java 總是按值調(diào)用幔翰,方法得到的是所有參數(shù)值的副本漩氨,傳遞對(duì)象時(shí)實(shí)際上方法接收的是對(duì)象引用的副本西壮。方法不能修改基本數(shù)據(jù)類型的參數(shù)遗增,如果傳遞了一個(gè) int 值 ,改變值不會(huì)影響實(shí)參款青,因?yàn)楦淖兊氖侵档囊粋€(gè)副本做修。
可以改變對(duì)象參數(shù)的狀態(tài),但不能讓對(duì)象參數(shù)引用一個(gè)新的對(duì)象抡草。如果傳遞了一個(gè) int 數(shù)組饰及,改變數(shù)組的內(nèi)容會(huì)影響實(shí)參,而改變這個(gè)參數(shù)的引用并不會(huì)讓實(shí)參引用新的數(shù)組對(duì)象康震。
淺拷貝和深拷貝的區(qū)別燎含?
淺拷貝: 只復(fù)制當(dāng)前對(duì)象的基本數(shù)據(jù)類型及引用變量,沒有復(fù)制引用變量指向的實(shí)際對(duì)象腿短。修改克隆對(duì)象可能影響原對(duì)象屏箍,不安全绘梦。
深拷貝: 完全拷貝基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,安全赴魁。
什么是反射卸奉?
在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類都能知道它的所有屬性和方法颖御,對(duì)于任意一個(gè)對(duì)象都能調(diào)用它的任意方法和屬性榄棵,這種動(dòng)態(tài)獲取信息及調(diào)用對(duì)象方法的功能稱為反射。缺點(diǎn)是破壞了封裝性以及泛型約束潘拱。反射是框架的核心疹鳄,Spring 大量使用反射。
Class 類的作用泽铛?如何獲取一個(gè) Class 對(duì)象尚辑?
在程序運(yùn)行期間,Java 運(yùn)行時(shí)系統(tǒng)為所有對(duì)象維護(hù)一個(gè)運(yùn)行時(shí)類型標(biāo)識(shí)盔腔,這個(gè)信息會(huì)跟蹤每個(gè)對(duì)象所屬的類杠茬,虛擬機(jī)利用運(yùn)行時(shí)類型信息選擇要執(zhí)行的正確方法,保存這些信息的類就是 Class弛随,這是一個(gè)泛型類瓢喉。
獲取 Class 對(duì)象:① 類名.class 。②對(duì)象的 getClass方法舀透。③ Class.forName(類的全限定名)栓票。
什么是注解?什么是元注解愕够?
注解是一種標(biāo)記走贪,使類或接口附加額外信息,幫助編譯器和 JVM 完成一些特定功能惑芭,例如 @Override 標(biāo)識(shí)一個(gè)方法是重寫方法坠狡。
元注解是自定義注解的注解,例如:
@Target:約束作用位置遂跟,值是 ElementType 枚舉常量逃沿,包括 METHOD 方法、VARIABLE 變量幻锁、TYPE 類/接口凯亮、PARAMETER 方法參數(shù)、CONSTRUCTORS 構(gòu)造方法和 LOACL_VARIABLE 局部變量等哄尔。
@Rentention:約束生命周期假消,值是 RetentionPolicy 枚舉常量,包括 SOURCE 源碼岭接、CLASS 字節(jié)碼和 RUNTIME 運(yùn)行時(shí)富拗。
@Documented:表明這個(gè)注解應(yīng)該被 javadoc 記錄堂鲤。
什么是泛型,有什么作用媒峡?
泛型本質(zhì)是參數(shù)化類型瘟栖,解決不確定對(duì)象具體類型的問題。泛型在定義處只具備執(zhí)行 Object 方法的能力谅阿。
泛型的好處:① 類型安全半哟,放置什么出來就是什么,不存在 ClassCastException签餐。② 提升可讀性寓涨,編碼階段就顯式知道泛型集合、泛型方法等處理的對(duì)象類型氯檐。③ 代碼重用戒良,合并了同類型的處理代碼。
泛型擦除是什么冠摄?
泛型用于編譯階段糯崎,編譯后的字節(jié)碼文件不包含泛型類型信息,因?yàn)樘摂M機(jī)沒有泛型類型對(duì)象河泳,所有對(duì)象都屬于普通類沃呢。例如定義 List<Object> 或 List<String>,在編譯后都會(huì)變成 List 拆挥。
定義一個(gè)泛型類型薄霜,會(huì)自動(dòng)提供一個(gè)對(duì)應(yīng)原始類型,類型變量會(huì)被擦除纸兔。如果沒有限定類型就會(huì)替換為 Object惰瓜,如果有限定類型就會(huì)替換為第一個(gè)限定類型,例如 <T extends A & B> 會(huì)使用 A 類型替換 T汉矿。
JDK8 新特性有哪些崎坊?
lambda 表達(dá)式:允許把函數(shù)作為參數(shù)傳遞到方法,簡(jiǎn)化匿名內(nèi)部類代碼负甸。
函數(shù)式接口:使用 @FunctionalInterface 標(biāo)識(shí)流强,有且僅有一個(gè)抽象方法痹届,可被隱式轉(zhuǎn)換為 lambda 表達(dá)式呻待。
方法引用:可以引用已有類或?qū)ο蟮姆椒ê蜆?gòu)造方法,進(jìn)一步簡(jiǎn)化 lambda 表達(dá)式队腐。
接口:接口可以定義 default 修飾的默認(rèn)方法蚕捉,降低了接口升級(jí)的復(fù)雜性,還可以定義靜態(tài)方法柴淘。
注解:引入重復(fù)注解機(jī)制迫淹,相同注解在同地方可以聲明多次秘通。注解作用范圍也進(jìn)行了擴(kuò)展,可作用于局部變量敛熬、泛型肺稀、方法異常等。
類型推測(cè):加強(qiáng)了類型推測(cè)機(jī)制应民,使代碼更加簡(jiǎn)潔话原。
Optional 類:處理空指針異常,提高代碼可讀性诲锹。
Stream 類:引入函數(shù)式編程風(fēng)格繁仁,提供了很多功能,使代碼更加簡(jiǎn)潔归园。方法包括 forEach 遍歷黄虱、count 統(tǒng)計(jì)個(gè)數(shù)、filter 按條件過濾庸诱、limit 取前 n 個(gè)元素捻浦、skip 跳過前 n 個(gè)元素、map 映射加工桥爽、concat 合并 stream 流等默勾。
日期:增強(qiáng)了日期和時(shí)間 API,新的 java.time 包主要包含了處理日期聚谁、時(shí)間母剥、日期/時(shí)間、時(shí)區(qū)形导、時(shí)刻和時(shí)鐘等操作环疼。
JavaScript:提供了一個(gè)新的 JavaScript 引擎,允許在 JVM上運(yùn)行特定 JavaScript 應(yīng)用朵耕。
異常有哪些分類炫隶?
所有異常都是 Throwable 的子類,分為 Error 和 Exception阎曹。Error 是 Java 運(yùn)行時(shí)系統(tǒng)的內(nèi)部錯(cuò)誤和資源耗盡錯(cuò)誤伪阶,例如 StackOverFlowError 和 OutOfMemoryError,這種異常程序無法處理处嫌。
Exception 分為受檢異常和非受檢異常栅贴,受檢異常需要在代碼中顯式處理,否則會(huì)編譯出錯(cuò)熏迹,非受檢異常是運(yùn)行時(shí)異常檐薯,繼承自 RuntimeException。
受檢異常:① 無能為力型,如字段超長導(dǎo)致的 SQLException坛缕。② 力所能及型墓猎,如未授權(quán)異常 UnAuthorizedException,程序可跳轉(zhuǎn)權(quán)限申請(qǐng)頁面赚楚。常見受檢異常還有 FileNotFoundException毙沾、ClassNotFoundException、IOException等宠页。
非受檢異常:① 可預(yù)測(cè)異常搀军,例如 IndexOutOfBoundsException、NullPointerException勇皇、ClassCastException 等罩句,這類異常應(yīng)該提前處理。② 需捕捉異常敛摘,例如進(jìn)行 RPC 調(diào)用時(shí)的遠(yuǎn)程服務(wù)超時(shí)门烂,這類異常客戶端必須顯式處理兄淫。③ 可透出異常屯远,指框架或系統(tǒng)產(chǎn)生的且會(huì)自行處理的異常,例如 Spring 的 NoSuchRequestHandingMethodException捕虽,Spring 會(huì)自動(dòng)完成異常處理慨丐,將異常自動(dòng)映射到合適的狀態(tài)碼。
數(shù)據(jù)類型
Java 有哪些基本數(shù)據(jù)類型泄私?
JVM 沒有 boolean 賦值的專用字節(jié)碼指令房揭,boolean f = false 就是使用 ICONST_0 即常數(shù) 0 賦值。單個(gè) boolean 變量用 int 代替晌端,boolean 數(shù)組會(huì)編碼成 byte 數(shù)組捅暴。
自動(dòng)裝箱/拆箱是什么?
每個(gè)基本數(shù)據(jù)類型都對(duì)應(yīng)一個(gè)包裝類咧纠,除了 int 和 char 對(duì)應(yīng) Integer 和 Character 外蓬痒,其余基本數(shù)據(jù)類型的包裝類都是首字母大寫即可。
自動(dòng)裝箱: 將基本數(shù)據(jù)類型包裝為一個(gè)包裝類對(duì)象漆羔,例如向一個(gè)泛型為 Integer 的集合添加 int 元素梧奢。
自動(dòng)拆箱: 將一個(gè)包裝類對(duì)象轉(zhuǎn)換為一個(gè)基本數(shù)據(jù)類型,例如將一個(gè)包裝類對(duì)象賦值給一個(gè)基本數(shù)據(jù)類型的變量演痒。
比較兩個(gè)包裝類數(shù)值要用 equals 亲轨,而不能用 == 。
String 是不可變類為什么值可以修改嫡霞?
String 類和其存儲(chǔ)數(shù)據(jù)的成員變量 value 字節(jié)數(shù)組都是 final 修飾的瓶埋。對(duì)一個(gè) String 對(duì)象的任何修改實(shí)際上都是創(chuàng)建一個(gè)新 String 對(duì)象希柿,再引用該對(duì)象诊沪。只是修改 String 變量引用的對(duì)象养筒,沒有修改原 String 對(duì)象的內(nèi)容。
字符串拼接的方式有哪些端姚?
① 直接用 + 晕粪,底層用 StringBuilder 實(shí)現(xiàn)。只適用小數(shù)量渐裸,如果在循環(huán)中使用 + 拼接巫湘,相當(dāng)于不斷創(chuàng)建新的 StringBuilder 對(duì)象再轉(zhuǎn)換成 String 對(duì)象,效率極差昏鹃。
② 使用 String 的 concat 方法尚氛,該方法中使用 Arrays.copyOf 創(chuàng)建一個(gè)新的字符數(shù)組 buf 并將當(dāng)前字符串 value 數(shù)組的值拷貝到 buf 中,buf 長度 = 當(dāng)前字符串長度 + 拼接字符串長度洞渤。之后調(diào)用 getChars 方法使用 System.arraycopy 將拼接字符串的值也拷貝到 buf 數(shù)組阅嘶,最后用 buf 作為構(gòu)造參數(shù) new 一個(gè)新的 String 對(duì)象返回。效率稍高于直接使用 +载迄。
③ 使用 StringBuilder 或 StringBuffer讯柔,兩者的 append 方法都繼承自 AbstractStringBuilder,該方法首先使用 Arrays.copyOf 確定新的字符數(shù)組容量护昧,再調(diào)用 getChars 方法使用 System.arraycopy 將新的值追加到數(shù)組中魂迄。StringBuilder 是 JDK5 引入的,效率高但線程不安全惋耙。StringBuffer 使用 synchronized 保證線程安全捣炬。
String a = "a" + new String("b") 創(chuàng)建了幾個(gè)對(duì)象?
常量和常量拼接仍是常量绽榛,結(jié)果在常量池遥金,只要有變量參與拼接結(jié)果就是變量,存在堆蒜田。
使用字面量時(shí)只創(chuàng)建一個(gè)常量池中的常量稿械,使用 new 時(shí)如果常量池中沒有該值就會(huì)在常量池中新創(chuàng)建美莫,再在堆中創(chuàng)建一個(gè)對(duì)象引用常量池中常量襟铭。因此 String a = "a" + new String("b") 會(huì)創(chuàng)建四個(gè)對(duì)象寒砖,常量池中的 a 和 b哩都,堆中的 b 和堆中的 ab咐汞。
面向?qū)ο?/h1>
談一談你對(duì)面向?qū)ο蟮睦斫?/h4>
面向過程讓計(jì)算機(jī)有步驟地順序做一件事侯谁,是過程化思維墙贱,使用面向過程語言開發(fā)大型項(xiàng)目府寒,軟件復(fù)用和維護(hù)存在很大問題纵隔,模塊之間耦合嚴(yán)重捌刮。面向?qū)ο笙鄬?duì)面向過程更適合解決規(guī)模較大的問題个少,可以拆解問題復(fù)雜度糊探,對(duì)現(xiàn)實(shí)事物進(jìn)行抽象并映射為開發(fā)對(duì)象氨菇,更接近人的思維儡炼。
例如開門這個(gè)動(dòng)作,面向過程是 open(Door door)查蓉,動(dòng)賓結(jié)構(gòu)乌询,door 作為操作對(duì)象的參數(shù)傳入方法,方法內(nèi)定義開門的具體步驟豌研。面向?qū)ο蟮姆绞绞紫葧?huì)定義一個(gè)類 Door妹田,抽象出門的屬性(如尺寸、顏色)和行為(如 open 和 close)鹃共,主謂結(jié)構(gòu)鬼佣。
面向過程代碼松散,強(qiáng)調(diào)流程化解決問題霜浴。面向?qū)ο蟠a強(qiáng)調(diào)高內(nèi)聚晶衷、低耦合,先抽象模型定義共性行為阴孟,再解決實(shí)際問題房铭。
面向?qū)ο蟮娜筇匦裕?/h4>
封裝是對(duì)象功能內(nèi)聚的表現(xiàn)形式,在抽象基礎(chǔ)上決定信息是否公開及公開等級(jí)温眉,核心問題是以什么方式暴漏哪些信息缸匪。主要任務(wù)是對(duì)屬性、數(shù)據(jù)类溢、敏感行為實(shí)現(xiàn)隱藏凌蔬,對(duì)屬性的訪問和修改必須通過公共接口實(shí)現(xiàn)露懒。封裝使對(duì)象關(guān)系變得簡(jiǎn)單,降低了代碼耦合度砂心,方便維護(hù)懈词。
迪米特原則就是對(duì)封裝的要求,即 A 模塊使用 B 模塊的某接口行為辩诞,對(duì) B 模塊中除此行為外的其他信息知道得應(yīng)盡可能少坎弯。不直接對(duì) public 屬性進(jìn)行讀取和修改而使用 getter/setter 方法是因?yàn)榧僭O(shè)想在修改屬性時(shí)進(jìn)行權(quán)限控制、日志記錄等操作译暂,在直接訪問屬性的情況下無法實(shí)現(xiàn)抠忘。如果將 public 的屬性和行為修改為 private 一般依賴模塊都會(huì)報(bào)錯(cuò),因此不知道使用哪種權(quán)限時(shí)應(yīng)優(yōu)先使用 private外永。
繼承用來擴(kuò)展一個(gè)類崎脉,子類可繼承父類的部分屬性和行為使模塊具有復(fù)用性。繼承是"is-a"關(guān)系伯顶,可使用里氏替換原則判斷是否滿足"is-a"關(guān)系囚灼,即任何父類出現(xiàn)的地方子類都可以出現(xiàn)。如果父類引用直接使用子類引用來代替且可以正確編譯并執(zhí)行祭衩,輸出結(jié)果符合子類場(chǎng)景預(yù)期灶体,那么說明兩個(gè)類符合里氏替換原則。
多態(tài)以封裝和繼承為基礎(chǔ)掐暮,根據(jù)運(yùn)行時(shí)對(duì)象實(shí)際類型使同一行為具有不同表現(xiàn)形式蝎抽。多態(tài)指在編譯層面無法確定最終調(diào)用的方法體,在運(yùn)行期由 JVM 動(dòng)態(tài)綁定劫乱,調(diào)用合適的重寫方法织中。由于重載屬于靜態(tài)綁定,本質(zhì)上重載結(jié)果是完全不同的方法衷戈,因此多態(tài)一般專指重寫狭吼。
重載和重寫的區(qū)別?
重載指方法名稱相同殖妇,但參數(shù)類型個(gè)數(shù)不同刁笙,是行為水平方向不同實(shí)現(xiàn)。對(duì)編譯器來說谦趣,方法名稱和參數(shù)列表組成了一個(gè)唯一鍵疲吸,稱為方法簽名,JVM 通過方法簽名決定調(diào)用哪種重載方法前鹅。不管繼承關(guān)系如何復(fù)雜摘悴,重載在編譯時(shí)可以根據(jù)規(guī)則知道調(diào)用哪種目標(biāo)方法,因此屬于靜態(tài)綁定舰绘。
JVM 在重載方法中選擇合適方法的順序:① 精確匹配蹂喻。② 基本數(shù)據(jù)類型自動(dòng)轉(zhuǎn)換成更大表示范圍葱椭。③ 自動(dòng)拆箱與裝箱。④ 子類向上轉(zhuǎn)型口四。⑤ 可變參數(shù)孵运。
重寫指子類實(shí)現(xiàn)接口或繼承父類時(shí),保持方法簽名完全相同蔓彩,實(shí)現(xiàn)不同方法體治笨,是行為垂直方向不同實(shí)現(xiàn)豌熄。
元空間有一個(gè)方法表保存方法信息肉康,如果子類重寫了父類的方法,則方法表中的方法引用會(huì)指向子類實(shí)現(xiàn)滩租。父類引用執(zhí)行子類方法時(shí)無法調(diào)用子類存在而父類不存在的方法探膊。
重寫方法訪問權(quán)限不能變小杠愧,返回類型和拋出的異常類型不能變大待榔,必須加 @Override 逞壁。
類之間有哪些關(guān)系?
Object 類有哪些方法锐锣?
equals:檢測(cè)對(duì)象是否相等腌闯,默認(rèn)使用 == 比較對(duì)象引用,可以重寫 equals 方法自定義比較規(guī)則雕憔。equals 方法規(guī)范:自反性姿骏、對(duì)稱性、傳遞性斤彼、一致性分瘦、對(duì)于任何非空引用 x,x.equals(null) 返回 false琉苇。
hashCode:散列碼是由對(duì)象導(dǎo)出的一個(gè)整型值嘲玫,沒有規(guī)律,每個(gè)對(duì)象都有默認(rèn)散列碼并扇,值由對(duì)象存儲(chǔ)地址得出去团。字符串散列碼由內(nèi)容導(dǎo)出,值可能相同穷蛹。為了在集合中正確使用土陪,一般需要同時(shí)重寫 equals 和 hashCode,要求 equals 相同 hashCode 必須相同肴熏,hashCode 相同 equals 未必相同鬼雀,因此 hashCode 是對(duì)象相等的必要不充分條件。
toString:打印對(duì)象時(shí)默認(rèn)的方法蛙吏,如果沒有重寫打印的是表示對(duì)象值的一個(gè)字符串源哩。
clone:clone 方法聲明為 protected蹋肮,類只能通過該方法克隆它自己的對(duì)象,如果希望其他類也能調(diào)用該方法必須定義該方法為 public璧疗。如果一個(gè)對(duì)象的類沒有實(shí)現(xiàn) Cloneable 接口坯辩,該對(duì)象調(diào)用 clone 方***拋出一個(gè) CloneNotSupport 異常。默認(rèn)的 clone 方法是淺拷貝崩侠,一般重寫 clone 方法需要實(shí)現(xiàn) Cloneable 接口并指定訪問修飾符為 public漆魔。
finalize:確定一個(gè)對(duì)象死亡至少要經(jīng)過兩次標(biāo)記,如果對(duì)象在可達(dá)性分析后發(fā)現(xiàn)沒有與 GC Roots 連接的引用鏈會(huì)被第一次標(biāo)記却音,隨后進(jìn)行一次篩選改抡,條件是對(duì)象是否有必要執(zhí)行 finalize 方法。假如對(duì)象沒有重寫該方法或方法已被虛擬機(jī)調(diào)用系瓢,都視為沒有必要執(zhí)行阿纤。如果有必要執(zhí)行,對(duì)象會(huì)被放置在 F-Queue 隊(duì)列夷陋,由一條低調(diào)度優(yōu)先級(jí)的 Finalizer 線程去執(zhí)行欠拾。虛擬機(jī)會(huì)觸發(fā)該方法但不保證會(huì)結(jié)束,這是為了防止某個(gè)對(duì)象的 finalize 方法執(zhí)行緩慢或發(fā)生死循環(huán)骗绕。只要對(duì)象在 finalize 方法中重新與引用鏈上的對(duì)象建立關(guān)聯(lián)就會(huì)在第二次標(biāo)記時(shí)被移出回收集合藐窄。由于運(yùn)行代價(jià)高昂且無法保證調(diào)用順序,在 JDK 9 被標(biāo)記為過時(shí)方法酬土,并不適合釋放資源荆忍。
getClass:返回包含對(duì)象信息的類對(duì)象。
wait / notify / notifyAll:阻塞或喚醒持有該對(duì)象鎖的線程撤缴。
內(nèi)部類的作用是什么刹枉,有哪些分類?
內(nèi)部類可對(duì)同一包中其他類隱藏屈呕,內(nèi)部類方法可以訪問定義這個(gè)內(nèi)部類的作用域中的數(shù)據(jù)微宝,包括 private 數(shù)據(jù)。
內(nèi)部類是一個(gè)編譯器現(xiàn)象凉袱,與虛擬機(jī)無關(guān)芥吟。編譯器會(huì)把內(nèi)部類轉(zhuǎn)換成常規(guī)的類文件,用 $ 分隔外部類名與內(nèi)部類名专甩,其中匿名內(nèi)部類使用數(shù)字編號(hào)钟鸵,虛擬機(jī)對(duì)此一無所知。
靜態(tài)內(nèi)部類: 屬于外部類涤躲,只加載一次棺耍。作用域僅在包內(nèi),可通過 外部類名.內(nèi)部類名 直接訪問种樱,類內(nèi)只能訪問外部類所有靜態(tài)屬性和方法蒙袍。HashMap 的 Node 節(jié)點(diǎn)俊卤,ReentrantLock 中的 Sync 類,ArrayList 的 SubList 都是靜態(tài)內(nèi)部類害幅。內(nèi)部類中還可以定義內(nèi)部類消恍,如 ThreadLoacl 靜態(tài)內(nèi)部類 ThreadLoaclMap 中定義了內(nèi)部類 Entry。
成員內(nèi)部類: 屬于外部類的每個(gè)對(duì)象以现,隨對(duì)象一起加載狠怨。不可以定義靜態(tài)成員和方法,可訪問外部類的所有內(nèi)容邑遏。
局部內(nèi)部類: 定義在方法內(nèi)佣赖,不能聲明訪問修飾符,只能定義實(shí)例成員變量和實(shí)例方法记盒,作用范圍僅在聲明類的代碼塊中憎蛤。
匿名內(nèi)部類: 只用一次的沒有名字的類,可以簡(jiǎn)化代碼纪吮,創(chuàng)建的對(duì)象類型相當(dāng)于 new 的類的子類類型俩檬。用于實(shí)現(xiàn)事件監(jiān)聽和其他回調(diào)。
訪問權(quán)限控制符有哪些彬碱?
接口和抽象類的異同豆胸?
接口和抽象類對(duì)實(shí)體類進(jìn)行更高層次的抽象奥洼,僅定義公共行為和特征巷疼。
接口和抽象類應(yīng)該怎么選擇?
抽象類體現(xiàn) is-a 關(guān)系灵奖,接口體現(xiàn) can-do 關(guān)系嚼沿。與接口相比,抽象類通常是對(duì)同類事物相對(duì)具體的抽象瓷患。
抽象類是模板式設(shè)計(jì)骡尽,包含一組具體特征,例如某汽車擅编,底盤攀细、控制電路等是抽象出來的共同特征,但內(nèi)飾爱态、顯示屏谭贪、座椅材質(zhì)可以根據(jù)不同級(jí)別配置存在不同實(shí)現(xiàn)。
接口是契約式設(shè)計(jì)锦担,是開放的俭识,定義了方法名、參數(shù)洞渔、返回值套媚、拋出的異常類型缚态,誰都可以實(shí)現(xiàn)它,但必須遵守接口的約定堤瘤。例如所有車輛都必須實(shí)現(xiàn)剎車這種強(qiáng)制規(guī)范玫芦。
接口是頂級(jí)類,抽象類在接口下面的第二層本辐,對(duì)接口進(jìn)行了組合姨俩,然后實(shí)現(xiàn)部分接口。當(dāng)糾結(jié)定義接口和抽象類時(shí)师郑,推薦定義為接口环葵,遵循接口隔離原則,按維度劃分成多個(gè)接口宝冕,再利用抽象類去實(shí)現(xiàn)這些张遭,方便后續(xù)的擴(kuò)展和重構(gòu)。
例如 Plane 和 Bird 都有 fly 方法地梨,應(yīng)把 fly 定義為接口菊卷,而不是抽象類的抽象方法再繼承,因?yàn)槌?fly 行為外 Plane 和 Bird 間很難再找到其他共同特征宝剖。
子類初始化的順序
① 父類靜態(tài)代碼塊和靜態(tài)變量洁闰。② 子類靜態(tài)代碼塊和靜態(tài)變量。③ 父類普通代碼塊和普通變量万细。④ 父類構(gòu)造方法扑眉。⑤ 子類普通代碼塊和普通變量。⑥ 子類構(gòu)造方法赖钞。
集合
說一說 ArrayList
ArrayList 是容量可變的非線程安全列表腰素,使用數(shù)組實(shí)現(xiàn),集合擴(kuò)容時(shí)會(huì)創(chuàng)建更大的數(shù)組雪营,把原有數(shù)組復(fù)制到新數(shù)組弓千。支持對(duì)元素的快速隨機(jī)訪問,但插入與刪除速度很慢献起。ArrayList 實(shí)現(xiàn)了 RandomAcess 標(biāo)記接口洋访,如果一個(gè)類實(shí)現(xiàn)了該接口,那么表示使用索引遍歷比迭代器更快谴餐。
elementData是 ArrayList 的數(shù)據(jù)域姻政,被 transient 修飾,序列化時(shí)會(huì)調(diào)用 writeObject 寫入流总寒,反序列化時(shí)調(diào)用 readObject 重新賦值到新對(duì)象的 elementData扶歪。原因是 elementData 容量通常大于實(shí)際存儲(chǔ)元素的數(shù)量,所以只需發(fā)送真正有實(shí)際值的數(shù)組元素。
size 是當(dāng)前實(shí)際大小善镰,elementData 大小大于等于 size妹萨。
*modCount *記錄了 ArrayList 結(jié)構(gòu)性變化的次數(shù),繼承自 AbstractList炫欺。所有涉及結(jié)構(gòu)變化的方法都會(huì)增加該值乎完。expectedModCount 是迭代器初始化時(shí)記錄的 modCount 值,每次訪問新元素時(shí)都會(huì)檢查 modCount 和 expectedModCount 是否相等品洛,不相等就會(huì)拋出異常树姨。這種機(jī)制叫做 fail-fast,所有集合類都有這種機(jī)制桥状。
說一說 LinkedList
LinkedList 本質(zhì)是雙向鏈表帽揪,與 ArrayList 相比插入和刪除速度更快,但隨機(jī)訪問元素很慢辅斟。除繼承 AbstractList 外還實(shí)現(xiàn)了 Deque 接口转晰,這個(gè)接口具有隊(duì)列和棧的性質(zhì)。成員變量被 transient 修飾士飒,原理和 ArrayList 類似查邢。
LinkedList 包含三個(gè)重要的成員:size、first 和 last酵幕。size 是雙向鏈表中節(jié)點(diǎn)的個(gè)數(shù)扰藕,first 和 last 分別指向首尾節(jié)點(diǎn)的引用。
LinkedList 的優(yōu)點(diǎn)在于可以將零散的內(nèi)存單元通過附加引用的方式關(guān)聯(lián)起來芳撒,形成按鏈路順序查找的線性結(jié)構(gòu)邓深,內(nèi)存利用率較高。
Set 有什么特點(diǎn)番官,有哪些實(shí)現(xiàn)庐完?
Set 不允許元素重復(fù)且無序,常用實(shí)現(xiàn)有 HashSet徘熔、LinkedHashSet 和 TreeSet。
HashSet 通過 HashMap 實(shí)現(xiàn)淆党,HashMap 的 Key 即 HashSet 存儲(chǔ)的元素酷师,所有 Key 都使用相同的 Value ,一個(gè)名為 PRESENT 的 Object 類型常量染乌。使用 Key 保證元素唯一性山孔,但不保證有序性。由于 HashSet 是 HashMap 實(shí)現(xiàn)的荷憋,因此線程不安全台颠。
HashSet 判斷元素是否相同時(shí),對(duì)于包裝類型直接按值比較。對(duì)于引用類型先比較 hashCode 是否相同串前,不同則代表不是同一個(gè)對(duì)象瘫里,相同則繼續(xù)比較 equals,都相同才是同一個(gè)對(duì)象荡碾。
LinkedHashSet 繼承自 HashSet谨读,通過 LinkedHashMap 實(shí)現(xiàn),使用雙向鏈表維護(hù)元素插入順序坛吁。
TreeSet 通過 TreeMap 實(shí)現(xiàn)的劳殖,添加元素到集合時(shí)按照比較規(guī)則將其插入合適的位置,保證插入后的集合仍然有序拨脉。
TreeMap 有什么特點(diǎn)哆姻?
TreeMap 基于紅黑樹實(shí)現(xiàn),增刪改查的平均和最差時(shí)間復(fù)雜度均為 O(logn) 玫膀,最大特點(diǎn)是 Key 有序填具。Key 必須實(shí)現(xiàn) Comparable 接口或提供的 Comparator 比較器,所以 Key 不允許為 null匆骗。
HashMap 依靠 hashCode 和 equals 去重劳景,而 TreeMap 依靠 Comparable 或 Comparator。 TreeMap 排序時(shí)碉就,如果比較器不為空就會(huì)優(yōu)先使用比較器的 compare 方法盟广,否則使用 Key 實(shí)現(xiàn)的 Comparable 的 compareTo 方法,兩者都不滿足會(huì)拋出異常瓮钥。
TreeMap 通過 put 和 deleteEntry 實(shí)現(xiàn)增加和刪除樹節(jié)點(diǎn)筋量。插入新節(jié)點(diǎn)的規(guī)則有三個(gè):① 需要調(diào)整的新節(jié)點(diǎn)總是紅色的。② 如果插入新節(jié)點(diǎn)的父節(jié)點(diǎn)是黑色的碉熄,不需要調(diào)整桨武。③ 如果插入新節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色的,由于紅黑樹不能出現(xiàn)相鄰紅色锈津,進(jìn)入循環(huán)判斷呀酸,通過重新著色或左右旋轉(zhuǎn)來調(diào)整。TreeMap 的插入操作就是按照 Key 的對(duì)比往下遍歷琼梆,大于節(jié)點(diǎn)值向右查找性誉,小于向左查找,先按照二叉查找樹的特性操作茎杂,后續(xù)會(huì)重新著色和旋轉(zhuǎn)错览,保持紅黑樹的特性。
HashMap 有什么特點(diǎn)煌往?
JDK8 之前底層實(shí)現(xiàn)是數(shù)組 + 鏈表倾哺,JDK8 改為數(shù)組 + 鏈表/紅黑樹,節(jié)點(diǎn)類型從Entry 變更為 Node。主要成員變量包括存儲(chǔ)數(shù)據(jù)的 table 數(shù)組羞海、元素?cái)?shù)量 size忌愚、加載因子 loadFactor。
table 數(shù)組記錄 HashMap 的數(shù)據(jù)扣猫,每個(gè)下標(biāo)對(duì)應(yīng)一條鏈表菜循,所有哈希沖突的數(shù)據(jù)都會(huì)被存放到同一條鏈表,Node/Entry 節(jié)點(diǎn)包含四個(gè)成員變量:key申尤、value癌幕、next 指針和 hash 值。
HashMap 中數(shù)據(jù)以鍵值對(duì)的形式存在昧穿,鍵對(duì)應(yīng)的 hash 值用來計(jì)算數(shù)組下標(biāo)勺远,如果兩個(gè)元素 key 的 hash 值一樣,就會(huì)發(fā)生哈希沖突时鸵,被放到同一個(gè)鏈表上胶逢,為使查詢效率盡可能高,鍵的 hash 值要盡可能分散饰潜。
HashMap 默認(rèn)初始化容量為 16初坠,擴(kuò)容容量必須是 2 的冪次方、最大容量為 1<< 30 彭雾、默認(rèn)加載因子為 0.75碟刺。
Q6:HashMap 相關(guān)方法的源碼?
JDK8 之前
hash:計(jì)算元素 key 的散列值
① 處理 String 類型時(shí)薯酝,調(diào)用 stringHash32 方法獲取 hash 值半沽。
② 處理其他類型數(shù)據(jù)時(shí),提供一個(gè)相對(duì)于 HashMap 實(shí)例唯一不變的隨機(jī)值 hashSeed 作為計(jì)算初始量吴菠。
③ 執(zhí)行異或和無符號(hào)右移使 hash 值更加離散者填,減小哈希沖突概率。
indexFor:計(jì)算元素下標(biāo)
將 hash 值和數(shù)組長度-1 進(jìn)行與操作做葵,保證結(jié)果不會(huì)超過 table 數(shù)組范圍占哟。
get:獲取元素的 value 值
① 如果 key 為 null,調(diào)用 getForNullKey 方法蜂挪,如果 size 為 0 表示鏈表為空重挑,返回 null。如果 size 不為 0 說明存在鏈表棠涮,遍歷 table[0] 鏈表,如果找到了 key 為 null 的節(jié)點(diǎn)則返回其 value刺覆,否則返回 null严肪。
② 如果 key 為 不為 null,調(diào)用 getEntry 方法,如果 size 為 0 表示鏈表為空驳糯,返回 null 值篇梭。如果 size 不為 0,首先計(jì)算 key 的 hash 值酝枢,然后遍歷該鏈表的所有節(jié)點(diǎn)恬偷,如果節(jié)點(diǎn)的 key 和 hash 值都和要查找的元素相同則返回其 Entry 節(jié)點(diǎn)。
③ 如果找到了對(duì)應(yīng)的 Entry 節(jié)點(diǎn)帘睦,調(diào)用 getValue 方法獲取其 value 并返回袍患,否則返回 null。
put:添加元素
① 如果 key 為 null竣付,直接存入 table[0]诡延。
② 如果 key 不為 null,計(jì)算 key 的 hash 值古胆。
③ 調(diào)用 indexFor 計(jì)算元素存放的下標(biāo) i肆良。
④ 遍歷 table[i] 對(duì)應(yīng)的鏈表,如果 key 已存在逸绎,就更新 value 然后返回舊 value惹恃。
⑤ 如果 key 不存在,將 modCount 值加 1棺牧,使用 addEntry 方法增加一個(gè)節(jié)點(diǎn)并返回 null巫糙。
resize:擴(kuò)容數(shù)組
① 如果當(dāng)前容量達(dá)到了最大容量,將閾值設(shè)置為 Integer 最大值陨帆,之后擴(kuò)容不再觸發(fā)曲秉。
② 否則計(jì)算新的容量,將閾值設(shè)為 newCapacity x loadFactor 和 最大容量 + 1 的較小值疲牵。
③ 創(chuàng)建一個(gè)容量為 newCapacity 的 Entry 數(shù)組承二,調(diào)用 transfer 方法將舊數(shù)組的元素轉(zhuǎn)移到新數(shù)組。
transfer:轉(zhuǎn)移元素
① 遍歷舊數(shù)組的所有元素纲爸,調(diào)用 rehash 方法判斷是否需要哈希重構(gòu)亥鸠,如果需要就重新計(jì)算元素 key 的 hash 值。
② 調(diào)用 indexFor 方法計(jì)算元素存放的下標(biāo) i识啦,利用頭插法將舊數(shù)組的元素轉(zhuǎn)移到新數(shù)組负蚊。
JDK8
hash:計(jì)算元素 key 的散列值
如果 key 為 null 返回 0,否則就將 key 的 hashCode 方法返回值高低16位異或颓哮,讓盡可能多的位參與運(yùn)算家妆,讓結(jié)果的 0 和 1 分布更加均勻,降低哈希沖突概率冕茅。
put:添加元素
① 調(diào)用 putVal 方法添加元素伤极。
② 如果 table 為空或長度為 0 就進(jìn)行擴(kuò)容蛹找,否則計(jì)算元素下標(biāo)位置,不存在就調(diào)用 newNode 創(chuàng)建一個(gè)節(jié)點(diǎn)哨坪。
③ 如果存在且是鏈表庸疾,如果首節(jié)點(diǎn)和待插入元素的 hash 和 key 都一樣,更新節(jié)點(diǎn)的 value当编。
④ 如果首節(jié)點(diǎn)是 TreeNode 類型届慈,調(diào)用 putTreeVal 方法增加一個(gè)樹節(jié)點(diǎn),每一次都比較插入節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)的大小忿偷,待插入節(jié)點(diǎn)小就往左子樹查找金顿,否則往右子樹查找,找到空位后執(zhí)行兩個(gè)方法:balanceInsert 方法牵舱,插入節(jié)點(diǎn)并調(diào)整平衡串绩、moveRootToFront 方法,由于調(diào)整平衡后根節(jié)點(diǎn)可能變化芜壁,需要重置根節(jié)點(diǎn)礁凡。
⑤ 如果都不滿足,遍歷鏈表慧妄,根據(jù) hash 和 key 判斷是否重復(fù)顷牌,決定更新 value 還是新增節(jié)點(diǎn)。如果遍歷到了鏈表末尾則添加節(jié)點(diǎn)塞淹,如果達(dá)到建樹閾值 7窟蓝,還需要調(diào)用 treeifyBin 把鏈表重構(gòu)為紅黑樹。
⑥ 存放元素后將 modCount 加 1饱普,如果 ++size > threshold 运挫,調(diào)用 resize 擴(kuò)容。
get :獲取元素的 value 值
① 調(diào)用 getNode 方法獲取 Node 節(jié)點(diǎn)套耕,如果不是 null 就返回其 value 值谁帕,否則返回 null。
② getNode 方法中如果數(shù)組不為空且存在元素冯袍,先比較第一個(gè)節(jié)點(diǎn)和要查找元素的 hash 和 key 匈挖,如果都相同則直接返回。
③ 如果第二個(gè)節(jié)點(diǎn)是 TreeNode 類型則調(diào)用 getTreeNode 方法進(jìn)行查找康愤,否則遍歷鏈表根據(jù) hash 和 key 查找儡循,如果沒有找到就返回 null。
resize:擴(kuò)容數(shù)組
重新規(guī)劃長度和閾值征冷,如果長度發(fā)生了變化择膝,部分?jǐn)?shù)據(jù)節(jié)點(diǎn)也要重新排列。
重新規(guī)劃長度
① 如果當(dāng)前容量 oldCap > 0 且達(dá)到最大容量检激,將閾值設(shè)為 Integer 最大值调榄,return 終止擴(kuò)容踊赠。
② 如果未達(dá)到最大容量呵扛,當(dāng) oldCap << 1 不超過最大容量就擴(kuò)大為 2 倍每庆。
③ 如果都不滿足且當(dāng)前擴(kuò)容閾值 oldThr > 0,使用當(dāng)前擴(kuò)容閾值作為新容量今穿。
④ 否則將新容量置為默認(rèn)初始容量 16缤灵,新擴(kuò)容閾值置為 12。
重新排列數(shù)據(jù)節(jié)點(diǎn)
① 如果節(jié)點(diǎn)為 null 不進(jìn)行處理蓝晒。
② 如果節(jié)點(diǎn)不為 null 且沒有next節(jié)點(diǎn)腮出,那么通過節(jié)點(diǎn)的 hash 值和 新容量-1 進(jìn)行與運(yùn)算計(jì)算下標(biāo)存入新的 table 數(shù)組。
③ 如果節(jié)點(diǎn)為 TreeNode 類型芝薇,調(diào)用 split 方法處理胚嘲,如果節(jié)點(diǎn)數(shù) hc 達(dá)到6 會(huì)調(diào)用 untreeify 方法轉(zhuǎn)回鏈表。
④ 如果是鏈表節(jié)點(diǎn)洛二,需要將鏈表拆分為 hash 值超出舊容量的鏈表和未超出容量的鏈表馋劈。對(duì)于hash & oldCap == 0 的部分不需要做處理,否則需要放到新的下標(biāo)位置上晾嘶,新下標(biāo) = 舊下標(biāo) + 舊容量妓雾。
HashMap 為什么線程不安全?
JDK7 存在死循環(huán)和數(shù)據(jù)丟失問題垒迂。
數(shù)據(jù)丟失:
并發(fā)賦值被覆蓋: 在 createEntry 方法中械姻,新添加的元素直接放在頭部,使元素之后可以被更快訪問机断,但如果兩個(gè)線程同時(shí)執(zhí)行到此處楷拳,會(huì)導(dǎo)致其中一個(gè)線程的賦值被覆蓋。
已遍歷區(qū)間新增元素丟失: 當(dāng)某個(gè)線程在 transfer 方法遷移時(shí)吏奸,其他線程新增的元素可能落在已遍歷過的哈希槽上欢揖。遍歷完成后,table 數(shù)組引用指向了 newTable苦丁,新增元素丟失浸颓。
新表被覆蓋: 如果 resize 完成,執(zhí)行了 table = newTable旺拉,則后續(xù)元素就可以在新表上進(jìn)行插入产上。但如果多線程同時(shí) resize ,每個(gè)線程都會(huì) new 一個(gè)數(shù)組蛾狗,這是線程內(nèi)的局部對(duì)象晋涣,線程之間不可見。遷移完成后resize 的線程會(huì)賦值給 table 線程共享變量沉桌,可能會(huì)覆蓋其他線程的操作谢鹊,在新表中插入的對(duì)象都會(huì)被丟棄算吩。
死循環(huán): 擴(kuò)容時(shí) resize 調(diào)用 transfer 使用頭插法遷移元素,雖然 newTable 是局部變量佃扼,但原先 table 中的 Entry 鏈表是共享的偎巢,問題根源是 Entry 的 next 指針并發(fā)修改,某線程還沒有將 table 設(shè)為 newTable 時(shí)用完了 CPU 時(shí)間片兼耀,導(dǎo)致數(shù)據(jù)丟失或死循環(huán)压昼。
JDK8 在 resize 方法中完成擴(kuò)容,并改用尾插法瘤运,不會(huì)產(chǎn)生死循環(huán)窍霞,但并發(fā)下仍可能丟失數(shù)據(jù)≌兀可用 ConcurrentHashMap 或 Collections.synchronizedMap 包裝成同步集合但金。
IO 流
同步/異步/阻塞/非阻塞 IO 的區(qū)別?
同步和異步是通信機(jī)制郁季,阻塞和非阻塞是調(diào)用狀態(tài)冷溃。
同步 IO 是用戶線程發(fā)起 IO 請(qǐng)求后需要等待或輪詢內(nèi)核 IO 操作完成后才能繼續(xù)執(zhí)行。異步 IO 是用戶線程發(fā)起 IO 請(qǐng)求后可以繼續(xù)執(zhí)行巩踏,當(dāng)內(nèi)核 IO 操作完成后會(huì)通知用戶線程秃诵,或調(diào)用用戶線程注冊(cè)的回調(diào)函數(shù)。
阻塞 IO 是 IO 操作需要徹底完成后才能返回用戶空間 塞琼。非阻塞 IO 是 IO 操作調(diào)用后立即返回一個(gè)狀態(tài)值菠净,無需等 IO 操作徹底完成。
什么是 BIO彪杉?
BIO 是同步阻塞式 IO毅往,JDK1.4 之前的 IO 模型。服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接請(qǐng)求對(duì)應(yīng)一個(gè)線程派近,服務(wù)器需要為每一個(gè)客戶端請(qǐng)求創(chuàng)建一個(gè)線程攀唯,如果這個(gè)連接不做任何事會(huì)造成不必要的線程開銷】释瑁可以通過線程池改善侯嘀,這種 IO 稱為偽異步 IO。適用連接數(shù)目少且服務(wù)器資源多的場(chǎng)景谱轨。
什么是 NIO戒幔?
NIO 是 JDK1.4 引入的同步非阻塞 IO。服務(wù)器實(shí)現(xiàn)模式為多個(gè)連接請(qǐng)求對(duì)應(yīng)一個(gè)線程土童,客戶端連接請(qǐng)求會(huì)注冊(cè)到一個(gè)多路復(fù)用器 Selector 诗茎,Selector 輪詢到連接有 IO 請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程處理。適用連接數(shù)目多且連接時(shí)間短的場(chǎng)景献汗。
同步是指線程還是要不斷接收客戶端連接并處理數(shù)據(jù)敢订,非阻塞是指如果一個(gè)管道沒有數(shù)據(jù)王污,不需要等待,可以輪詢下一個(gè)管道楚午。
核心組件:
Selector: 多路復(fù)用器昭齐,輪詢檢查多個(gè) Channel 的狀態(tài),判斷注冊(cè)事件是否發(fā)生醒叁,即判斷 Channel 是否處于可讀或可寫狀態(tài)司浪。使用前需要將 Channel 注冊(cè)到 Selector,注冊(cè)后會(huì)得到一個(gè) SelectionKey把沼,通過 SelectionKey 獲取 Channel 和 Selector 相關(guān)信息。
Channel: 雙向通道吁伺,替換了 BIO 中的 Stream 流饮睬,不能直接訪問數(shù)據(jù),要通過 Buffer 來讀寫數(shù)據(jù)篮奄,也可以和其他 Channel 交互捆愁。
Buffer: 緩沖區(qū),本質(zhì)是一塊可讀寫數(shù)據(jù)的內(nèi)存窟却,用來簡(jiǎn)化數(shù)據(jù)讀寫昼丑。Buffer 三個(gè)重要屬性:position 下次讀寫數(shù)據(jù)的位置,limit 本次讀寫的極限位置夸赫,capacity 最大容量菩帝。
flip 將寫轉(zhuǎn)為讀,底層實(shí)現(xiàn)原理把 position 置 0茬腿,并把 limit 設(shè)為當(dāng)前的 position 值呼奢。
clear 將讀轉(zhuǎn)為寫模式(用于讀完全部數(shù)據(jù)的情況,把 position 置 0切平,limit 設(shè)為 capacity)握础。
compact 將讀轉(zhuǎn)為寫模式(用于存在未讀數(shù)據(jù)的情況,讓 position 指向未讀數(shù)據(jù)的下一個(gè))悴品。
通道方向和 Buffer 方向相反禀综,讀數(shù)據(jù)相當(dāng)于向 Buffer 寫,寫數(shù)據(jù)相當(dāng)于從 Buffer 讀苔严。
使用步驟:向 Buffer 寫數(shù)據(jù)定枷,調(diào)用 flip 方法轉(zhuǎn)為讀模式,從 Buffer 中讀數(shù)據(jù)邦蜜,調(diào)用 clear 或 compact 方法清空緩沖區(qū)依鸥。
什么是 AIO?
AIO 是 JDK7 引入的異步非阻塞 IO悼沈。服務(wù)器實(shí)現(xiàn)模式為一個(gè)有效請(qǐng)求對(duì)應(yīng)一個(gè)線程贱迟,客戶端的 IO 請(qǐng)求都是由操作系統(tǒng)先完成 IO 操作后再通知服務(wù)器應(yīng)用來直接使用準(zhǔn)備好的數(shù)據(jù)姐扮。適用連接數(shù)目多且連接時(shí)間長的場(chǎng)景。
異步是指服務(wù)端線程接收到客戶端管道后就交給底層處理IO通信衣吠,自己可以做其他事情茶敏,非阻塞是指客戶端有數(shù)據(jù)才會(huì)處理,處理好再通知服務(wù)器缚俏。
實(shí)現(xiàn)方式包括通過 Future 的 get 方法進(jìn)行阻塞式調(diào)用以及實(shí)現(xiàn) CompletionHandler 接口惊搏,重寫請(qǐng)求成功的回調(diào)方法 completed 和請(qǐng)求失敗回調(diào)方法 failed。
java.io 包下有哪些流忧换?
主要分為字符流和字節(jié)流恬惯,字符流一般用于文本文件,字節(jié)流一般用于圖像或其他文件亚茬。
字符流包括了字符輸入流 Reader 和字符輸出流 Writer酪耳,字節(jié)流包括了字節(jié)輸入流 InputStream 和字節(jié)輸出流 OutputStream。字符流和字節(jié)流都有對(duì)應(yīng)的緩沖流刹缝,字節(jié)流也可以包裝為字符流碗暗,緩沖流帶有一個(gè) 8KB 的緩沖數(shù)組,可以提高流的讀寫效率梢夯。除了緩沖流外還有過濾流 FilterReader言疗、字符數(shù)組流 CharArrayReader、字節(jié)數(shù)組流 ByteArrayInputStream颂砸、文件流 FileInputStream 等噪奄。
序列化和反序列化是什么?
Java 對(duì)象 JVM 退出時(shí)會(huì)全部銷毀沾凄,如果需要將對(duì)象及狀態(tài)持久化梗醇,就要通過序列化實(shí)現(xiàn),將內(nèi)存中的對(duì)象保存在二進(jìn)制流中撒蟀,需要時(shí)再將二進(jìn)制流反序列化為對(duì)象叙谨。對(duì)象序列化保存的是對(duì)象的狀態(tài),因此屬于類屬性的靜態(tài)變量不會(huì)被序列化保屯。
常見的序列化有三種:
Java 原生序列化
實(shí)現(xiàn) Serializabale 標(biāo)記接口手负,Java 序列化保留了對(duì)象類的元數(shù)據(jù)(如類、成員變量姑尺、繼承類信息)以及對(duì)象數(shù)據(jù)竟终,兼容性最好,但不支持跨語言切蟋,性能一般统捶。序列化和反序列化必須保持序列化 ID 的一致,一般使用 private static final long serialVersionUID 定義序列化 ID,如果不設(shè)置編譯器會(huì)根據(jù)類的內(nèi)部實(shí)現(xiàn)自動(dòng)生成該值喘鸟。如果是兼容升級(jí)不應(yīng)該修改序列化 ID匆绣,防止出錯(cuò),如果是不兼容升級(jí)則需要修改什黑。
Hessian 序列化
Hessian 序列化是一種支持動(dòng)態(tài)類型崎淳、跨語言、基于對(duì)象傳輸?shù)木W(wǎng)絡(luò)協(xié)議愕把。Java 對(duì)象序列化的二進(jìn)制流可以被其它語言反序列化拣凹。Hessian 協(xié)議的特性:① 自描述序列化類型,不依賴外部描述文件恨豁,用一個(gè)字節(jié)表示常用基礎(chǔ)類型嚣镜,極大縮短二進(jìn)制流。② 語言無關(guān)圣絮,支持腳本語言祈惶。③ 協(xié)議簡(jiǎn)單,比 Java 原生序列化高效扮匠。Hessian 會(huì)把復(fù)雜對(duì)象所有屬性存儲(chǔ)在一個(gè) Map 中序列化,當(dāng)父類和子類存在同名成員變量時(shí)會(huì)先序列化子類再序列化父類凡涩,因此子類值會(huì)被父類覆蓋棒搜。
JSON 序列化
JSON 序列化就是將數(shù)據(jù)對(duì)象轉(zhuǎn)換為 JSON 字符串,在序列化過程中拋棄了類型信息活箕,所以反序列化時(shí)只有提供類型信息才能準(zhǔn)確進(jìn)行力麸。相比前兩種方式可讀性更好,方便調(diào)試育韩。
序列化通常會(huì)使用網(wǎng)絡(luò)傳輸對(duì)象克蚂,而對(duì)象中往往有敏感數(shù)據(jù),容易遭受攻擊筋讨,Jackson 和 fastjson 等都出現(xiàn)過反序列化漏洞埃叭,因此不需要進(jìn)行序列化的敏感屬性傳輸時(shí)應(yīng)加上 transient 關(guān)鍵字。transient 的作用就是把變量生命周期僅限于內(nèi)存而不會(huì)寫到磁盤里持久化悉罕,變量會(huì)被設(shè)為對(duì)應(yīng)數(shù)據(jù)類型的零值赤屋。
總結(jié)
感謝你能看到這里!由于篇幅原因壁袄,知識(shí)點(diǎn)無法在一篇文章更完类早!整篇知識(shí)點(diǎn)大概包括了Spring,Dubbo嗜逻,MyBatis, RPC, 源碼分析涩僻,高并發(fā)、高性能、分布式,性能優(yōu)化逆日,微服務(wù) 高級(jí)架構(gòu)開發(fā)等等嵌巷!我會(huì)盡快把剩下來的兩篇更完,另外這份知識(shí)點(diǎn)我已經(jīng)整理成了PDF文檔屏富!
需要提前觀看的可以關(guān)注公眾號(hào):前程有光自行領(lǐng)惹缇骸!