2021秋招面試計算機基礎總結背誦版(還在寫)

Borris發表於2020-09-15

(Java、資料庫、計網、作業系統、演算法、Spring相關)

Java 平臺無關性

  • Java語言規範: 通過規定Java語言中基本資料型別的取值範圍和行為
  • Class檔案: 所有Java檔案要編譯成統一的Class檔案
  • Java虛擬機器
    • 通過Java虛擬機器將Class檔案轉成對應平臺的二進位制檔案等
    • JVM是平臺相關的,需要在不同系統安裝對應的虛擬機器。

Java 三大特性

  • 封裝:將一系列的操作和資料組合在一個包中,使用者呼叫這個包的時候不必瞭解包中具體的方法是如何實現的。
  • 多型:父類的變數可以引用一個子類的物件,在執行時通過動態繫結來決定呼叫的方法。
  • 繼承:一個類可以擴充套件出一個子類,子類可以繼承父類的屬性和方法,也可以新增自己的成員變數和方法。介面可以多繼承,類只能單繼承。

過載和重寫

  • 重寫:子類具有和父類方法名和引數列表都相同的方法,返回值要不大於父類方法的返回值,丟擲的異常要不大於父類丟擲的異常,方法修飾符可見性要不小於父類。執行時多型。
  • 過載:同一個類中具有方法名相同但引數列表不同的方法,返回值不做要求。編譯時多型。

Integer 和 int 區別

  • Integer是int的包裝類,所表示的變數是一個物件;而int所表示的變數是基本資料型別的
  • 自動裝箱指的是將基本資料型別包裝為一個包裝類物件,自動拆箱指的是將一個包裝類物件轉換為一個基本資料型別。
  • 包裝類的比較使用equals

基本資料型別的大小

  • byte 1位元組;short 2位元組
  • int, float 4位元組
  • long, double 8位元組
  • boolean 單獨出現時4位元組,陣列時單個元素1位元組
  • char 英文都是1位元組,GBK中文2位元組,UTF-8中文3位元組

值傳遞和引用傳遞

值傳遞對基本資料型別而言的,傳遞的是變數的一個副本,改變副本不影響原變數的值
引用傳遞對於物件型變數而言,傳遞的是物件地址的副本,不是原變數本身,所以對引用物件操作會改變原變數的值。

== 和 equals 區別

  • == 比較的物件如果是基本資料型別,就是兩者的值進行比較;如果是引用物件的比較,是判斷物件的地址值是否相同
  • equals 如果比較的是String物件,就是判斷字串的值是否相同;如果比較的是Object物件,比較的是引用的地址記憶體;可以通過重寫equals方法來自定義比較規則,也需要同時重寫hashCode方法

equals 和 hashCode

  • equals用來比較物件地址值是否相同
  • hashCode返回由物件地址計算得出的一個雜湊值
  • 兩者要同時重寫的原因
    • 使用hashcode方法提前校驗,可以避免每一次比對都呼叫equals方法,提高效率
    • 保證是同一個物件,如果重寫了equals方法,而沒有重寫hashcode方法,會出現equals相等的物件,hashcode不相等的情況,重寫hashcode方法就是為了避免這種情況的出現。

String,StringBuilder,StringBuffer

String

  • 一旦被建立就不可被修改,所以修改String變數值的時候是新建了一個String物件,賦值給原變數引用
  • 兩種建立方法
    • 直接賦值一個字串,就是將字串放進常量池,位於棧中的變數直接引用常量池中的字串。
    • new 方式建立先在堆中建立String物件,再去常量池中查詢是否有賦值的字串常量,找到了就直接使用,沒找到就開闢空間存字串。通過變數引用物件,物件引用字串的形式建立。

StringBuilder & StringBuffer

  • 都繼承自AbstractStringBuilder類,是可變類
  • 前者執行緒不安全,後者執行緒安全,所以StringBuilder執行效率高

public, protected, default, private

其他類,本包,子類,本類;許可權依次遞減。

final關鍵字

  • 所修飾的變數,是基本資料型別則值不能改變;是引用型變數的話,初始化後就不能指向另一個物件了。
  • 所修飾的類,不能被繼承

抽象類和介面的區別

分為四個方面

  • 成員變數:介面中預設public static final
  • 成員方法:介面中
  • 構造器:介面和抽象類都不能被例項化,但介面中沒有構造器,抽象類中有
  • 繼承:介面可以多繼承,抽象類只能單繼承

異常

所有的異常都繼承自Throwable類的,分為 Error 和 Exception。

  • Error 類描述了 Java 執行時系統的內部錯誤和資源耗盡錯誤,如果出現了這種錯誤,一般無能為力。
  • Exception 類又分為 RuntimeException 和檢查型異常。
  • Error 和 RuntimeException 的異常屬於非檢查型異常,其他的都是檢查型異常。

常見的 RuntimeException 異常:

  • ClassCastException,錯誤的強制型別轉換。
  • ArrayIndexOutOfBoundsException,陣列訪問越界。
  • NullPointerException,空指標異常。

常見的檢查型異常:

  • FileNotFoundException,試圖開啟不存在的檔案。

  • ClassNotFoundException,試圖根據指定字串查詢 Class 物件,而這個類並不存在。

  • IOException,試圖超越檔案末尾繼續讀取資料。

  • 異常處理:

    • 丟擲異常:遇到異常不進行具體處理,而是將異常丟擲給呼叫者,由呼叫者根據情況處理。丟擲異常有2種形式,一種是 throws 關鍵字宣告丟擲的異常,作用在方法上,一種是使用throw 語句直接丟擲異常,作用在方法內。
      • 捕獲異常:使用 try/catch 進行異常的捕獲,try 中發生的異常會被 catch 程式碼塊捕獲,根據情況進行處理,如果有 finally 程式碼塊無論是否發生異常都會執行,一般用於釋放資源,Java 7 開始可以將資源定義在 try 程式碼塊中自動釋放資源。

序列化

Java物件在JVM執行時被建立,JVM退出時存活物件被銷燬。為了保證物件及其狀態的持久化,就需要使用序列化了。序列化就是將物件通過ObjectOutputStream儲存為位元組流;反序列化就是將位元組流還原為物件。

  • 要實現Serializable介面來進行序列化。
  • 序列化和反序列化必須保持序列化 ID 的一致。
  • 靜態、transient修飾的變數和方法不能被序列化。

反射

在執行狀態中,動態獲取一個類的都能夠知道這個類的所有屬性和方法,對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為Java的反射機制。優點是執行時動態獲取類的全部資訊,缺點是破壞了類的封裝性,泛型的約束性。

集合

List

List 是一種線性列表結構,元素是有序、可重複的。

  • ArrayList

    • 底層由陣列實現,隨機訪問效率高,讀快寫慢,由於寫操作涉及元素的移動,因此寫操作效率低。
    • 三個成員變數:elementData 是 ArrayList 的資料域,不能被序列化;size表示list的實際大小;modCount記錄了ArrayList新增或者刪除元素這種結構性變化的次數。
  • LinkedList

    • 底層由連結串列實現,需要順序訪問元素,即使有索引也需要從頭遍歷,所以說寫快讀慢。
    • LinkedList 實現了 Deque 介面,具有佇列的屬性,可在尾部增加元素,在頭部獲取元素,也能操作頭尾之間任意元素。
  • Vector 和 Stack

    • Vector 的實現和 ArrayList 基本一致,底層使用的也是陣列,區別主要在於:(1)Vector 的所有公有方法都使用了 synchronized 修飾保證執行緒安全性。(2)增長策略不同,Vector 多了一個成員變數 capacityIncrement 用於標明擴容的增量。
    • Stack 是 Vector 的子類,實現和 Vector基本一致,與之相比多提供了一些方法表達棧的含義。

HashSet

  • HashSet 中的元素是無序、不重複的,最多隻能有一個 null 值。
  • HashSet 的底層是通過 HashMap 實現的,HashMap 的 key 值即 HashSet 儲存的元素,所有 key 都使用相同的 value ,一個static final 修飾的變數名為 PRESENT 的 Object 型別的物件。
  • 由於 HashSet 的底層是 HashMap 實現的,HashMap 是執行緒不安全的,因此 HashSet 也是執行緒不安全的。
  • 去重:通過比較hashCode和equal方法

HashMap

JDK 8 之前

  • 底層實現是陣列 + 連結串列,主要成員變數包括:儲存資料的 table 陣列、鍵值對數量 size、載入因子 loadFactor。

  • table 陣列用於記錄 HashMap 的所有資料,它的每一個下標都對應一條連結串列,所有雜湊衝突的資料都會被存放到同一條連結串列中,Entry 是連結串列的節點元素,包含四個成員變數:鍵 key、值 value、指向下一個節點的指標 next 和 元素的雜湊值 hash。
    在 HashMap 中資料都是以鍵值對的形式存在的,鍵對應的 hash 值將會作為它在陣列裡的下標,如果兩個元素 key 的 hash 值一樣,就會傳送雜湊衝突,被放到同一個下標中的連結串列上,(為了使 HashMap 的查詢效率儘可能高,應該使鍵的 hash 值儘可能分散。)

  • HashMap 預設初始化容量為 16,擴容容量必須是 2 的冪次方、最大容量為 1<< 30 、預設載入因子為 0.75。

  • put 方法:新增元素
    ① 如果 key 為 null 值,直接存入 table[0]。② 如果 key 不為 null 值,先計算 key 對應的雜湊值。③ 呼叫 indexFor 方法根據 key 的雜湊值和陣列的長度計算元素存放的下標 i。④ 遍歷 table[i] 對應的連結串列,如果 key 已經存在,就更新 value 值然後返回舊的 value 值。⑤ 如果 key 不存在,就將 modCount 的值加 1,使用 addEntry 方法增加一個節點,並返回 null 值。

JDK 8 開始

  • 使用的是陣列 + 連結串列/紅黑樹的形式,table 陣列的元素資料型別換成了 Entry 的靜態實現類 Node。

  • put 方法:新增元素
    ① 呼叫 putVal 方法新增元素。② 如果 table 為空或沒有元素時就進行擴容,否則計算元素下標位置,如果不存在就新建立一個節點存入。③ 如果首個節點和待插入元素的 hash值和 key 值都一樣,直接更新 value 值。④ 如果首個節點是 TreeNode 型別,呼叫 putTreeVal 方法增加一個樹節點,每一次都比較插入節點和當前節點的大小,待插入節點小就往左子樹查詢,否則往右子樹查詢,找到空位後執行兩個方法:balanceInsert 方法,把節點插入紅黑樹並對紅黑樹進行調整使之平衡。moveRootToFront 方法,由於調整平衡後根節點可能變化,table 裡記錄的節點不再是根節點,需要重置根節點。⑤ 如果是連結串列節點,就遍歷連結串列,根據 hash 值和 key 值判斷是否重複,決定更新值還是新增節點。如果遍歷到了連結串列末尾,新增連結串列元素,如果達到了建樹閾值,還需要呼叫 treeifyBin 方法把連結串列重構為紅黑樹。⑥ 存放元素後,將 modCount 值加 1,如果節點數 + 1大於擴容閾值,還需要進行擴容。

重新規劃長度

① 如果 size 超出擴容閾值,把 table 容量增加為之前的2倍。② 如果新的 table 容量小於預設的初始化容量16,那麼將 table 容量重置為16。③ 如果新的 table 容量大於等於最大容量,那麼將閾值設為 Integer 的最大值,並且 return 終止擴容,由於 size 不可能超過該值因此之後不會再發生擴容。

重新排列資料節點

① 如果節點為 null 值則不進行處理。② 如果節點不為 null 值且沒有next節點,那麼重新計算其雜湊值然後存入新的 table 陣列中。③ 如果節點為 TreeNode 節點,那麼呼叫 split 方法進行處理,該方法用於對紅黑樹調整,如果太小會退化回連結串列。④ 如果節點是連結串列節點,需要將連結串列拆分為 hashCode() 返回值超出舊容量的連結串列和未超出容量的連結串列。對於hash & oldCap == 0 的部分不需要做處理,反之需要放到新的下標位置上,新下標 = 舊下標 + 舊容量。

執行緒不安全:
Java 8 以前 擴容時 resize 方法呼叫的 transfer 方法中使用頭插法遷移元素,多執行緒會導致 Entry 連結串列形成環形資料結構,Entry 節點的 next 永遠不為空,引起死迴圈。Java 8 開始在 resize 方法中完成擴容,並且改用了尾插法,不會產生死迴圈的問題,但是在多執行緒的情況下還是可能會導致資料覆蓋的問題,因此依舊執行緒不安全。

Java8 新特性

  • lambda 表示式:lambda 表示式允許把函式作為一個方法的引數傳遞到方法中,主要用來簡化匿名內部類的程式碼。

  • 函式式介面:使用 @FunctionalInterface 註解標識,有且僅有一個抽象方法,可以被隱式轉換為 lambda 表示式。

  • 方法引用:可以直接引用已有類或物件的方法或構造器,進一步簡化 lambda 表示式。方法引用有四種形式:引用構造方法、引用類的靜態方法、引用特定類的任意物件方法、引用某個物件的方法。

  • 介面中的方法:介面中可以定義 default 修飾的預設方法,降低了介面升級的複雜性,還可以定義靜態方法。

  • 註解:Java 8 引入了重複註解機制,相同的註解在同一個地方可以宣告多次。註解的作用範圍也進行了擴充套件,可以作用於區域性變數、泛型、方法異常等。

  • 型別推測:加強了型別推測機制,可以使程式碼更加簡潔,例如在定義泛型集合時可以省略物件中的泛型引數。

  • Optional 類:用來處理空指標異常,提高程式碼可讀性。

  • Stream 類:把函數語言程式設計風格引入 Java 語言,提供了很多功能,可以使程式碼更加簡潔。方法包括forEach() 遍歷、count() 統計個數、filter() 按條件過濾、limit() 取前 n 個元素、skip() 跳過前 n 個元素、map() 對映加工、concat() 合併stream流等。

  • 日期:增強了日期和時間的 API,新的 java.time 主要包含了處理日期、時間、日期/時間、時區、時刻和時鐘等操作。

事務

ACID

  • 原子性
    一個事務是無法分割的,也就是說一個事務中的所有操作要麼同時成功,要麼同時失敗。
  • 一致性
    一個事務前後的資料完整性要一致。(銀行轉賬案例,A給B轉賬,B增加金額,A必須減少相應金額)
  • 永續性
    事務一旦提交,對資料的改變就是永久性的。即使資料庫故障也不會對其有任何影響。
  • 隔離性
    多個事務併發執行時,別的事務中對資料的修改在提交前對當前事務中資料是沒有影響的。別的事務提交後根據不同的隔離級別可能會對當前事務造成影響。

隔離級別

從低至高:

  • 未提交讀 問題:髒讀、不可重複讀、幻讀
  • 已提交讀 問題:不可重複讀、幻讀
  • 可重複讀 問題:幻讀
  • 可序列化 解決所有問題

設定:SET GLOBAL transaction_isolation = '隔離級別';

髒讀、不可重複度、幻讀

  • 髒讀:事務A讀到了事務B未提交的資料,事務B發生回滾,事務A就讀到了髒資料
  • 不可重複讀:在事務A多次讀取一組資料的過程中,事務B對該組資料進行了修改並提交,那麼事務A會讀到不一樣的數值。(針對update操作,解決:使用行級鎖,不允許別的事務對這行資料修改)
  • 幻讀:事務A多次讀取資料總量過程中,事務B新增或刪除了資料並提交,導致事務A前後讀取到的資料總量不一致。(針對insert, delete,解決:使用表級鎖,事務A結束前不允許別的事務對該表進行修改)

三大正規化

  • 1NF 強調列的原子性,即一列不能夠被拆分成多個列
  • 2NF 基於1NF,還要滿足一個表必須有一個主鍵,沒有包含在主鍵中的列必須完全依賴於主鍵
  • 3NF 基於1、2NF,非主鍵列必須直接依賴主鍵,不能存在依賴傳遞,就是說:不能存在非主鍵列依賴於非主鍵列B,非主鍵列B依賴於主鍵。

可能舉個具體的例子有利於完全理解三大正規化,稍後更新。

索引

型別(主要的)

  • 全文索引
    查詢的是文字中的關鍵詞,而不是直接比較索引中的值,為了解決一些模糊查詢效率較低的問題。
  • 雜湊索引
    基於雜湊表實現,只有精確匹配索引所有列的查詢才有效。對於每一行資料,儲存引擎都會對所有的索引列計算一個雜湊碼,並且不同鍵值的行計算出的雜湊碼也不一樣。雜湊索引將所有的雜湊碼儲存在索引中,同時在雜湊表中儲存指向每個資料行的指標。
  • B-TREE索引
    是將索引使用B-TREE的形式建立起來。InnoDB引擎使用的是B+樹,類似於二叉查詢樹。
    根節點的槽中存放了指向子節點的指標,儲存引擎根據這些指標向下層查詢。通過比較節點頁的值和要查詢的值可以找到合適的指標進入下層子節點,這些指標實際上定義了子節點頁中值的上限和下限。最終葉子節點的指標指向的是被索引的資料。
    B+樹索引所有的使用者資料儲存在葉子節點,要通過上層節點的目錄項,從根節點層層查詢,找到對應的資料。
  • 覆蓋索引
    一個索引包含或覆蓋了所有需要查詢的欄位的值,不再需要根據索引回表查詢資料。覆蓋索引必須要儲存索引列的值,因此 MySQL 只能使用 B-Tree 索引做覆蓋索引。

種類

  • 普通索引:使用KEY或INDEX關鍵字建立
  • 唯一索引:使用UNIQUE,索引列的值必須唯一,組合索引的組合必須唯一,允許空值
  • 主鍵索引:PRIMARY,特殊的唯一索引,不允許有空值,InnoDB會自動為主鍵建立聚簇索引
  • 組合索引:基於多個欄位建立的索引,查詢時必須遵循最左字首原則(腦子裡詳細過一下)。
  • 全文索引:FULL TEXT,用來查詢文字中關鍵字,為了解決一些模糊查詢效率較低的問題

聚簇索引和非聚簇索引

  • 聚簇索引:將資料儲存與索引放到了一塊,索引結構的葉子節點儲存了行資料
  • 非聚簇索引:將資料與索引分開儲存,索引結構的葉子節點指向了資料對應的位置

InnonDB的B+樹索引查詢如何實現?

  • B+樹每層中頁面之間由雙向連結串列連線,頁面中的記錄項由單向連結串列連線,記錄項根據索引列值從小到大排列

  • 索引建立的過程

    • 一個表建立一個 B+ 樹索引時,會建立一個根節點頁面
      • 這個根節點是不會再移動的,InnoDB 用到該索引時會從資料字典中取出這個索引根節點頁面號,進行訪問。
    • 每當表中有使用者記錄插入,都會把使用者記錄儲存到根節點
    • 當根節點可用空間用完的時候(InnoDB 每頁大小 16KB),根節點的所有使用者記錄都複製到一個新分配的頁面,通過頁分裂,得到兩個頁,使用者記錄根據索引值從小到大分配到兩個新頁,成為葉子節點,根節點成為儲存目錄項記錄的頁。
    • 目錄項記錄中記錄兩個頁中的最小索引值和頁號,並將記錄根據索引值從小到大排列。
    • 當葉子節點全部存滿之後,再進行上一步的分裂操作,始終保證葉子節點儲存使用者資料,而上級頁面成為目錄項記錄頁。不斷重複這個過程,形成一個多級目錄。
  • 根據主鍵索引查詢

    • 獲取到要查詢的主鍵值之後,訪問主鍵根節點所在頁面
    • 由於根節點中目錄項記錄是根據主鍵索引值從小到大排列,我們可以用二分查詢找到主鍵大概所在的目錄項記錄頁
    • 再到下一層的這個目錄項中繼續二分查詢,找到下層的目錄項記錄頁
    • 直到找到葉子節點的目錄項,在目錄項中查詢到具體的主鍵值
  • 根據自己建立的二級索引查詢

    • 由二級索引建立的B+樹,葉子節點儲存的是二級索引值+主鍵值
    • 根據索引值在由這個索引建立的B+樹中找到所有匹配的二級索引值,再根據它們對應的主鍵值一一回表查詢,得到最終要查詢的完整結果。

回表的代價

  • 二級索引得到的結果過多,回表次數會過多,造成使用這個二級索引查詢的效能低下。
    • 原因:訪問二級索引使用的是順序I/O,因為資料依據大小順序是存放在連續頁中,用連結串列連線的。取出二級索引查詢結果後,去主鍵索引中查詢,並不是按照主鍵大小依次查詢的,所以是隨機I/O,會訪問較多資料頁,造成效能低下。

B+樹索引的適用條件

  • 全值匹配
  • 最左列匹配
  • 列字首匹配
  • 範圍值匹配
  • orderBy排序,且desc,asc不混用
  • groupBy

索引的選擇

  • 只為用於搜尋、分組、排序的列建立索引
  • 列的基數儘量大(重複的值少,查出的結果就少,回表次數少)
  • 索引列資料型別儘量小(MEDIUMINT,INT,BIGINT)
  • 為字串字首建立索引(只取字串前幾位,節約B+樹空間,節省查詢比較的時間)

InnoDB 和 MyISAM 的區別

InnoDB MyISAM
支援事務、外來鍵 不支援事務、外來鍵
必須有主鍵,並作為聚集索引 沒有聚集索引,所有索引都是二級索引,資料和索引分開存放,索引儲存的是資料檔案的指標
不儲存具體行數,要全表掃描得到具體行數 儲存行數資訊
支援表級鎖和行級鎖 只支援表級鎖

連線

內連線: 只連線匹配的行
左外連線: 包含左邊表的全部行(不管右邊的表中是否存在與它們匹配的行),以及右邊表中全部匹配的行
右外連線: 包含右邊表的全部行(不管左邊的表中是否存在與它們匹配的行),以及左邊表中全部匹配的行

優化

執行緒建立方式

  • 繼承Thread類建立執行緒類
    (1)定義Thread類的子類,並重寫該類的run方法,這個run方法的方法體就代表了執行緒要完成的任務。因此把run()方法稱為執行體。
    (2)建立Thread子類的例項,即建立了執行緒物件。
    (3)呼叫執行緒物件的start()方法來啟動該執行緒。

  • 通過Runnable介面建立執行緒類
    (1)定義runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體。
    (2)建立 Runnable實現類的例項,並依此例項作為Thread的target來建立Thread物件,該Thread物件才是真正的執行緒物件。
    (3)呼叫執行緒物件的start()方法來啟動該執行緒。

  • 通過Callable和Future建立執行緒
    (1)建立Callable介面的實現類,並實現call()方法,該call()方法將作為執行緒執行體,並且有返回值。
    (2)建立Callable實現類的例項,使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的返回值。
    (3)使用FutureTask物件作為Thread物件的target建立並啟動新執行緒。
    (4)呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值

Sleep()和Wait()區別

  • 每個物件都有一個鎖來控制同步訪問,Synchronized關鍵字可以和物件的鎖互動,來實現同步方法或同步塊。sleep()方法正在執行的執行緒主動讓出CPU(然後CPU就可以去執行其他任務),在sleep指定時間後CPU再回到該執行緒繼續往下執行(注意:sleep方法只讓出了CPU,而並不會釋放同步資源鎖!!!);wait()方法則是指當前執行緒讓自己暫時退讓出同步資源鎖,以便其他正在等待該資源的執行緒得到該資源進而執行,只有呼叫了notify()方法,之前呼叫wait()的執行緒才會解除wait狀態,可以去參與競爭同步資源鎖,進而得到執行。(注意:notify的作用相當於叫醒睡著的人,而並不會給他分配任務,就是說notify只是讓之前呼叫wait的執行緒有權利重新參與執行緒的排程);

  • sleep()方法可以在任何地方使用;wait()方法則只能在同步方法或同步塊中使用;

  • sleep()是執行緒類(Thread)的方法,呼叫會暫停此執行緒指定的時間,但監控依然保持,不會釋放物件鎖,到時間自動恢復;wait()是Object的方法,呼叫會放棄物件鎖,進入等待佇列,待呼叫notify()/notifyAll()喚醒指定的執行緒或者所有執行緒,才會進入鎖池,不再次獲得物件鎖才會進入執行狀態;

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章