Java 基礎 40
語言特性 12
Q1:Java 語言的優勢?
① 平臺無關性,擺脫硬件束縛,"一次編寫,處處運行"。
② 相對安全的內存管理和訪問機制,避免大部份內存泄漏和指針越界。
③ 熱點代碼檢測和運行時編譯及優化,使程序隨運行時間增加得到更高性能。
④ 完善的應用程序接口,支持第三方類庫。
Q2:Java 如何實現平臺無關?
JVM: Java 編譯器可生成與計算機體系結構無關的字節碼指令,字節碼文件不只能夠輕易地在任何機器上解釋執行,還能夠動態地轉換成本地機器代碼,轉換是由 JVM 實現的,JVM 是平臺相關的,屏蔽了不一樣操做系統的差別。
語言規範: 基本數據類型大小有明確規定,例如 int 永遠爲 32 位,而 C/C++ 中多是 16 位、32 位,也多是編譯器開發商指定的其餘大小。Java 中數值類型有固定字節數,二進制數據以固定格式存儲和傳輸,字符串採用標準的 Unicode 格式存儲。
Q3:JDK 和 JRE 的區別?
JDK: Java Development Kit,開發工具包。提供了編譯運行 Java 程序的各類工具,包括編譯器、JRE 及經常使用類庫,是 JAVA 核心。
JRE: Java Runtime Environment,運行時環境,運行 Java 程序的必要環境,包括 JVM、核心類庫、核心配置工具。
Q4:Java 按值調用仍是引用調用?
按值調用指方法接收調用者提供的值,按引用調用指方法接收調用者提供的變量地址。
Java 老是按值調用,方法獲得的是全部參數值的副本,傳遞對象時實際上方法接收的是對象引用的副本。方法不能修改基本數據類型的參數,若是傳遞了一個 int 值 ,改變值不會影響實參,由於改變的是值的一個副本。
能夠改變對象參數的狀態,但不能讓對象參數引用一個新的對象。若是傳遞了一個 int 數組,改變數組的內容會影響實參,而改變這個參數的引用並不會讓實參引用新的數組對象。
Q5:淺拷貝和深拷貝的區別?
淺拷貝: 只複製當前對象的基本數據類型及引用變量,沒有複製引用變量指向的實際對象。修改克隆對象可能影響原對象,不安全。
深拷貝: 徹底拷貝基本數據類型和引用數據類型,安全。
Q6:什麼是反射?
在運行狀態中,對於任意一個類都能知道它的全部屬性和方法,對於任意一個對象都能調用它的任意方法和屬性,這種動態獲取信息及調用對象方法的功能稱爲反射。缺點是破壞了封裝性以及泛型約束。反射是框架的核心,Spring 大量使用反射。
Q7:Class 類的做用?如何獲取一個 Class 對象?
在程序運行期間,Java 運行時系統爲全部對象維護一個運行時類型標識,這個信息會跟蹤每一個對象所屬的類,虛擬機利用運行時類型信息選擇要執行的正確方法,保存這些信息的類就是 Class,這是一個泛型類。
獲取 Class 對象:① 類名.class 。②對象的 getClass方法。③ Class.forName(類的全限定名)。
Q8:什麼是註解?什麼是元註解?
註解是一種標記,使類或接口附加額外信息,幫助編譯器和 JVM 完成一些特定功能,例如 @Override 標識一個方法是重寫方法。
元註解是自定義註解的註解,例如:
@Target:約束做用位置,值是 ElementType 枚舉常量,包括 METHOD 方法、VARIABLE 變量、TYPE 類/接口、PARAMETER 方法參數、CONSTRUCTORS 構造方法和 LOACL_VARIABLE 局部變量等。
@Rentention:約束生命週期,值是 RetentionPolicy 枚舉常量,包括 SOURCE 源碼、CLASS 字節碼和 RUNTIME 運行時。
@Documented:代表這個註解應該被 javadoc 記錄。
Q9:什麼是泛型,有什麼做用?
泛型本質是參數化類型,解決不肯定對象具體類型的問題。泛型在定義處只具有執行 Object 方法的能力。
泛型的好處:① 類型安全,放置什麼出來就是什麼,不存在 ClassCastException。② 提高可讀性,編碼階段就顯式知道泛型集合、泛型方法等處理的對象類型。③ 代碼重用,合併了同類型的處理代碼。
Q10:泛型擦除是什麼?
泛型用於編譯階段,編譯後的字節碼文件不包含泛型類型信息,由於虛擬機沒有泛型類型對象,全部對象都屬於普通類。例如定義 List 或 List,在編譯後都會變成 List 。
定義一個泛型類型,會自動提供一個對應原始類型,類型變量會被擦除。若是沒有限定類型就會替換爲 Object,若是有限定類型就會替換爲第一個限定類型,例如 `` 會使用 A 類型替換 T。
Q11:JDK8 新特性有哪些?
lambda 表達式:容許把函數做爲參數傳遞到方法,簡化匿名內部類代碼。
函數式接口:使用 @FunctionalInterface 標識,有且僅有一個抽象方法,可被隱式轉換爲 lambda 表達式。
方法引用:能夠引用已有類或對象的方法和構造方法,進一步簡化 lambda 表達式。
接口:接口能夠定義 default 修飾的默認方法,下降了接口升級的複雜性,還能夠定義靜態方法。
註解:引入重複註解機制,相同註解在同地方能夠聲明屢次。註解做用範圍也進行了擴展,可做用於局部變量、泛型、方法異常等。
類型推測:增強了類型推測機制,使代碼更加簡潔。
Optional 類:處理空指針異常,提升代碼可讀性。
Stream 類:引入函數式編程風格,提供了不少功能,使代碼更加簡潔。方法包括 forEach 遍歷、count 統計個數、filter 按條件過濾、limit 取前 n 個元素、skip 跳過前 n 個元素、map 映射加工、concat 合併 stream 流等。
日期:加強了日期和時間 API,新的 java.time 包主要包含了處理日期、時間、日期/時間、時區、時刻和時鐘等操做。
JavaScript:提供了一個新的 JavaScript 引擎,容許在 JVM上運行特定 JavaScript 應用。
Q12:異常有哪些分類?
全部異常都是 Throwable 的子類,分爲 Error 和 Exception。Error 是 Java 運行時系統的內部錯誤和資源耗盡錯誤,例如 StackOverFlowError 和 OutOfMemoryError,這種異常程序沒法處理。
Exception 分爲受檢異常和非受檢異常,受檢異常須要在代碼中顯式處理,不然會編譯出錯,非受檢異常是運行時異常,繼承自 RuntimeException。
受檢異常:① 無能爲力型,如字段超長致使的 SQLException。② 力所能及型,如未受權異常 UnAuthorizedException,程序可跳轉權限申請頁面。常見受檢異常還有 FileNotFoundException、ClassNotFoundException、IOException等。
非受檢異常:① 可預測異常,例如 IndexOutOfBoundsException、NullPointerException、ClassCastException 等,這類異常應該提早處理。② 需捕捉異常,例如進行 RPC 調用時的遠程服務超時,這類異常客戶端必須顯式處理。③ 可透出異常,指框架或系統產生的且會自行處理的異常,例如 Spring 的 NoSuchRequestHandingMethodException,Spring 會自動完成異常處理,將異常自動映射到合適的狀態碼。
數據類型 5
Q1:Java 有哪些基本數據類型?
數據類型 內存大小 默認值 取值範圍
字節 1個 (字節)0 -128 ~ 127
短 2個 (短)0 -215 ~ 215-1
整型 4個 0 -231 ~ 231-1
long 8層 0升 -263 ~ 263-1
浮動 4個 0.0F ±3.4E+38(有效位數 6~7 位)
雙 8層 0.0D ±1.7E+308(有效位數 15 位)
燒焦 英文 1B,中文 UTF-8 佔 3B,GBK 佔 2B。 '\ u0000' '\ u0000'〜'\ uFFFF'
布爾值 單個變量 4B / 數組 1B 假 真假
JVM 沒有 boolean 賦值的專用字節碼指令,boolean f = false 就是使用 ICONST_0 即常數 0 賦值。單個 boolean 變量用 int 代替,boolean 數組會編碼成 byte 數組。
Q2:自動裝箱/拆箱是什麼?
每一個基本數據類型都對應一個包裝類,除了 int 和 char 對應 Integer 和 Character 外,其他基本數據類型的包裝類都是首字母大寫便可。
自動裝箱: 將基本數據類型包裝爲一個包裝類對象,例如向一個泛型爲 Integer 的集合添加 int 元素。
自動拆箱: 將一個包裝類對象轉換爲一個基本數據類型,例如將一個包裝類對象賦值給一個基本數據類型的變量。
比較兩個包裝類數值要用 equals ,而不能用 == 。
Q3:String 是不可變類爲何值能夠修改?
String 類和其存儲數據的成員變量 value 字節數組都是 final 修飾的。對一個 String 對象的任何修改實際上都是建立一個新 String 對象,再引用該對象。只是修改 String 變量引用的對象,沒有修改原 String 對象的內容。
Q4:字符串拼接的方式有哪些?
① 直接用 + ,底層用 StringBuilder 實現。只適用小數量,若是在循環中使用 + 拼接,至關於不斷建立新的 StringBuilder 對象再轉換成 String 對象,效率極差。
② 使用 String 的 concat 方法,該方法中使用 Arrays.copyOf 建立一個新的字符數組 buf 並將當前字符串 value 數組的值拷貝到 buf 中,buf 長度 = 當前字符串長度 + 拼接字符串長度。以後調用 getChars 方法使用 System.arraycopy 將拼接字符串的值也拷貝到 buf 數組,最後用 buf 做爲構造參數 new 一個新的 String 對象返回。效率稍高於直接使用 +。
③ 使用 StringBuilder 或 StringBuffer,二者的 append 方法都繼承自 AbstractStringBuilder,該方法首先使用 Arrays.copyOf 肯定新的字符數組容量,再調用 getChars 方法使用 System.arraycopy 將新的值追加到數組中。StringBuilder 是 JDK5 引入的,效率高但線程不安全。StringBuffer 使用 synchronized 保證線程安全。
Q5:String a = "a" + new String("b") 建立了幾個對象?
常量和常量拼接還是常量,結果在常量池,只要有變量參與拼接結果就是變量,存在堆。
使用字面量時只建立一個常量池中的常量,使用 new 時若是常量池中沒有該值就會在常量池中新建立,再在堆中建立一個對象引用常量池中常量。所以 String a = "a" + new String("b") 會建立四個對象,常量池中的 a 和 b,堆中的 b 和堆中的 ab。
面向對象 10
Q1:談一談你對面向對象的理解
面向過程讓計算機有步驟地順序作一件事,是過程化思惟,使用面向過程語言開發大型項目,軟件複用和維護存在很大問題,模塊之間耦合嚴重。面向對象相對面向過程更適合解決規模較大的問題,能夠拆解問題複雜度,對現實事物進行抽象並映射爲開發對象,更接近人的思惟。
例如開門這個動做,面向過程是 open(Door door),動賓結構,door 做爲操做對象的參數傳入方法,方法內定義開門的具體步驟。面向對象的方式首先會定義一個類 Door,抽象出門的屬性(如尺寸、顏色)和行爲(如 open 和 close),主謂結構。
面向過程代碼鬆散,強調流程化解決問題。面向對象代碼強調高內聚、低耦合,先抽象模型定義共性行爲,再解決實際問題。
Q2:面向對象的三大特性?
封裝是對象功能內聚的表現形式,在抽象基礎上決定信息是否公開及公開等級,核心問題是以什麼方式暴漏哪些信息。主要任務是對屬性、數據、敏感行爲實現隱藏,對屬性的訪問和修改必須經過公共接口實現。封裝使對象關係變得簡單,下降了代碼耦合度,方便維護。
迪米特原則就是對封裝的要求,即 A 模塊使用 B 模塊的某接口行爲,對 B 模塊中除此行爲外的其餘信息知道得應儘量少。不直接對 public 屬性進行讀取和修改而使用 getter/setter 方法是由於假設想在修改屬性時進行權限控制、日誌記錄等操做,在直接訪問屬性的狀況下沒法實現。若是將 public 的屬性和行爲修改成 private 通常依賴模塊都會報錯,所以不知道使用哪一種權限時應優先使用 private。
繼承用來擴展一個類,子類可繼承父類的部分屬性和行爲使模塊具備複用性。繼承是"is-a"關係,可以使用里氏替換原則判斷是否知足"is-a"關係,即任何父類出現的地方子類均可以出現。若是父類引用直接使用子類引用來代替且能夠正確編譯並執行,輸出結果符合子類場景預期,那麼說明兩個類符合里氏替換原則。
多態以封裝和繼承爲基礎,根據運行時對象實際類型使同一行爲具備不一樣表現形式。多態指在編譯層面沒法肯定最終調用的方法體,在運行期由 JVM 動態綁定,調用合適的重寫方法。因爲重載屬於靜態綁定,本質上重載結果是徹底不一樣的方法,所以多態通常專指重寫。
Q3:重載和重寫的區別?
重載指方法名稱相同,但參數類型個數不一樣,是行爲水平方向不一樣實現。對編譯器來講,方法名稱和參數列表組成了一個惟一鍵,稱爲方法簽名,JVM 經過方法簽名決定調用哪一種重載方法。無論繼承關係如何複雜,重載在編譯時能夠根據規則知道調用哪一種目標方法,所以屬於靜態綁定。
JVM 在重載方法中選擇合適方法的順序:① 精確匹配。② 基本數據類型自動轉換成更大表示範圍。③ 自動拆箱與裝箱。④ 子類向上轉型。⑤ 可變參數。
重寫指子類實現接口或繼承父類時,保持方法簽名徹底相同,實現不一樣方法體,是行爲垂直方向不一樣實現。
元空間有一個方法表保存方法信息,若是子類重寫了父類的方法,則方法表中的方法引用會指向子類實現。父類引用執行子類方法時沒法調用子類存在而父類不存在的方法。
重寫方法訪問權限不能變小,返回類型和拋出的異常類型不能變大,必須加 @Override 。
Q4:類之間有哪些關係?
類關係 描述 權力強側 舉例
繼承 父子類之間的關係:is-a 父類 小狗繼承於動物
實現 接口和實現類之間的關係:can-do 接口 小狗實現了狗叫接口
組合 比聚合更強的關係:contains-a 總體 頭是身體的一部分
聚合 暫時組裝的關係:has-a 組裝方 小狗和繩子是暫時的聚合關係
依賴 一個類用到另外一個:depends-a 被依賴方 人養小狗,人依賴於小狗
關聯 平等的使用關係:links-a 平等 人使用卡消費,卡能夠提取人的信息
Q5:Object 類有哪些方法?
equals:檢測對象是否相等,默認使用 == 比較對象引用,能夠重寫 equals 方法自定義比較規則。equals 方法規範:自反性、對稱性、傳遞性、一致性、對於任何非空引用 x,x.equals(null) 返回 false。
hashCode:散列碼是由對象導出的一個整型值,沒有規律,每一個對象都有默認散列碼,值由對象存儲地址得出。字符串散列碼由內容導出,值可能相同。爲了在集合中正確使用,通常須要同時重寫 equals 和 hashCode,要求 equals 相同 hashCode 必須相同,hashCode 相同 equals 未必相同,所以 hashCode 是對象相等的必要不充分條件。
toString:打印對象時默認的方法,若是沒有重寫打印的是表示對象值的一個字符串。
*clone:clone 方法聲明爲 protected,類只能經過該方法克隆它本身的對象,若是但願其餘類也能調用該方法必須定義該方法爲 public。若是一個對象的類沒有實現 Cloneable 接口,該對象調用 clone 方拋出一個 CloneNotSupport 異常。默認的 clone 方法是淺拷貝,通常重寫 clone 方法須要實現 Cloneable 接口並指定訪問修飾符爲 public。
finalize:肯定一個對象死亡至少要通過兩次標記,若是對象在可達性分析後發現沒有與 GC Roots 鏈接的引用鏈會被第一次標記,隨後進行一次篩選,條件是對象是否有必要執行 finalize 方法。假如對象沒有重寫該方法或方法已被虛擬機調用,都視爲沒有必要執行。若是有必要執行,對象會被放置在 F-Queue 隊列,由一條低調度優先級的 Finalizer 線程去執行。虛擬機會觸發該方法但不保證會結束,這是爲了防止某個對象的 finalize 方法執行緩慢或發生死循環。只要對象在 finalize 方法中從新與引用鏈上的對象創建關聯就會在第二次標記時被移出回收集合。因爲運行代價高昂且沒法保證調用順序,在 JDK 9 被標記爲過期方法,並不適合釋放資源。
getClass:返回包含對象信息的類對象。
wait / notify / notifyAll:阻塞或喚醒持有該對象鎖的線程。
Q6:內部類的做用是什麼,有哪些分類?
內部類可對同一包中其餘類隱藏,內部類方法能夠訪問定義這個內部類的做用域中的數據,包括 private 數據。
內部類是一個編譯器現象,與虛擬機無關。編譯器會把內部類轉換成常規的類文件,用 $ 分隔外部類名與內部類名,其中匿名內部類使用數字編號,虛擬機對此一無所知。
靜態內部類: 屬於外部類,只加載一次。做用域僅在包內,可經過 外部類名.內部類名 直接訪問,類內只能訪問外部類全部靜態屬性和方法。HashMap 的 Node 節點,ReentrantLock 中的 Sync 類,ArrayList 的 SubList 都是靜態內部類。內部類中還能夠定義內部類,如 ThreadLoacl 靜態內部類 ThreadLoaclMap 中定義了內部類 Entry。
成員內部類: 屬於外部類的每一個對象,隨對象一塊兒加載。不能夠定義靜態成員和方法,可訪問外部類的全部內容。
局部內部類: 定義在方法內,不能聲明訪問修飾符,只能定義實例成員變量和實例方法,做用範圍僅在聲明類的代碼塊中。
匿名內部類: 只用一次的沒有名字的類,能夠簡化代碼,建立的對象類型至關於 new 的類的子類類型。用於實現事件監聽和其餘回調。
Q7:訪問權限控制符有哪些?
訪問權限控制符 本類 封裝形式 包外子類 任何地方
上市 √ √ √ √
受保護的 √ √ √ ×
無 √ √ × ×
私人的 √ × × ×
Q8:接口和抽象類的異同?
接口和抽象類對實體類進行更高層次的抽象,僅定義公共行爲和特徵。
語法維度 抽象類 接口
成員變量 無特殊要求 默認 public static final 常量
構造方法 有構造方法,不能實例化 沒有構造方法,不能實例化
方法 抽象類能夠沒有抽象方法,但有抽象方法必定是抽象類。 默認 public abstract,JDK8 支持默認/靜態方法,JDK9 支持私有方法。
繼承 單繼承 多繼承
Q9:接口和抽象類應該怎麼選擇?
抽象類體現 is-a 關係,接口體現 can-do 關係。與接口相比,抽象類一般是對同類事物相對具體的抽象。
抽象類是模板式設計,包含一組具體特徵,例如某汽車,底盤、控制電路等是抽象出來的共同特徵,但內飾、顯示屏、座椅材質能夠根據不一樣級別配置存在不一樣實現。
接口是契約式設計,是開放的,定義了方法名、參數、返回值、拋出的異常類型,誰均可以實現它,但必須遵照接口的約定。例如全部車輛都必須實現剎車這種強制規範。
接口是頂級類,抽象類在接口下面的第二層,對接口進行了組合,而後實現部分接口。當糾結定義接口和抽象類時,推薦定義爲接口,遵循接口隔離原則,按維度劃分紅多個接口,再利用抽象類去實現這些,方便後續的擴展和重構。
例如 Plane 和 Bird 都有 fly 方法,應把 fly 定義爲接口,而不是抽象類的抽象方法再繼承,由於除了 fly 行爲外 Plane 和 Bird 間很難再找到其餘共同特徵。
Q10:子類初始化的順序
① 父類靜態代碼塊和靜態變量。② 子類靜態代碼塊和靜態變量。③ 父類普通代碼塊和普通變量。④ 父類構造方法。⑤ 子類普通代碼塊和普通變量。⑥ 子類構造方法。
集合 7
Q1:說一說 ArrayList
ArrayList 是容量可變的非線程安全列表,使用數組實現,集合擴容時會建立更大的數組,把原有數組複製到新數組。支持對元素的快速隨機訪問,但插入與刪除速度很慢。ArrayList 實現了 RandomAcess 標記接口,若是一個類實現了該接口,那麼表示使用索引遍歷比迭代器更快。
elementData是 ArrayList 的數據域,被 transient 修飾,序列化時會調用 writeObject 寫入流,反序列化時調用 readObject 從新賦值到新對象的 elementData。緣由是 elementData 容量一般大於實際存儲元素的數量,因此只需發送真正有實際值的數組元素。
size 是當前實際大小,elementData 大小大於等於 size。
**modCount **記錄了 ArrayList 結構性變化的次數,繼承自 AbstractList。全部涉及結構變化的方法都會增長該值。expectedModCount 是迭代器初始化時記錄的 modCount 值,每次訪問新元素時都會檢查 modCount 和 expectedModCount 是否相等,不相等就會拋出異常。這種機制叫作 fail-fast,全部集合類都有這種機制。
Q2:說一說 LinkedList
LinkedList 本質是雙向鏈表,與 ArrayList 相比插入和刪除速度更快,但隨機訪問元素很慢。除繼承 AbstractList 外還實現了 Deque 接口,這個接口具備隊列和棧的性質。成員變量被 transient 修飾,原理和 ArrayList 相似。
LinkedList 包含三個重要的成員:size、first 和 last。size 是雙向鏈表中節點的個數,first 和 last 分別指向首尾節點的引用。
LinkedList 的優勢在於能夠將零散的內存單元經過附加引用的方式關聯起來,造成按鏈路順序查找的線性結構,內存利用率較高。
Q3:Set 有什麼特色,有哪些實現?
Set 不容許元素重複且無序,經常使用實現有 HashSet、LinkedHashSet 和 TreeSet。
HashSet 經過 HashMap 實現,HashMap 的 Key 即 HashSet 存儲的元素,全部 Key 都使用相同的 Value ,一個名爲 PRESENT 的 Object 類型常量。使用 Key 保證元素惟一性,但不保證有序性。因爲 HashSet 是 HashMap 實現的,所以線程不安全。
HashSet 判斷元素是否相同時,對於包裝類型直接按值比較。對於引用類型先比較 hashCode 是否相同,不一樣則表明不是同一個對象,相同則繼續比較 equals,都相同纔是同一個對象。
LinkedHashSet 繼承自 HashSet,經過 LinkedHashMap 實現,使用雙向鏈表維護元素插入順序。
TreeSet 經過 TreeMap 實現的,添加元素到集合時按照比較規則將其插入合適的位置,保證插入後的集合仍然有序。
Q4:TreeMap 有什麼特色?
TreeMap 基於紅黑樹實現,增刪改查的平均和最差時間複雜度均爲 O(logñ) ,最大特色是 Key 有序。Key 必須實現 Comparable 接口或提供的 Comparator 比較器,因此 Key 不容許爲 null。
HashMap 依靠 hashCode 和 equals 去重,而 TreeMap 依靠 Comparable 或 Comparator。TreeMap 排序時,若是比較器不爲空就會優先使用比較器的 compare 方法,不然使用 Key 實現的 Comparable 的 compareTo 方法,二者都不知足會拋出異常。
TreeMap 經過 put 和 deleteEntry 實現增長和刪除樹節點。插入新節點的規則有三個:① 須要調整的新節點老是紅色的。② 若是插入新節點的父節點是黑色的,不須要調整。③ 若是插入新節點的父節點是紅色的,因爲紅黑樹不能出現相鄰紅色,進入循環判斷,經過從新着色或左右旋轉來調整。TreeMap 的插入操做就是按照 Key 的對比往下遍歷,大於節點值向右查找,小於向左查找,先按照二叉查找樹的特性操做,後續會從新着色和旋轉,保持紅黑樹的特性。
Q5:HashMap 有什麼特色?
JDK8 以前底層實現是數組 + 鏈表,JDK8 改成數組 + 鏈表/紅黑樹,節點類型從Entry 變動爲 Node。主要成員變量包括存儲數據的 table 數組、元素數量 size、加載因子 loadFactor。
table 數組記錄 HashMap 的數據,每一個下標對應一條鏈表,全部哈希衝突的數據都會被存放到同一條鏈表,Node/Entry 節點包含四個成員變量:key、value、next 指針和 hash 值。
HashMap 中數據以鍵值對的形式存在,鍵對應的 hash 值用來計算數組下標,若是兩個元素 key 的 hash 值同樣,就會發生哈希衝突,被放到同一個鏈表上,爲使查詢效率儘量高,鍵的 hash 值要儘量分散。
HashMap 默認初始化容量爲 16,擴容容量必須是 2 的冪次方、最大容量爲 1<< 30 、默認加載因子爲 0.75。
Q6:HashMap 相關方法的源碼?
JDK8 以前
hash:計算元素 key 的散列值
① 處理 String 類型時,調用 stringHash32 方法獲取 hash 值。
② 處理其餘類型數據時,提供一個相對於 HashMap 實例惟一不變的隨機值 hashSeed 做爲計算初始量。
③ 執行異或和無符號右移使 hash 值更加離散,減少哈希衝突機率。
indexFor:計算元素下標
將 hash 值和數組長度-1 進行與操做,保證結果不會超過 table 數組範圍。
get:獲取元素的 value 值
① 若是 key 爲 null,調用 getForNullKey 方法,若是 size 爲 0 表示鏈表爲空,返回 null。若是 size 不爲 0 說明存在鏈表,遍歷 table[0] 鏈表,若是找到了 key 爲 null 的節點則返回其 value,不然返回 null。
② 若是 key 爲 不爲 null,調用 getEntry 方法,若是 size 爲 0 表示鏈表爲空,返回 null 值。若是 size 不爲 0,首先計算 key 的 hash 值,而後遍歷該鏈表的全部節點,若是節點的 key 和 hash 值都和要查找的元素相同則返回其 Entry 節點。
③ 若是找到了對應的 Entry 節點,調用 getValue 方法獲取其 value 並返回,不然返回 null。
put:添加元素
① 若是 key 爲 null,直接存入 table[0]。
② 若是 key 不爲 null,計算 key 的 hash 值。
③ 調用 indexFor 計算元素存放的下標 i。
④ 遍歷 table[i] 對應的鏈表,若是 key 已存在,就更新 value 而後返回舊 value。
⑤ 若是 key 不存在,將 modCount 值加 1,使用 addEntry 方法增長一個節點並返回 null。
resize:擴容數組
① 若是當前容量達到了最大容量,將閾值設置爲 Integer 最大值,以後擴容再也不觸發。
② 不然計算新的容量,將閾值設爲 newCapacity x loadFactor 和 最大容量 + 1 的較小值。
③ 建立一個容量爲 newCapacity 的 Entry 數組,調用 transfer 方法將舊數組的元素轉移到新數組。
transfer:轉移元素
① 遍歷舊數組的全部元素,調用 rehash 方法判斷是否須要哈希重構,若是須要就從新計算元素 key 的 hash 值。
② 調用 indexFor 方法計算元素存放的下標 i,利用頭插法將舊數組的元素轉移到新數組。
JDK8
hash:計算元素 key 的散列值
若是 key 爲 null 返回 0,不然就將 key 的 hashCode 方法返回值高低16位異或,讓儘量多的位參與運算,讓結果的 0 和 1 分佈更加均勻,下降哈希衝突機率。
put:添加元素
① 調用 putVal 方法添加元素。
② 若是 table 爲空或長度爲 0 就進行擴容,不然計算元素下標位置,不存在就調用 newNode 建立一個節點。
③ 若是存在且是鏈表,若是首節點和待插入元素的 hash 和 key 都同樣,更新節點的 value。
④ 若是首節點是 TreeNode 類型,調用 putTreeVal 方法增長一個樹節點,每一次都比較插入節點和當前節點的大小,待插入節點小就往左子樹查找,不然往右子樹查找,找到空位後執行兩個方法:balanceInsert 方法,插入節點並調整平衡、moveRootToFront 方法,因爲調整平衡後根節點可能變化,須要重置根節點。
⑤ 若是都不知足,遍歷鏈表,根據 hash 和 key 判斷是否重複,決定更新 value 仍是新增節點。若是遍歷到了鏈表末尾則添加節點,若是達到建樹閾值 7,還須要調用 treeifyBin 把鏈表重構爲紅黑樹。
⑥ 存放元素後將 modCount 加 1,若是 ++size > threshold ,調用 resize 擴容。
get :獲取元素的 value 值
① 調用 getNode 方法獲取 Node 節點,若是不是 null 就返回其 value 值,不然返回 null。
② getNode 方法中若是數組不爲空且存在元素,先比較第一個節點和要查找元素的 hash 和 key ,若是都相同則直接返回。
③ 若是第二個節點是 TreeNode 類型則調用 getTreeNode 方法進行查找,不然遍歷鏈表根據 hash 和 key 查找,若是沒有找到就返回 null。
resize:擴容數組
從新規劃長度和閾值,若是長度發生了變化,部分數據節點也要從新排列。
從新規劃長度
① 若是當前容量 oldCap > 0 且達到最大容量,將閾值設爲 Integer 最大值,return 終止擴容。
② 若是未達到最大容量,當 oldCap << 1 不超過最大容量就擴大爲 2 倍。
③ 若是都不知足且當前擴容閾值 oldThr > 0,使用當前擴容閾值做爲新容量。
④ 不然將新容量置爲默認初始容量 16,新擴容閾值置爲 12。
從新排列數據節點
① 若是節點爲 null 不進行處理。
② 若是節點不爲 null 且沒有next節點,那麼經過節點的 hash 值和 新容量-1 進行與運算計算下標存入新的 table 數組。
③ 若是節點爲 TreeNode 類型,調用 split 方法處理,若是節點數 hc 達到6 會調用 untreeify 方法轉回鏈表。
④ 若是是鏈表節點,須要將鏈表拆分爲 hash 值超出舊容量的鏈表和未超出容量的鏈表。對於hash & oldCap == 0 的部分不須要作處理,不然須要放到新的下標位置上,新下標 = 舊下標 + 舊容量。
Q7:HashMap 爲何線程不安全?
JDK7 存在死循環和數據丟失問題。
數據丟失:
併發賦值被覆蓋: 在 createEntry 方法中,新添加的元素直接放在頭部,使元素以後能夠被更快訪問,但若是兩個線程同時執行到此處,會致使其中一個線程的賦值被覆蓋。
已遍歷區間新增元素丟失: 當某個線程在 transfer 方法遷移時,其餘線程新增的元素可能落在已遍歷過的哈希槽上。遍歷完成後,table 數組引用指向了 newTable,新增元素丟失。
新表被覆蓋: 若是 resize 完成,執行了 table = newTable,則後續元素就能夠在新表上進行插入。但若是多線程同時 resize ,每一個線程都會 new 一個數組,這是線程內的局部對象,線程之間不可見。遷移完成後resize 的線程會賦值給 table 線程共享變量,可能會覆蓋其餘線程的操做,在新表中插入的對象都會被丟棄。
死循環: 擴容時 resize 調用 transfer 使用頭插法遷移元素,雖然 newTable 是局部變量,但原先 table 中的 Entry 鏈表是共享的,問題根源是 Entry 的 next 指針併發修改,某線程尚未將 table 設爲 newTable 時用完了 CPU 時間片,致使數據丟失或死循環。
JDK8 在 resize 方法中完成擴容,並改用尾插法,不會產生死循環,但併發下仍可能丟失數據。可用 ConcurrentHashMap 或 Collections.synchronizedMap 包裝成同步集合。
IO風格6
Q1:同步/異步/阻塞/非阻塞 IO 的區別?
同步和異步是通訊機制,阻塞和非阻塞是調用狀態。
同步 IO 是用戶線程發起 IO 請求後須要等待或輪詢內核 IO 操做完成後才能繼續執行。異步 IO 是用戶線程發起 IO 請求後能夠繼續執行,當內核 IO 操做完成後會通知用戶線程,或調用用戶線程註冊的回調函數。
阻塞 IO 是 IO 操做須要完全完成後才能返回用戶空間 。非阻塞 IO 是 IO 操做調用後當即返回一個狀態值,無需等 IO 操做完全完成。
Q2:什麼是 BIO?
BIO 是同步阻塞式 IO,JDK1.4 以前的 IO 模型。服務器實現模式爲一個鏈接請求對應一個線程,服務器須要爲每個客戶端請求建立一個線程,若是這個鏈接不作任何事會形成沒必要要的線程開銷。能夠經過線程池改善,這種 IO 稱爲僞異步 IO。適用鏈接數目少且服務器資源多的場景。
Q3:什麼是 NIO?
NIO 是 JDK1.4 引入的同步非阻塞 IO。服務器實現模式爲多個鏈接請求對應一個線程,客戶端鏈接請求會註冊到一個多路複用器 Selector ,Selector 輪詢到鏈接有 IO 請求時才啓動一個線程處理。適用鏈接數目多且鏈接時間短的場景。
同步是指線程仍是要不斷接收客戶端鏈接並處理數據,非阻塞是指若是一個管道沒有數據,不須要等待,能夠輪詢下一個管道。
核心組件:
Selector: 多路複用器,輪詢檢查多個 Channel 的狀態,判斷註冊事件是否發生,即判斷 Channel 是否處於可讀或可寫狀態。使用前須要將 Channel 註冊到 Selector,註冊後會獲得一個 SelectionKey,經過 SelectionKey 獲取 Channel 和 Selector 相關信息。
Channel: 雙向通道,替換了 BIO 中的 Stream 流,不能直接訪問數據,要經過 Buffer 來讀寫數據,也能夠和其餘 Channel 交互。
Buffer: 緩衝區,本質是一塊可讀寫數據的內存,用來簡化數據讀寫。Buffer 三個重要屬性:position 下次讀寫數據的位置,limit 本次讀寫的極限位置,capacity 最大容量。
使用步驟:向 Buffer 寫數據,調用 flip 方法轉爲讀模式,從 Buffer 中讀數據,調用 clear 或 compact 方法清空緩衝區。
flip 將寫轉爲讀,底層實現原理把 position 置 0,並把 limit 設爲當前的 position 值。
clear 將讀轉爲寫模式(用於讀徹底部數據的狀況,把 position 置 0,limit 設爲 capacity)。
compact 將讀轉爲寫模式(用於存在未讀數據的狀況,讓 position 指向未讀數據的下一個)。
通道方向和 Buffer 方向相反,讀數據至關於向 Buffer 寫,寫數據至關於從 Buffer 讀。
Q4:什麼是 AIO?
AIO 是 JDK7 引入的異步非阻塞 IO。服務器實現模式爲一個有效請求對應一個線程,客戶端的 IO 請求都是由操做系統先完成 IO 操做後再通知服務器應用來直接使用準備好的數據。適用鏈接數目多且鏈接時間長的場景。
異步是指服務端線程接收到客戶端管道後就交給底層處理IO通訊,本身能夠作其餘事情,非阻塞是指客戶端有數據纔會處理,處理好再通知服務器。
實現方式包括經過 Future 的 get 方法進行阻塞式調用以及實現 CompletionHandler 接口,重寫請求成功的回調方法 completed 和請求失敗回調方法 failed。
Q5:java.io 包下有哪些流?
主要分爲字符流和字節流,字符流通常用於文本文件,字節流通常用於圖像或其餘文件。
字符流包括了字符輸入流 Reader 和字符輸出流 Writer,字節流包括了字節輸入流 InputStream 和字節輸出流 OutputStream。字符流和字節流都有對應的緩衝流,字節流也能夠包裝爲字符流,緩衝流帶有一個 8KB 的緩衝數組,能夠提升流的讀寫效率。除了緩衝流外還有過濾流 FilterReader、字符數組流 CharArrayReader、字節數組流 ByteArrayInputStream、文件流 FileInputStream 等。
Q6:序列化和反序列化是什麼?
Java 對象 JVM 退出時會所有銷燬,若是須要將對象及狀態持久化,就要經過序列化實現,將內存中的對象保存在二進制流中,須要時再將二進制流反序列化爲對象。對象序列化保存的是對象的狀態,所以屬於類屬性的靜態變量不會被序列化。
常見的序列化有三種:
Java 原生序列化
實現 Serializabale 標記接口,Java 序列化保留了對象類的元數據(如類、成員變量、繼承類信息)以及對象數據,兼容性最好,但不支持跨語言,性能通常。序列化和反序列化必須保持序列化 ID 的一致,通常使用 private static final long serialVersionUID 定義序列化 ID,若是不設置編譯器會根據類的內部實現自動生成該值。若是是兼容升級不該該修改序列化 ID,防止出錯,若是是不兼容升級則須要修改。
Hessian 序列化
Hessian 序列化是一種支持動態類型、跨語言、基於對象傳輸的網絡協議。Java 對象序列化的二進制流能夠被其它語言反序列化。Hessian 協議的特性:① 自描述序列化類型,不依賴外部描述文件,用一個字節表示經常使用基礎類型,極大縮短二進制流。② 語言無關,支持腳本語言。③ 協議簡單,比 Java 原生序列化高效。Hessian 會把複雜對象全部屬性存儲在一個 Map 中序列化,當父類和子類存在同名成員變量時會先序列化子類再序列化父類,所以子類值會被父類覆蓋。
JSON 序列化
JSON 序列化就是將數據對象轉換爲 JSON 字符串,在序列化過程當中拋棄了類型信息,因此反序列化時只有提供類型信息才能準確進行。相比前兩種方式可讀性更好,方便調試。
序列化一般會使用網絡傳輸對象,而對象中每每有敏感數據,容易遭受攻擊,Jackson 和 fastjson 等都出現過反序列化漏洞,所以不須要進行序列化的敏感屬性傳輸時應加上 transient 關鍵字。transient 的做用就是把變量生命週期僅限於內存而不會寫到磁盤裏持久化,變量會被設爲對應數據類型的零值。java
總結了一些2020年的面試題,這份面試題的包含的模塊分爲19個模塊,分別是: Java基礎、容器、多線程、反射、對象拷貝、JavaWeb異常、網絡、設計模式、Spring/SpringMVC、SpringBoot/SpringCloud、Hibernate、MyBatis、RabbitMQ、Kafka、Zookeeper、MySQL、Redis、JVM。
獲取如下資料,關注公衆號:【有故事的程序員】。
記得點個關注+評論哦~程序員