主頁 > 後端開發 > 40道一線互聯網公司高頻面試題(附答案!)

40道一線互聯網公司高頻面試題(附答案!)

2021-02-20 06:11:28 後端開發

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 的作用就是把變數生命周期僅限于記憶體而不會寫到磁盤里持久化,變數會被設為對應資料型別的零值,

總結了一些2020年的面試題,這份面試題的包含的模塊分為19個模塊,分別是: Java基礎、容器、多執行緒、反射、物件拷貝、JavaWeb例外、網路、設計模式、Spring/SpringMVC、SpringBoot/SpringCloud、Hibernate、MyBatis、RabbitMQ、Kafka、Zookeeper、MySQL、Redis、JVM,
獲取以下資料,關注公眾號:【有故事的程式員】,
記得點個關注+評論哦~

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/261241.html

標籤:Java

上一篇:Java 添加 、讀取以及洗掉PPT幻燈片中的視頻、音頻檔案

下一篇:五分鐘教你如何優雅的統計代碼耗時,讓你知道你的程式到底慢在哪!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more