(以下僅為個人見解,如果有誤,歡迎大家批評并指出錯誤,謝謝大家)
1.項目中的驗證碼功能是如何實現的?
- 第一步:在項目的
pom.xml
文件中導入EasyCaptcha
的依賴;
<dependency><groupId>com.github.penggle</groupId><artifactId>easy-captcha</artifactId><version>2.6.2</version>
</dependency>
- 第二步:
Controller
層寫一個captcha
接口,接收前端請求
@Controller
public class CaptchaController {@RequestMapping("/captcha")public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {CaptchaUtil.out(request, response);}
}
- 第三步:前端頁面定義接口
<img src="/captcha" width="130px" height="48px" />
不要忘了把`/captcha路徑從攔截器中排除
2.怎么自己實現一個驗證碼功能?從生成到回顯到驗證,怎么設計?
- 生成驗證碼:
創建一個生成驗證碼的類,這個類專門負責生成隨機的驗證碼字符串,并且生成對應的驗證碼圖片;
可以使用Java的 BufferedImage 類來創建驗證碼圖片,并在圖片上繪制生成的驗證碼文字。
- 回顯驗證碼到前端頁面:
將生成的驗證碼圖片以 Base64 編碼字符串的形式返回給前端頁面,或者直接生成一個驗證碼圖片的 URL 地址,讓前端頁面可以通過這個 URL 獲取驗證碼圖片進行展示。
下面是一個小Demo
:
// 驗證碼生成器類
public class CaptchaGenerator {public static Captcha generateCaptcha() {// 在這里使用你選擇的驗證碼生成庫,例如 EasyCaptcha 或者其他驗證碼生成工具// 生成驗證碼并返回}
}
// Controller 類
@RestController
public class CaptchaController {// 生成驗證碼圖片并返回給前端@GetMapping("/captchaImage")public ResponseEntity<String> getCaptchaImage() {Captcha captcha = CaptchaGenerator.generateCaptcha();String base64Image = captcha.getBase64Image(); // 獲取驗證碼圖片的 Base64 編碼字符串return ResponseEntity.ok(base64Image);}
3.==
和equals
的區別?
==
操作符用于比較兩個對象的引用是否相同,即它們是否指向內存中的同一塊地址。
對于基本數據類型,==
用于比較它們的數值是否相等。
當使用==
操作符比較兩個對象時,如果兩者指向內存中的相同對象,則返回 true;否則返回 false。
equals 方法是 Object 類中定義的方法,用于比較兩個對象的內容是否相等。
在 Object 類中,equals 方法默認實現是與 == 相同的,即比較兩個對象的引用是否相同。
通常情況下,我們會在自定義類中重寫 equals 方法,以便根據對象的內容來判斷是否相等。
總結起來,==
用于比較兩個對象的引用是否相同,而 equals
方法用于比較兩個對象的內容是否相同。
4.重寫equals
要重寫什么?為什么?
???????在Java中,對象的hashCode方法用于計算對象的哈希碼,而哈希碼在哈希表中用于快速查找對象。當我們將自定義對象用作哈希表的鍵時,需要確保對象的hashCode方法返回的哈希碼能夠與equals方法一致。
???????哈希表在查找鍵值對時,首先會根據鍵的哈希碼找到對應的桶,然后在桶中使用equals方法進行比較來找到對應的值。如果equals方法被重寫,hashCode方法沒有被重寫,那么在哈希表中查找時,可能無法正確地找到對應的值。
???????根據Java規范,如果兩個對象根據equals方法是相等的,那么它們的hashCode方法應該返回相同的值。所以在重寫equals方法時,為了保證對象的一致性,必須同時重寫hashCode方法,以確保相等的對象具有相等的哈希碼,從而在哈希表中正確地進行查找。
5.見過哪些重寫equals
的類?
- String類:String類重寫了equals方法,用來比較字符串的內容是否相等。
- Integer類:Integer類重寫了equals方法,用來比較兩個整數對象的值是否相等。
- Boolean類:Boolean類重寫了equals方法,用來比較兩個布爾值對象是否相等。
- Date類:Date類重寫了equals方法,用來比較兩個日期對象是否代表同一時刻。
- 自定義類:在自定義的類中,如果需要自定義相等性判斷邏輯,通常會重寫equals方法。比如,你可以在自定義的Person類中重寫equals方法,根據姓名和年齡來判斷兩個人是否相等。
6.兩個對象的Hashcode
相等,這兩個對象相同嗎?
???????如果兩個對象的hashcode相等,它們并不一定相同,這就是所謂的哈希沖突。因為哈希碼值是通過對象的內容計算出來的,不同的對象可能會有相同的哈希碼值。這種情況下,哈希表中就會出現沖突,需要使用equals方法進行進一步的比較。
???????當我們使用具有哈希表結構的集合類(例如HashMap、HashSet等)時,對象在存儲和檢索時會使用哈希碼值。哈希表可以看做是一個數組,每個元素都是一個鏈表或紅黑樹。當我們向哈希表中添加元素時,系統會自動調用該元素的hashCode方法,計算出哈希碼值,并將其插入到對應的位置上。當我們需要查找元素時,系統也會先使用hashCode方法計算出哈希碼值,然后根據哈希碼值找到對應的位置,最后使用equals方法進行比較,找到目標元素。
???????因此,如果兩個對象的hashCode相等,只能說明它們存儲在哈希表中的位置相同,但不能說明它們一定相同。只有在hashCode相等的情況下,再使用equals方法進行比較,才能判斷兩個對象是否相等。
7.介紹一下類加載器中的雙親委派?
???????類加載器(Class Loader)是負責加載Java類文件并將其轉換為Java虛擬機(JVM)可識別的二進制格式的重要組件。在Java程序運行時,每個類加載器都會維護一棵類加載器層次結構。
???????雙親委派模型(Parent Delegation Model)是類加載器層次結構中的一個重要概念。它是指當類加載器收到一個類加載請求時,它首先會把這個請求委托給它的父類加載器去處理,如果父類加載器還存在父類加載器,則依次向上委托,直到頂層的啟動類加載器。如果父類加載器可以完成類加載任務,就返回類加載結果;否則,子類加載器才會嘗試自己去加載。
???????雙親委派模型的優勢在于保證了Java類的安全性和穩定性。因為在一個類加載器的作用域中,相同名稱的類只會被加載一次,而且這個類的加載是由頂層的啟動類加載器來完成的。這樣就能夠避免不同的類加載器對同一個類的多次加載,從而確保了類的唯一性和一致性
8.重寫和重載的區別?
- 重寫是指繼承關系中,子類繼承了父類中同名同參同返回值的方法,但是訪問修飾符的限制一定要大于或等于被重寫方法的訪問修飾符。重寫是運行時多態。
- 重載是指在同一個類中,方法名相同,但是參數列表不同,如參數個數、類型和順序等。重載是編譯時多態。
9.Java
多線程中int i
進行i++
操作會有什么問題嗎?如何解決?
- 在Java多線程中,對一個整型變量 int i 執行 i++ 操作可能會引發線程安全性問題。這是因為 i++ 操作并不是原子操作,它包括讀取 i 的當前值、對其進行加一操作、然后將結果寫回 i。在多線程環境下,如果有多個線程同時對 i 執行 i++ 操作,就可能導致競態條件和數據不一致的問題。
為了解決這個問題,可以采取以下幾種方法來保證對 i 的操作是線程安全的:
-
使用synchronized關鍵字:
???????可以使用synchronized關鍵字來保護對 i 的操作,確保同一時刻只有一個線程可以執行 i++ 操作。例如:synchronized (this) {i++; }
-
使用AtomicInteger類:
???????可以使用java.util.concurrent.atomic.AtomicInteger類來代替普通的 int 類型,它提供了一系列原子操作,可以保證對 i 的操作是線程安全的。例如:AtomicInteger atomicI = new AtomicInteger(0);atomicI.incrementAndGet();
-
使用volatile關鍵字:
???????使用volatile關鍵字修飾 i 變量,可以確保多個線程看到的是同一個變量副本,從而避免一些可見性問題。但這并不能解決i++操作的原子性問題,仍需要額外的手段來保證原子性。 -
使用ReentrantLock:
???????可以使用顯式的鎖來保護對 i 的操作,例如java.util.concurrent.locks.ReentrantLock。通過獲取鎖之后執行 i++ 操作,然后釋放鎖,來保證操作的原子性和線程安全性。
10.Redis
中有很多Key
同時過期,會發生什么現象?在項目業務中會有什么影響?
???????在Redis中,如果有很多Key同時過期,會導致Redis的性能下降,并且可能會引發一些問題,例如:
- Redis CPU 占用率上升:當有大量Key同時過期時,Redis會將其從內存中刪除。這個操作可能會消耗大量的CPU資源,導致Redis的CPU占用率上升,影響Redis的性能和穩定性。
- 緩存穿透: 如果很多Key同時過期,并且這些Key都是熱點數據,那么這些數據在被重新加載到Redis之前,如果有大量請求訪問這些數據,就會導致緩存穿透問題,使得請求直接打到后端數據庫,增加了數據庫的負載。
- 緩存雪崩: 如果很多Key同時過期,那么這些Key在過期之后,如果都同時被請求到,就會導致緩存雪崩問題,使得請求都打到后端數據庫,增加了數據庫的負載。
- 數據丟失:如果Redis中的Key同時過期,并且沒有進行持久化存儲,那么這些過期的數據將會被永久刪除。如果這些數據是重要的業務數據,可能會導致數據丟失的問題。
11.Redis
中只知道某些數據的前綴,如何查找這一類數據?
在Redis中,可以使用KEYS命令來查找符合特定模式的Key。如果我們只知道某些數據的前綴,可以使用通配符*來匹配后面的字符,從而查找這一類數據。
例如,如果我們想查找所有以"foo_"為前綴的Key,可以使用以下命令:
KEYS foo_*
這個命令會返回所有以"foo_"為前綴的Key列表。需要注意的是,KEYS命令會掃描Redis中的所有Key,如果數據量很大,可能會對Redis的性能產生影響。因此,應該盡量避免頻繁地使用KEYS命令。
12.Redis
中Keys
和Scan
的區別?你推薦用哪一個?
Keys
和Scan
都是Redis
中用于查找Key
的命令,但它們在實現方式和使用場景上有所不同。
Keys
命令會掃描Redis
中所有的Key
,返回符合特定模式的Key
列表。這個命令簡單易用,但如果Redis
中的Key
數量很大,就可能會對Redis
的性能產生影響。因此,不建議在生產環境中頻繁使用Keys
命令。- 相比之下,
Scan
命令更加高效和安全。Scan
命令通過游標分步掃描整個數據庫,每次返回一部分數據,直到遍歷完整個數據庫。這種方式可以避免在一次操作中對整個數據庫進行掃描,從而減輕Redis
的負擔,提高命令的執行效率。此外,Scan
命令還支持并發修改和過期Key
的情況,可以保證在執行期間不會出現數據丟失或重復訪問的問題。
13.MySQL
數據庫中int(5)
,存儲1,查詢結果是?
???????在MySQL中,對于int(5)來說,括號中的數字只表示顯示寬度,并不會影響存儲或數據類型的范圍。所以存儲值為1時,查詢結果仍然會返回整數1,后面不會出現亂碼。
???????這種情況與char和varchar是不同的。對于char和varchar類型,指定的長度是用于限制存儲的實際字符數量,而int(5)中的5只是用于指定展示寬度,并不影響實際存儲的值。
14.char
和Varchar
的區別?
char
:char
類型是一種固定長度的字符類型,需要指定存儲的最大字符數量。存儲的值會被固定在指定長度內,如果存儲的值超過了定義的長度,會發生截斷。如果不足指定長度,會在末尾用空格進行填充;varchar
:varchar
類型是一種可變長度的字符類型。它也需要指定存儲的最大字符數量。但是varchar
類型存儲的值會根據實際長度動態調整占用的存儲空間,不會進行填充或截斷。只占用實際需要的存儲空間。
15.id = 1
,怎么給他加一個查詢鎖?
???????在MySQL
中,可以通過使用SELECT ... FOR UPDATE
語句為特定的行添加查詢鎖。這樣做可以確保在當前事務中對該行的讀取操作不會被其他事務所修改。
SELECT * FROM your_table WHERE id = 1 FOR UPDATE;
16.如何給name
字段加一個索引。
???????可以在已經存在的表上為需要優化查詢的字段創建索引:
CREATE INDEX idx_name ON your_table (name);
17.索引列有null
值會對索引列有影響嗎?
索引列有
NULL
值會對索引的效率和查詢結果產生影響。
- 在
B-Tree
索引中,NULL
值被視為一個特殊值,與其他值分開存儲。如果索引列包含NULL
值,則在索引中將為每個NULL
值保存一個指針。這樣,當查詢需要查找NULL
值時,它會使用這些指針來定位匹配的行。 - 然而,由于
NULL
值不是實際的值,因此它們無法進行比較運算。因此,在使用索引進行排序或范圍查詢時,包含NULL
值的列通常不會被使用。 - 另外,當查詢條件中包含索引列時,如果列包含
NULL
值,那么索引可能無法使用。例如,如果查詢條件是WHERE index_column = 5
,并且索引列包含NULL
值,那么索引可能無法使用,因為索引無法判斷NULL
值是否等于 5。 - 需要注意的是,如果索引列中包含大量的
NULL
值,那么它可能會影響索引的性能和空間占用。因此,在設計索引時,需要考慮到數據的實際情況和查詢需求,適當地處理NULL
值。例如,可以使用部分索引或過濾條件來排除NULL
值。
18.復合索引中有NULL
值有影響嗎?
???????在復合索引中,NULL
值對索引的影響是需要注意的。復合索引是由多個列組成的索引,用于加快查詢的速度。當涉及到 NULL
值時,以下情況需要考慮:
-
NULL
值的位置:
???????如果復合索引中的某個列包含NULL
值,那么整個索引的鍵值也將是NULL
。在這種情況下,該索引項將不會包含具體的值,只會標識為NULL
。因此,在使用復合索引進行查詢時,如果需要匹配到具有NULL
值的列,那么該索引就會被使用。 -
匹配
NULL
值:
???????如果查詢條件中涉及到了包含NULL
值的列,那么復合索引可能會被使用來篩選滿足條件的行。 -
不匹配
NULL
值:
???????當查詢條件排除了NULL
值時,復合索引可能無法使用,因為索引中的鍵值包含NULL
值。此時,數據庫可能會選擇其他索引或執行全表掃描來滿足查詢條件。
???????需要注意的是,復合索引中的 NULL
值并不會影響索引的創建和維護。它主要會影響到查詢時的索引使用情況。
???????總結起來,復合索引中的 NULL
值對索引的影響取決于具體的查詢條件。如果查詢需要匹配或排除包含 NULL
值的列,那么復合索引可能會被使用。而當查詢條件不涉及 NULL
值時,NULL
值對索引的影響較小,可能會導致索引無法使用。因此,在設計復合索引時需要考慮到數據的特點和實際查詢需求。
19.NOT IN
和 NOT EXISTS
的區別?
NOT IN
和NOT EXISTS
都是用于條件查詢中的否定操作符,用于篩選出不滿足指定條件的結果。盡管它們可以達到相同的效果,但它們在實現方式和性能方面存在一些區別。
NOT IN
是一個子查詢操作符,用于從一個查詢結果中排除滿足給定條件的行。它通常與子查詢結合使用,將子查詢結果與外部查詢進行比較,并返回不匹配條件的結果。
NOT EXISTS
:NOT EXISTS
也是一個子查詢操作符,用于檢查子查詢的結果是否為空。如果子查詢返回空集,則NOT EXISTS
為真,否則為假。它常用于判斷某個條件下是否存在符合要求的數據。
NOT IN
使用一個子查詢來獲取比較值,而NOT EXISTS
只需確定子查詢是否返回結果。NOT IN
適用于對單個列進行比較,而NOT EXISTS
可以使用子查詢中的任意條件進行比較。- 從性能角度來看,
NOT EXISTS
在某些情況下可能更有效,因為它只需確定子查詢是否返回結果,而NOT IN
需要將所有結果加載到內存中進行比較。
20.請自己設計一個算法實現字符串的反轉?
方法一:可以使用雙指針來實現字符串的反轉
- 首先將字符串轉換為字符數組;
- 使用雙指針法,一個指向數組的頭部,一個指向數組的尾部。
- 交換數組頭部和尾部的元素。
- 將指針向中心靠攏,重復執行第3步,直到指針相遇。
源碼如下:
package com.kfm;import java.util.Scanner;/*** {class description}** @author SWP* @version 1.0.0*/
public class StringReversal {public static String reverseString(String s){if (s == null || s.length() == 0) {return s;}// 將字符串轉換為字符數組char[] arr = s.toCharArray();int left = 0;int right = arr.length - 1;while (left < right){char temp = arr[left];arr[left] = arr[right];arr[right] = temp;left++;right--;}// 將字符數組轉換為字符串return new String(arr);}public static void main(String[] args){Scanner sc = new Scanner(System.in);System.out.println("請輸入一個字符串:");String s = sc.nextLine();System.out.println("反轉后的字符串為:" + reverseString(s));}
}
運行效果:
方法二:使用遞歸實現字符串的反轉
源碼如下:
package com.kfm;import java.util.Scanner;/*** {class description}** @author SWP* @version 1.0.0*/
public class StringReversal02 {public static String reverseString(String s) {// 遞歸結束的條件:當字符串長度為0或者1時,無需再反轉字符串,直接返回字符串if (s.length() <= 1) {return s;}// 遞歸調用:將字符串分解為首字符和剩余部分,然后對剩余部分進行遞歸反轉,并將首字符放在最后return reverseString(s.substring(1)) + s.charAt(0);}public static void main(String[] args) {Scanner sc = new Scanner(System.in);System.out.println("請輸入一個字符串:");String s = sc.nextLine();System.out.println("反轉后的字符串為: "+ reverseString(s));}
}