在上一輪的面試中,小林在mysql方面因為作答不夠完善,被面試官吊打了一番,經過兩天的自我復習之后,新的一輪面試又開始了,
面試官:你好,請簡單介紹下自己吧,
小林:你好,我是xxxxxx,之前在深圳的xxx公司負責了xxx系統的研發設計,
面試官:嗯嗯,那我先來問你一些基礎問題吧,
小林:嗯嗯,好的,
面試官:你了解arraylist吧,請說下內部的一些特性,
小林此時心里一下子樂開了花,這個簡單啊,
小林:arraylist的底層主要是由陣列組成,它和普通陣列不太一樣,arraylist具有自動擴容的功能,每次當我們add一個元素到佇列里面的時候,都會有一步確認容量的機制判斷(對應原始碼里面的ensureCapacityInternal函式)如果當陣列內部的元素達到了陣列閾值的時候,就會以1.5倍的體積去做擴容,底層是呼叫了才做系統內部的一個System.arraycopy方法,
又由于arraylist是采用陣列存盤的,在讀取資料的時候可以借助陣列位的下標去快速定位,寫資料的時候需要涉及到挪動陣列,所以讀的性能平均要比寫的性能更高一些,
面試官:嗯嗯,回答地挺全面的,那你覺得在使用arraylist的時候一般會注意些什么嗎?
小林:嗯嗯,有的,一般我會根據代碼的背景關系給arraylist附一個初始值來定位這個陣列的大小,防止其做過多不必要的擴容操作,另外在回圈中進行洗掉操作的時候需要注意會有坑,一般建議采用迭代器的模式來處理,
ps:
如果使用以下這種方式進行元素的移除可能會導致出現洗掉元素不完整的情況:
public static void main(String[] args) { ArrayListApplication arrayListApplication = new ArrayListApplication(); List<String> list = new ArrayList(3); list.add("a"); list.add("c"); list.add("c"); list.add("d"); list.add("e"); System.out.println(list); System.out.println("=========="); removeV1(list, "c"); System.out.println(list); } public static void removeV1(List<String> list, String deleteItem) { for (int i = 0; i < list.size(); i++) { String item = list.get(i); if (item.equals(deleteItem)) { list.remove(item); } } }
列印結果:
[a, c, c, d, e] ========== [a, c, d, e]
此時由于洗掉掉list里面元素之后,list的size值也減少了,隨之導致了陣列元素的前移,因此會出現被洗掉元素的后一位直接繞開了if判斷,沒有被“命中”,如果采用foreach的方式洗掉,則會拋出一段例外資訊,宣告洗掉失敗:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859)
面試官:嗯嗯,你剛剛有說到迭代器洗掉,那么你有了解過迭代器模式嗎?
小林:額,迭代器模式,讓我思考一下.... 背了知識點,但是不記得了....

面試官:好吧...
ps:其實迭代器模式的好處在于,直接幫我們屏蔽了具體的實作細節,通過采用迭代器的方式來幫助我們撰寫一些可以復用的類,例如說arraylist,其實內部也有自己迭代器的具體實作,vector也有自己迭代器模式的具體實作,非常的方便,并且有助于減少代碼里面的對于具體實作的強依賴性,
面試官:那我們切入下一個話題吧,能說下自己對于幻讀的理解嗎?
小林:嗯嗯,可以的,我在上家公司作業的時候,公司內部的事務隔離級別設定為了可重復讀級別,這樣能夠保證當前事務讀取的資料不會受到其他事務提交的影響,但是這種隔離級別會在事務提交完畢之后查詢資料的時候出現幻讀的場景,如果需要解決幻讀的情況需要將事務的隔離級別提升為串行化等級,
面試官:哦,那你在作業中有試過提升為串行化嗎?
小林:沒有,因為串行化是強行在mysql層加鎖,使得事務得排隊執行,容易產生堵塞的情況,性能不佳,
面試官:那你是怎么解決幻讀的情況呢?
小林:嗯....別的同事幫我解決的.....
此時,面試官臉上漸漸露出了詭異的笑容,

ps:其實幻讀這種情況在作業中偶爾還是會遇到的,舉個具體場景:
假設某一時刻,同時有兩個事務訪問了資料庫,需要先從庫里面查詢訂單是否存在,然后再插入新的訂單記錄,
a連接
mysql> select @@global.tx_isolation,@@tx_isolation; +-----------------------+-----------------+ | @@global.tx_isolation | @@tx_isolation | +-----------------------+-----------------+ | REPEATABLE-READ | REPEATABLE-READ | +-----------------------+-----------------+ 1 row in set, 2 warnings (0.00 sec) mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t_order_1 where id =100; Empty set (0.00 sec)
b連接
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t_order_01 where id =100; ERROR 1146 (42S02): Table 'test-db01.t_order_01' doesn't exist mysql> select * from t_order_1 where id =100; Empty set (0.00 sec)
假設在兩邊事務都開啟的一刻,a連接中的事務往資料庫插入了一條id為100的資料,然后commit,a連接
mysql> INSERT INTO `test-db01`.`t_order_1` ( `id`, `order_no`, `product_id`, `user_id`, `create_time`, `update_time` ) -> VALUES -> ( 100, 2, 2, 2, now(), now()); Query OK, 1 row affected (0.00 sec) mysql> select * from t_order_1 where id=100; +-----+----------+------------+---------+---------------------+---------------------+ | id | order_no | product_id | user_id | create_time | update_time | +-----+----------+------------+---------+---------------------+---------------------+ | 100 | 2 | 2 | 2 | 2020-07-14 22:57:37 | 2020-07-14 22:57:37 | +-----+----------+------------+---------+---------------------+---------------------+ 1 row in set (0.00 sec) mysql> commit; Query OK, 0 rows affected (0.00 sec)
當a事務提交結束了,此時b事務開始執行select 查詢校驗的操作,判斷不存在id為100的資料,此時打算執行插入資料的操作:
mysql> select * from t_order_1 where id=100; Empty set (0.00 sec) mysql> INSERT INTO `test-db01`.`t_order_1` ( `id`, `order_no`, `product_id`, `user_id`, `create_time`, `update_time` ) -> VALUES -> ( 100, 2, 2, 2, now(), now()); ERROR 1062 (23000): Duplicate entry '100' for key 'PRIMARY'
結果出現了例外,這種情況我們通常稱之為幻讀,那么該如何解決這種場景的問題呢?其實mysql內部提供了一種叫做next-key-lock的加鎖機制,可以供我們處理這類特殊情況:
mysql> select * from t_order_1 where id=100 for update; +-----+----------+------------+---------+---------------------+---------------------+ | id | order_no | product_id | user_id | create_time | update_time | +-----+----------+------------+---------+---------------------+---------------------+ | 100 | 2 | 2 | 2 | 2020-07-14 23:06:03 | 2020-07-14 23:06:03 | +-----+----------+------------+---------+---------------------+---------------------+ 1 row in set (0.00 sec)
借助 **for update **陳述句,我們可以在應用程式的層面手工實作資料加鎖保護操作,就是那些需要業務層面資料獨占時,可以考慮使用** for update**,
其實 for update 可以理解為一把悲觀鎖,每次獲取資料的時候,都擔心會有其他執行緒修改當前的資料,因此在拿資料的時候就會加入一把鎖,其他試圖改寫資料的請求將會處于堵塞情況,(讀資料的請求不會堵塞)
面試官:那你知道mysql里面的mvcc機制嗎?
小林:mvcc是啥?mvc我倒知道,之前作業中有使用過springmvc框架 blabla(希望把面試官繞開引到自己熟系的話題方向)
面試官滿臉微笑地看著小林,似乎想緩解下尷尬的氣氛,
面試官:好吧,你之前有對快取了解過嗎?
小林:嗯嗯,我在作業中一般喜歡使用redis作為快取,當查詢資料的時候先去redis中查詢,如果redis沒有再去mysql中讀取資料,
面試官:嗯嗯,那你有了解過redis里面的哪些資料結構嗎?
小林:嗯嗯,我在作業中有使用過string,list,hash,zset,set這幾類資料結構,它們各自都有自己的特點,在使用的時候需要結合實際的業務場景來使用,
String型別可以用于存盤一些簡單的鍵值對資料,例如數字,字串之類的,
List結構一般是采用了雙端佇列的結構,這類結構通常使用的命令有lpush,lpop,rpop等,如果想移除某個節點的前置和后置節點就比較簡單(復雜度就是O(1)),但是搜索比較復雜,適合用于存盤一些串列型別的資料資訊,例如說用戶的留言和評論資訊,
Set 是一個無順序的集合,比較常見的例如說交集查詢,用于搜索兩個好友之間共同閱讀過的圖書,或者兩個人之間的共同好友等,使用命令sinter key [key...] 即可實作,

Hash是包含鍵值對的無序散串列,常用命令有:hget,hgetall等,
ZSet則是一套有序集合,通常會根據分值范圍或者成員來獲取元素并且計算一個鍵的排名,
面試官:嗯嗯,你講的這些東西都只是停留在了表層,關于其內部的構造有去做過一些深入的了解嗎?
小林:額,沒有....
此時小林又一次被面試官打擊了自信心
面試官:好吧,今天的一面主要只是問問基礎,那么就先這樣吧,我去找下我老大詢問下,
小林:嗯嗯,好的,(似乎感徑訓有戲)
小林一個人在前臺坐著等待著下一場面試的到來,不僅內心感嘆道,自己過去的作業中過多地安逸于寫crud,很多java的基礎問題也都記得不太清楚了,每天下班之后也沒怎么學習,雖然一面只是問了些基礎問題,結果卻暴露了自己這么多的知識盲區,
過了不久一個陌生的男人慢慢走了過來,天啊,這家伙真的是聰明“絕頂” 了,小林一下子慌了,腦袋一片空白,二面似乎來了一位資深的大佬.....
二面面試官:你好,我是你的二面面試官,下邊我可能會針對你的專案做一些詢問,
(未完待續....)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/122647.html
標籤:Java
