第一天
1. 快速排序
public class QuickSort {public static void quickSort(int[] arr, int low, int high) {if (low < high) {// 分區操作,獲取基準元素的最終位置int pivotIndex = partition(arr, low, high);// 遞歸排序基準元素左邊的部分quickSort(arr, low, pivotIndex - 1);// 遞歸排序基準元素右邊的部分quickSort(arr, pivotIndex + 1, high);}}private static int partition(int[] arr, int low, int high) {// 選擇最后一個元素作為基準元素int pivot = arr[high];int i = low - 1;for (int j = low; j < high; j++) {if (arr[j] <= pivot) {i++;// 交換 arr[i] 和 arr[j]swap(arr, i, j);}}// 將基準元素放到正確的位置swap(arr, i + 1, high);return i + 1;}private static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}public static void main(String[] args) {int[] arr = {10, 7, 8, 9, 1, 5};int n = arr.length;System.out.println("排序前的數組:");for (int num : arr) {System.out.print(num + " ");}quickSort(arr, 0, n - 1);System.out.println("\n排序后的數組:");for (int num : arr) {System.out.print(num + " ");}}
}
2. 插入排序
public static void insertionSort(int[] arr) {int n = arr.length;for (int i = 1; i < n; i++) {int key = arr[i];int j = i - 1;while (j >= 0 && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;}}
3. 解釋java三大特征
- 封裝:指的是把對象的屬性和方法捆綁在一起,同時隱藏對象的內部實現細節,僅對外提供必要的訪問方式。這樣做可以增強數據的安全性,避免外部隨意修改對象內部數據。
- 繼承:指一個類(子類)可以繼承另一個類(父類)的屬性和方法,從而實現代碼的復用和擴展。子類能夠擁有父類的所有非私有成員,還能添加自己的獨特屬性和方法。
- 多態:意味著同一方法可以根據調用對象的不同類型表現出不同的行為。多態主要通過方法重寫和接口實現來達成(編譯時多態是在編譯階段就確定要調用的方法,它主要通過方法重載來實現。方法重載指的是在一個類中可以定義多個同名的方法,但這些方法的參數列表(參數的類型、個數或順序)不同;運行時多態是在運行階段才確定要調用的方法,它主要通過方法重寫和向上轉型來實現。方法重寫是指子類重寫父類的方法,向上轉型是指將子類對象賦值給父類引用。)
4. 反射機制
在 Java 中,反射機制允許程序在運行時動態地獲取類的信息,并且可以調用類的方法、訪問和修改類的屬性等。下面將詳細介紹 Java 反射機制的原理、關鍵類以及使用場景。
Java 反射機制的核心原理在于 Java 程序運行時,每個類都會在內存中生成一個 java.lang.Class 對象,該對象包含了這個類的完整結構信息,如類名、父類、接口、方法、字段等。通過這個 Class 對象,程序就能在運行時動態地獲取和操作類的各種信息。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;// 定義一個示例類
class Person {private String name;public int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public void setName(String name) {this.name = name;}public String getName() {return name;}public void sayHello() {System.out.println("Hello, my name is " + name + ", I'm " + age + " years old.");}
}public class ReflectionExample {public static void main(String[] args) throws Exception {// 獲取 Person 類的 Class 對象Class<?> personClass = Person.class;// 創建 Person 類的實例Constructor<?> constructor = personClass.getConstructor(String.class, int.class);Person person = (Person) constructor.newInstance("Alice", 20);// 調用 Person 類的方法Method sayHelloMethod = personClass.getMethod("sayHello");sayHelloMethod.invoke(person);// 訪問和修改 Person 類的字段Field ageField = personClass.getField("age");ageField.set(person, 21);// 調用修改后的方法sayHelloMethod.invoke(person);}
}
5.深克隆和淺克隆
- 淺克隆(Shallow Clone)
定義:在淺克隆中,創建一個新對象,新對象的基本數據類型(如int、double等)會復制其值,而對于引用類型的成員變量,僅僅復制引用,即新對象和原對象的引用類型成員變量指向同一個內存地址。這意味著如果修改其中一個對象的引用類型成員變量所指向的對象內容,另一個對象也會受到影響。 - 深克隆(Deep Clone)
定義:深克隆會創建一個新對象,并且遞歸地復制原對象的所有成員變量,包括引用類型的成員變量。這意味著新對象和原對象的所有成員變量都有各自獨立的內存空間,修改其中一個對象的任何成員變量都不會影響另一個對象。
6. 數據庫和緩存如何保持一致性?
先更新數據庫,再刪除緩存(推薦)
此策略是先對數據庫中的數據進行更新,更新成功后,將緩存中的對應數據刪除。
- 優點:
數據一致性較高:在大多數情況下能保證數據的最終一致性。因為讀請求在緩存未命中時會從數據庫獲取最新數據并更新緩存。
性能較好:相比于更新緩存,刪除緩存的操作更加輕量級。 - 缺點:
存在短暫不一致:在更新數據庫后、刪除緩存前,如果有讀請求進來,會讀取到舊的緩存數據。不過這種不一致的時間窗口通常較短。
保證一致性的額外措施
- 重試機制
在使用 “先更新數據庫,再刪除緩存” 策略時,如果刪除緩存失敗,可以引入重試機制。可以將刪除緩存的操作記錄到消息隊列中,通過消息隊列的重試功能來保證緩存最終被刪除。 - 異步更新
對于一些對實時性要求不是特別高的場景,可以采用異步更新的方式。例如在更新數據庫后,通過消息隊列異步地更新或刪除緩存,這樣可以減少對業務邏輯的影響,提高系統的吞吐量。 - 分布式鎖
在高并發場景下,可以使用分布式鎖來保證同一時間只有一個線程進行數據庫和緩存的更新操作,避免并發問題導致的數據不一致。 - 緩存過期時間
為緩存設置合理的過期時間,這樣即使出現短暫的數據不一致,在緩存過期后也能從數據庫獲取到最新數據并更新緩存,保證數據的最終一致性。
7.HashMap 底層原理
- 數據結構
在 Java 中,HashMap 底層數據結構是數組 + 鏈表 + 紅黑樹(JDK 1.8 及以后)。 - 擴容機制
HashMap 有一個負載因子(默認為 0.75),當鍵值對數量超過數組長度乘以負載因子時,會觸發擴容操作。擴容時,數組長度會變為原來的 2 倍,然后將原數組中的所有鍵值對重新計算哈希值并插入到新數組中。 - 線程安全
HashMap 不是線程安全的。在多線程環境下,對 HashMap 進行并發操作可能會導致以下問題:
- 數據不一致
多個線程同時對 HashMap 進行插入、刪除或修改操作時,可能會出現數據覆蓋或丟失的情況。例如,線程 A 和線程 B 同時插入一個鍵值對,且它們計算得到的數組下標相同,可能會導致其中一個線程的插入操作被覆蓋。 - 死循環(JDK 1.7)
在 JDK 1.7 中,HashMap 在擴容時采用頭插法,當多個線程同時進行擴容操作時,可能會導致鏈表形成環形結構,從而引發死循環。 - 解決方案
如果需要在多線程環境下使用哈希表,可以考慮以下替代方案:
ConcurrentHashMap:是線程安全的哈希表,它采用分段鎖(JDK 1.7)或 CAS + synchronized(JDK 1.8)的方式來保證線程安全,并發性能較高。
Hashtable:也是線程安全的哈希表,但它使用 synchronized 關鍵字對整個方法進行同步,并發性能較低。
8. ConcurrentHashMap和hashtable和hashmap區別和作用
區別
- 線程安全性
HashMap:它并非線程安全的類。在多線程環境下,如果多個線程同時對 HashMap 進行讀寫操作,可能會引發數據不一致、死循環(JDK 1.7)等問題。因此,HashMap 適用于單線程環境。
Hashtable:是線程安全的類。它的方法都被 synchronized 關鍵字修飾,這意味著同一時間只能有一個線程訪問 Hashtable 的方法。這種實現方式雖然保證了線程安全,但在多線程高并發場景下,由于所有操作都需要獲取鎖,會導致性能嚴重下降。
ConcurrentHashMap:同樣是線程安全的類,但它的并發性能要比 Hashtable 高很多。在 JDK 1.7 中,ConcurrentHashMap 使用分段鎖機制,將整個哈希表分成多個段(Segment),每個段都有自己的鎖,不同段之間的操作可以并發進行。在 JDK 1.8 中,ConcurrentHashMap 摒棄了分段鎖,采用 CAS(Compare-And-Swap)和 synchronized 來保證并發操作的線程安全,進一步提高了并發性能。 - 對 null 鍵和 null 值的支持
HashMap:允許鍵和值為 null。也就是說,你可以向 HashMap 中插入一個鍵為 null 的鍵值對,也可以插入值為 null 的鍵值對。
Hashtable:不允許鍵或值為 null。如果嘗試插入 null 鍵或 null 值,會拋出 NullPointerException。
ConcurrentHashMap:和 Hashtable 一樣,不允許鍵或值為 null。這是因為 ConcurrentHashMap 主要用于多線程環境,null 值可能會導致歧義,例如在 get 方法返回 null 時,無法確定是鍵不存在還是值本身為 null。 - 性能
HashMap:在單線程環境下,由于不需要考慮線程安全問題,HashMap 的性能是最高的。
Hashtable:由于采用了全量同步機制,在多線程環境下,所有線程都需要競爭同一把鎖,性能較低,尤其是在高并發場景下,會成為系統的性能瓶頸。
ConcurrentHashMap:在多線程環境下,通過分段鎖(JDK 1.7)或 CAS + synchronized(JDK 1.8)的方式,允許更多的線程同時進行讀寫操作,并發性能遠遠高于 Hashtable。
作用
- HashMap
HashMap 適用于單線程環境,當你的應用程序不需要考慮線程安全問題,并且對性能有較高要求時,可以使用 HashMap。例如,在一個單線程的工具類中,需要存儲一些臨時數據,使用 HashMap 是一個不錯的選擇。 - Hashtable
由于 Hashtable 的性能較低,現在已經很少被使用。但在一些對線程安全有要求,且并發程度不高的場景下,仍然可以考慮使用 Hashtable。不過,通常更推薦使用 ConcurrentHashMap 來替代 Hashtable。 - ConcurrentHashMap
ConcurrentHashMap 主要用于多線程環境,特別是在高并發場景下。例如,在一個多線程的緩存系統中,多個線程可能會同時對緩存進行讀寫操作,使用 ConcurrentHashMap 可以保證線程安全,同時提供較高的并發性能。
9.分段鎖
- 原理
ConcurrentHashMap 在 JDK 1.7 里將整個哈希表劃分成多個相互獨立的段(Segment),每一個段本質上就是一個小的 HashTable,并且每個段都有屬于自己的鎖。不同的段能夠并發地進行操作,這樣在多線程環境下,不同線程可以同時訪問不同的段,進而提升了并發性能。
10.ThreadLocal
ThreadLocal 是 Java 里的一個類,它為每個使用該變量的線程都單獨創建一個獨立的副本,各個線程能夠獨立地改變自己的副本,而不會對其他線程的副本產生影響。
- 原理
ThreadLocal 的核心原理是借助每個線程內部的 ThreadLocalMap 來存儲數據。ThreadLocalMap 是 ThreadLocal 的一個靜態內部類,它以 ThreadLocal 對象作為鍵,以線程的變量副本作為值。當線程調用 ThreadLocal 的 set 方法時,實際上是把值存進了當前線程的 ThreadLocalMap 中;當調用 get 方法時,會從當前線程的 ThreadLocalMap 里獲取對應的值。 - 使用場景
線程安全:當某些對象不是線程安全的,但又希望在多線程環境下使用時,可以借助 ThreadLocal 為每個線程創建一個獨立的對象副本,以此保證線程安全。例如,SimpleDateFormat 不是線程安全的,使用 ThreadLocal 能為每個線程創建一個獨立的 SimpleDateFormat 實例。
上下文管理:在一個線程的執行過程中,需要在不同的方法之間傳遞一些上下文信息,此時可以使用 ThreadLocal 來存儲這些信息,避免在方法參數中頻繁傳遞。例如,在一個 Web 應用中,可以使用 ThreadLocal 來存儲當前用戶的信息。
import java.text.SimpleDateFormat;
import java.util.Date;public class ThreadLocalExample {// 創建一個 ThreadLocal 對象,用于存儲 SimpleDateFormat 實例private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");}};public static String formatDate(Date date) {// 從 ThreadLocal 中獲取當前線程的 SimpleDateFormat 實例SimpleDateFormat dateFormat = dateFormatThreadLocal.get();return dateFormat.format(date);}public static void main(String[] args) {// 創建一個日期對象Date date = new Date();// 創建兩個線程,分別格式化日期Thread thread1 = new Thread(() -> {String formattedDate = formatDate(date);System.out.println("線程 1 格式化的日期: " + formattedDate);});Thread thread2 = new Thread(() -> {String formattedDate = formatDate(date);System.out.println("線程 2 格式化的日期: " + formattedDate);});// 啟動線程thread1.start();thread2.start();try {// 等待線程執行完畢thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}// 移除當前線程的 ThreadLocal 變量dateFormatThreadLocal.remove();}
}
11.索引
索引是一種數據結構,用于提高數據庫表中數據的查詢效率。它就像一本書的目錄,能夠幫助數據庫快速定位到所需的數據行,而不必全表掃描。
- 常見類型
B-tree 索引:最常見的索引類型,適用于全值匹配、范圍查詢、前綴匹配等多種查詢場景。
哈希索引:基于哈希表實現,只能用于精確匹配,查詢速度非常快,但不支持范圍查詢。
全文索引:用于在文本類型的列中進行全文搜索,能夠快速找到包含特定關鍵詞的記錄。 - 使用場景和優化
經常用于查詢條件、連接條件的列上適合創建索引。
索引并非越多越好,過多的索引會增加數據插入、更新和刪除的成本,因為每次數據變更都需要同時更新索引。
12.事務
事務是一組數據庫操作,這些操作要么全部成功執行,要么全部不執行,是一個不可分割的工作單元,以保證數據庫的一致性。
- 特性(ACID)
- 原子性(Atomicity):事務中的所有操作要么全部完成,要么全部不執行,不會出現部分執行的情況。
- 一致性(Consistency):事務執行前后,數據庫的完整性約束沒有被破壞,數據從一個一致狀態轉換到另一個一致狀態。
- 隔離性(Isolation):多個事務并發執行時,相互之間不會干擾,就像每個事務都是獨立執行一樣。
- 持久性(Durability):事務一旦提交,其對數據庫的修改就會永久保存,即使數據庫發生故障也不會丟失。
13.并發問題及隔離級別
- 臟讀:一個事務讀取了另一個未提交事務修改的數據。
- 不可重復讀:在同一個事務中,多次讀取同一數據時,由于其他事務對該數據進行了修改并提交,導致每次讀取的結果不一致。
- 幻讀:在一個事務中,按照某個條件查詢數據時,由于其他事務插入或刪除了符合該條件的數據,導致前后兩次查詢的結果集不一致。
- 隔離級別:包括讀未提交(Read Uncommitted)、讀已提交(Read Committed)、可重復讀(Repeatable Read)和串行化(Serializable)。MySQL 的默認隔離級別是可重復讀,通過 MVCC(多版本并發控制)來解決并發問題,避免臟讀、不可重復讀和部分幻讀情況的發生。
14.MVCC(多版本并發控制)
MVCC 是 MySQL 在可重復讀隔離級別下實現高并發的關鍵技術。它通過在每行數據后面保存兩個隱藏的列來實現,一個是創建版本號,一個是刪除版本號。
當事務讀取數據時,會根據事務的版本號和數據的版本號來判斷數據是否可見,從而實現了不同事務之間的并發訪問控制,避免了數據的沖突和不一致。
15.三個日志
- redo log(重做日志)
用于記錄數據庫的物理修改,即數據頁的修改記錄。當數據庫發生故障時,通過 redo log 可以將數據庫恢復到故障前的狀態,保證事務的持久性。
它是順序寫入的,寫入性能很高,并且在事務提交時,會將 redo log buffer 中的數據刷新到磁盤上的 redo log 文件中。 - undo log(回滾日志)
主要用于事務回滾和 MVCC。在事務執行過程中,如果需要回滾,就可以根據 undo log 中的記錄來撤銷對數據的修改。
同時,undo log 也為 MVCC 提供了數據的歷史版本信息,使得在不同事務中可以看到數據的不同版本。 - bin log(二進制日志)
記錄了數據庫的所有更新操作,包括數據的插入、更新、刪除等,不包含查詢語句。它主要用于數據庫的備份、恢復以及主從復制。
前的狀態,保證事務的持久性。
它是順序寫入的,寫入性能很高,并且在事務提交時,會將 redo log buffer 中的數據刷新到磁盤上的 redo log 文件中。 - undo log(回滾日志)
主要用于事務回滾和 MVCC。在事務執行過程中,如果需要回滾,就可以根據 undo log 中的記錄來撤銷對數據的修改。
同時,undo log 也為 MVCC 提供了數據的歷史版本信息,使得在不同事務中可以看到數據的不同版本。 - bin log(二進制日志)
記錄了數據庫的所有更新操作,包括數據的插入、更新、刪除等,不包含查詢語句。它主要用于數據庫的備份、恢復以及主從復制。
在主從復制中,主庫將 bin log 中的日志發送給從庫,從庫根據這些日志來重新執行相應的操作,從而實現主從庫的數據一致性。