被美團【面試官】榨干了!
大家好,我是Tom哥。
今天分享一份美團春招實習的 Java 崗的面經,總共被問了接近 50 個八股文,問了非常多 Java 框架和 Java 并發的問題
面試時長超過了 1 個小時,感覺被榨干了。
MySQL
可重復讀和已提交讀隔離級別表現的現象是什么,區別是什么樣的?
已提交讀只能讀取其他事務已經提交的數據,但是這個隔離級別就會造成一個不可重復讀和幻讀的現象,可重復讀就是消除了不可重復讀和幻讀的現象。
補充:
- 讀提交,指一個事務提交之后,它做的變更才能被其他事務看到,會有不可重復讀、幻讀的問題。
- 可重復讀,指一個事務執行過程中看到的數據,一直跟這個事務啟動時看到的數據是一致的,MySQL InnoDB 引擎的默認隔離級別,解決了不可重復讀的問題,并且以很大程度上避免幻讀現象的發生。
數據文件大體分成哪幾種數據文件?
因為是聚簇索引,所以只有一個文件,索引和數據是放在一起的,就是一個bd文件。
補充:我們每創建一個 database(數據庫) 都會在 /var/lib/mysql/ 目錄里面創建一個以 database 為名的目錄,然后保存表結構和表數據的文件都會存放在這個目錄里。
比如,我這里有一個名為 my_test 的 database,該 database 里有一張名為 t_order 數據庫表。
然后,我們進入 /var/lib/mysql/my_test 目錄,看看里面有什么文件?
[root@xiaolin ~]#ls /var/lib/mysql/my_test
db.opt
t_order.frm
t_order.ibd
可以看到,共有三個文件,這三個文件分別代表著:
- db.opt,用來存儲當前數據庫的默認字符集和字符校驗規則。
- t_order.frm ,t_order 的表結構會保存在這個文件。在 MySQL 中建立一張表都會生成一個.frm 文件,該文件是用來保存每個表的元數據信息的,主要包含表結構定義。
- t_order.ibd,t_order 的表數據會保存在這個文件。表數據既可以存在共享表空間文件(文件名:ibdata1)里,也可以存放在獨占表空間文件(文件名:表名字.ibd)。這個行為是由參數 innodb_file_per_table 控制的,若設置了參數 innodb_file_per_table 為 1,則會將存儲的數據、索引等信息單獨存儲在一個獨占表空間,從 MySQL 5.6.6 版本開始,它的默認值就是 1 了,因此從這個版本之后, MySQL 中每一張表的數據都存放在一個獨立的 .ibd 文件。
mysql日志文件是分成了哪幾種?
mysql有三種日志文件,binlog,redolog和undolog。
補充:
- redo log 重做日志,確保事務的持久性
- undo log 回滾日志,確保事務的原子性,用于回滾事務,同時提供mvcc下的非鎖定讀
- bin log 二進制日志,用于主從復制場景下,記錄master做過的操作
- relay log 中繼日志,用于主從復制場景下,slave通過io線程拷貝master的bin log后本地生成的日志
- 慢查詢日志,用于記錄執行時間過長的sql,需要設置閾值后手動開啟
說下MVCC機制的原理?
MVCC就是多版本并發控制,實現了讀寫的并發控制,在mysql通過readview 隱藏字段和undolog實現了,比如在可重復讀里面,比如開啟了一個事務,就生成了一個readview,然后記錄現在active的事務,判斷查詢的數據在這個事務可不可讀。
索引的類型有哈希索引,B+樹索引,而hash索引的時間復雜度是o1,那為什么我們一般情況下不使用哈希索引,而使用b+樹索引呢?
因為hash索引只能做一個等值查詢,像范圍查詢是做不到的。還有哈希可能會出現hash碰撞的問題。
還能想到其他的原因嗎?
想不到了
補充:
- 哈希索引的key是經過hash運算得出的,即跟實際數據的值沒有關系,因此哈希索引不適用于范圍查詢和排序操作
- 容易導致全表掃描,因為可能存在不同的key經過hash運算后值相同
- 索引列上的值相同的話,易造成hash沖突,效率低下
對一個慢sql怎么去排查?
如果是在項目中,可以通過SpringAOP去查詢這個接口運行的時間,如果是一個sql,可以通過explain的指令去查這個sql的執行計劃。
補充:
可通過開啟mysql的慢日志查詢,設置好時間閾值,進行捕獲
索引字段是不是建的越多越好
不是,建的的越多會占用越多的空間
補充:
索引越多,在寫入頻繁的場景下,對于B+樹的維護所付出的性能消耗也會越大
網絡
http協議的報文的格式有了解嗎?
不是太清楚,有報文頭,報文體,如果是post請求就會在報文體寫上數據。
補充:
HTTP 請求報文結構
HTTP 的請求報文分為三個部分:
請求行、首部行、實體主體。
http常用的狀態碼?
4XX是請求報文有誤,5XX是服務器有錯,2XX是成功的。3XX忘了(是重定向
補充:
RFC 規定 HTTP 的狀態碼為三位數,被分為五類:
- 1xx:表示目前是協議處理的中間狀態,還需要后續操作
- 2xx:表示成功狀態
- 3xx:重定向狀態,資源位置發生變動,需要重新請求
- 4xx:請求報文有誤
- 5xx:服務端發生錯誤
Java框架
java這一塊對框架都是熟悉的吧?
只用過SSM,SpringBoot還在學中。
MyBatis運用了哪些常見的設計模式?
運用了工廠模式,創建sqlsession的時候用了工廠模式,其他的沒想起來。
補充:
- 工廠模式,工廠模式在 MyBatis 中的典型代表是 SqlSessionFactory
- 建造者模式,建造者模式在 MyBatis 中的典型代表是 SqlSessionFactoryBuilder
- 單例模式,單例模式在 MyBatis 中的典型代表是 ErrorContext
- 適配器模式,適配器模式在 MyBatis 中的典型代表是 Log
- 代理模式,代理模式在 MyBatis 中的典型代表是 MapperProxyFactory
- 模板方法模式,模板方法在 MyBatis 中的典型代表是 BaseExecutor
- 裝飾器模式,裝飾器模式在 MyBatis 中的典型代表是 Cache
MyBatis中創建了一個Mapper接口,在寫一個xml文件,java的接口是要實現的,為什么這沒有實現呢?
(現在回想起來,是要誘導我說動態代理)一個mapper接口通過namespace的id對應的就是一個xml的文件。沒了解背后的原理
補充:
MyBatis中的Mapper接口并不需要實現,它只是定義了一組方法簽名。MyBatis會根據Mapper接口中的方法名、參數類型和返回值類型,自動生成實現方法。因此,Mapper接口中的方法不需要實現,也不需要在該接口中編寫任何方法體。
相反,你需要編寫一個與Mapper接口同名的XML文件,來實現這些方法的具體SQL操作。這樣,當你在Java代碼中調用Mapper接口中的方法時,MyBatis會自動將該方法映射到對應的XML文件中的SQL語句,并執行該語句。
與傳統的JDBC相比,MyBatis的優點?
有些功能封裝的更好,像打開一個sqlsession的連接,而且寫的代碼要少
補充:
- mybatis的全局配置文件中可以設置數據庫連接池,和spring整合可以配置數據庫連接
- mybatis把sql和代碼分離,提供了Mapper.xml映射文件,在映射文件中通過標簽來寫sql
- mybatis中自動完成java對象和sql中參數的映射
- mybatis中通過ResultSetHandler自動將結果集映射到對應的java對象中
還記得JDBC連接數據庫的步驟嗎?
不記得了,太久沒用JDBC
補充:
使用JDBC連接數據庫的步驟如下:
- 加載數據庫驅動程序:使用Class.forName()方法加載對應的數據庫驅動程序,例如:Class.forName("com.mysql.jdbc.Driver");
- 建立數據庫連接:使用DriverManager.getConnection()方法建立與數據庫的連接,需要指定數據庫的URL、用戶名和密碼,例如:Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/mydatabase", "username", "password");
- 創建Statement對象:使用Connection對象的createStatement()方法創建一個Statement對象,用于執行SQL語句,例如:Statement stmt = conn.createStatement();
- 執行SQL語句:使用Statement對象的executeQuery()或executeUpdate()方法執行SQL語句,例如:ResultSet rs = stmt.executeQuery("SELECT * FROM mytable");
- 處理查詢結果:如果執行的是查詢語句,需要使用ResultSet對象來處理查詢結果,例如:while (rs.next()) { String name = rs.getString("name"); int age = rs.getInt("age"); }
- 關閉數據庫連接:在程序結束時,需要使用Connection對象的close()方法關閉數據庫連接,例如:conn.close();
怎么理解SpringIoc?
SpringIoc就是控制反轉,把創建對象的權力交給了框架,然后Spring通過反射就會創建一個對象。當我創建的一個類要用到這個對象,Spring就會把這個對象交給我。
如果讓你設計一個SpringIoc,你覺得會從哪些方面考慮這個設計?
- 多線程,這個對象是單例的還是每個線程都特有的。
- 可能會有循環依賴的問題,像對象A有B的引用,然后對象B有A的引用
- bean的生命周期也需要考慮。
補充:
- Bean的生命周期管理:需要設計Bean的創建、初始化、銷毀等生命周期管理機制,可以考慮使用工廠模式和單例模式來實現。
- 依賴注入:需要實現依賴注入的功能,包括屬性注入、構造函數注入、方法注入等,可以考慮使用反射機制和XML配置文件來實現。
- Bean的作用域:需要支持多種Bean作用域,比如單例、原型、會話、請求等,可以考慮使用Map來存儲不同作用域的Bean實例。
- AOP功能的支持:需要支持AOP功能,可以考慮使用動態代理機制和切面編程來實現。
- 異常處理:需要考慮異常處理機制,包括Bean創建異常、依賴注入異常等,可以考慮使用try-catch機制來處理異常。
- 配置文件加載:需要支持從不同的配置文件中加載Bean的相關信息,可以考慮使用XML、注解或者Java配置類來實現。
Spring給我們提供了很多擴展點,這些有了解嗎?
不太清楚擴展點指的什么
補充:
Spring框架提供了許多擴展點,使得開發者可以根據需求定制和擴展Spring的功能。以下是一些常用的擴展點:
- BeanFactoryPostProcessor:允許在Spring容器實例化bean之前修改bean的定義。常用于修改bean屬性或改變bean的作用域。
- BeanPostProcessor:可以在bean實例化、配置以及初始化之后對其進行額外處理。常用于代理bean、修改bean屬性等。
- PropertySource:用于定義不同的屬性源,如文件、數據庫等,以便在Spring應用中使用。
- ImportSelector和ImportBeanDefinitionRegistrar:用于根據條件動態注冊bean定義,實現配置類的模塊化。
- Spring MVC中的HandlerInterceptor:用于攔截處理請求,可以在請求處理前、處理中和處理后執行特定邏輯。
- Spring MVC中的ControllerAdvice:用于全局處理控制器的異常、數據綁定和數據校驗。
- Spring Boot的自動配置:通過創建自定義的自動配置類,可以實現對框架和第三方庫的自動配置。
- 自定義注解:創建自定義注解,用于實現特定功能或約定,如權限控制、日志記錄等。
servlet有寫過簡單的代碼嗎?
沒用框架之前,就用的servlet,用了框架就用的框架
大致了解SpringMVC的處理流程嗎?
首先通過一個dispatchservlet去轉接請求,到handlermapping去返回一個執行鏈,就比如攔截器到哪個controller,返回以后就到handler適配器獲取這個請求要求的controller,然后去controller這里返回一個數據或者頁面modelandview,然后給前端。
SpringAOP主要想解決什么問題
提供了一個擴展功能,可以一個類的某個方法進行加強,比如在之前加強,在之后加強,環繞加強。
補充:
Spring AOP主要解決的是橫切關注點的問題,即在一個系統中,可能存在多個模塊或組件都需要實現類似的功能,比如日志記錄、權限校驗、事務管理等等。如果每個模塊都去實現這些功能,就會導致代碼冗余,可維護性和可擴展性降低。而AOP則是基于動態代理的機制,在不修改原有代碼的情況下,通過在代碼執行前后插入增強代碼的方式,實現對橫切關注點的統一處理,從而提高代碼的復用性和可維護性。
SpringAOP的原理了解嗎
基于一個動態代理的設計模式,如果動態加強的類實現了某個接口,就會用JDK動態代理,如果是對于沒有實現接口的類,就會用cglib動態代理模板,去生成一個被代理對象的一個子類來作為代理對象。
補充:
Spring AOP的主要目的是將橫切關注點(如日志、安全和事務管理等)從業務邏輯中分離出來,從而提高代碼的模塊性和可維護性。
原理主要包括以下幾個方面:
- 代理模式:Spring AOP基于代理模式實現,主要有兩種代理方式,JDK動態代理和CGLIB代理。JDK動態代理要求目標類必須實現接口,而CGLIB代理則可以針對沒有實現接口的類進行代理。
- 切面(Aspect):切面是將橫切關注點模塊化的實現。切面通常包含通知(Advice)和切點(Pointcut)。通知是在特定的切點執行的動作,切點則用于定義通知應該在何處執行。
- 連接點(Joinpoint):連接點代表在應用程序中可以插入切面的點,如方法調用、異常處理等。
- 織入(Weaving):織入是將切面應用到目標對象的過程,從而創建代理對象。在Spring AOP中,織入過程發生在運行時。
通過以上原理,Spring AOP能夠在不修改原有業務代碼的情況下,將橫切關注點進行模塊化管理,提高代碼的可讀性和易維護性。
動態代理和靜態代理的區別
靜態代理是自己手寫一個代理類,但是動態代理不需要直接實現這個代理類的,相當于靜態代理在編譯的時候就已經變成了一個個二進制文件了,動態代理在運行的時候動態生成類的字節碼文件。
動態代理中如果有實現了接口,就會用JDK動態代理呢?
不太了解(后來查了應該是因為JDK動態代理會繼承Proxy類,但是java是單繼承)
代理模式和適配器模式有什么區別?
代理模式主要是去加強一個類的方法。適配器模式是接口轉換成一個想要的接口(這個問題被面試說回答的不好)
補充:
代理模式和適配器模式是兩種常用的設計模式,它們的區別主要體現在以下幾個方面:
- 作用不同:代理模式是為了控制對對象的訪問,而適配器模式是為了解決接口不匹配的問題。
- 解決問題的角度不同:代理模式是從外部控制訪問,保護目標對象,而適配器模式是從內部改變對象接口,讓其能夠適配客戶端的要求。
- 實現方式不同:代理模式通常使用面向對象的繼承或者組合方式實現,而適配器模式則通常使用對象組合方式實現。
- 適用場景不同:代理模式適用于需要對對象進行控制和保護的情況,例如遠程代理、虛擬代理等。適配器模式適用于需要將一個類的接口轉換成客戶端期望的另一個接口的情況,例如舊系統的升級改造、不兼容接口的統一等。
java 并發
java線程的生命周期有了解嗎?
new就是創建一個線程,變成ready的狀態,如果分配到時間片,就會是一個運行的狀態,等待其他線程做出一個動作就是waiting的狀態,如果是等待其他資源的釋放,就是block的狀態,最后是一個終止的狀態。
使用多線程要注意哪些問題?
避免死鎖,保證數據的可見性或者多個線程對這個數據的一致性。
保證數據的一致性有哪些方案呢?
比如有violate修飾一個變量,或者sychonized或者加鎖。
線程池有了解嗎?線程池大概的原理?
分為核心線程池,線程池的最大容量,還有等待任務的隊列,提交一個任務,如果核心線程沒有滿,就創建一個線程,如果滿了,就是會加入等待隊列,如果等待隊列滿了,就會增加線程,如果達到最大線程數量,如果都達到最大線程數量,就會按照一些丟棄的策略進行處理。
ArrayList和LinkedList有什么區別
ArrayList通過數組存儲數據,如果查詢的話是會快速定位到這個數據,但是新增和刪除數據會涉及數據的移動,像linkedList就是雙向鏈表,定位數據會一個個查,新增和刪除是一個o1。
補充:
ArrayList和LinkedList是Java中的兩種常用的List實現,它們的區別主要體現在底層數據結構、性能和使用場景上:
- 底層數據結構:ArrayList基于動態數組實現,LinkedList基于雙向鏈表實現。
- 插入和刪除操作性能:
- ArrayList:在插入和刪除元素時,需要移動元素以保持數組的連續性,所以在非尾部的插入和刪除操作性能較差,時間復雜度為O(n)。
- LinkedList:由于基于鏈表實現,插入和刪除元素只需修改指針,所以在任何位置的插入和刪除操作性能較好,時間復雜度為O(1)。
- 訪問和查找操作性能:
- ArrayList:由于基于數組實現,支持隨機訪問,訪問和查找元素的時間復雜度為O(1)。
- LinkedList:由于基于鏈表實現,需要順序遍歷鏈表,訪問和查找元素的時間復雜度為O(n)。
- 內存占用:
- ArrayList:內存占用相對較小,因為只需存儲元素本身。
- LinkedList:由于需要存儲額外的指針信息(前后節點指針),內存占用相對較大。
- 使用場景:
- ArrayList:更適合頻繁訪問和查找元素的場景,如查詢操作較多的情況。
- LinkedList:更適合頻繁插入和刪除元素的場景,如在列表中間進行大量的增刪操作。
ArrayList線程安全嗎?把ArrayList變成線程安全有哪些方法?
線程不安全,使用它的線程安全類Vector(被問還有呢?)
補充:
將ArrayList變成線程安全有幾種方法:
- 使用Collections.synchronizedList()方法將ArrayList轉換為線程安全的List。該方法會返回一個線程安全的List,使用該List時需要在訪問它的方法上添加synchronized關鍵字,以保證多線程訪問的安全性。
- 使用CopyOnWriteArrayList類來代替ArrayList。CopyOnWriteArrayList是一種線程安全的List實現,它通過在寫操作時復制整個數組來保證線程安全性,在讀操作時不需要加鎖,因此可以提高讀取效率。
- 使用Lock接口來實現同步。可以使用ReentrantLock類來實現對ArrayList的同步操作,該類提供了與synchronized類似的功能,但是具有更高的靈活性。比如可以使用tryLock()方法來嘗試獲取鎖,避免了線程的長時間等待。
- 使用讀寫鎖來實現同步。可以使用ReentrantReadWriteLock類來實現對ArrayList的讀寫操作的同步。該類提供了讀鎖和寫鎖兩種鎖,多個線程可以同時獲取讀鎖,但是只有一個線程可以獲取寫鎖,在寫操作時需要先獲取寫鎖,以保證線程安全。
其他
對面向對象的理解?
像面向過程就是把問題分解成一個一個函數,然后調用函數去解決問題。而面向對象就是把這個世界抽象成一個一個對象,然后賦予這些對象一個屬性,成員變量和方法,然后去調用對象的方法去解決問題,耦合性比較低。
面向過程的方法存在哪些問題?
耦合度比較高,分工不好分工。
補充:
- 可維護性較差:面向過程編程主要依賴于函數和過程,隨著代碼規模的增大,可能會導致代碼結構復雜,不易維護。
- 可復用性較低:面向過程編程難以實現模塊化,導致代碼難以復用,進一步增加開發時間和成本。
- 擴展性不足:面向過程編程在代碼邏輯發生變化時,往往需要對程序進行大量的修改,這樣的代碼擴展性不足。
- 抽象能力有限:面向過程編程主要關注過程和算法,而不是數據結構和對象,這使得它在表達現實世界的復雜問題時抽象能力有限。
- 封裝性差:面向過程編程沒有提供良好的封裝機制,程序中的數據和處理過程容易暴露,可能導致數據安全性和程序穩定性問題。
- 強耦合:面向過程編程的方法往往導致程序組件之間存在強耦合,當一個組件發生變化時,可能會影響其他組件的正常工作。
面向過程好處是什么?
解決問題思路比較簡單。
補充:
- 面向過程編程采用自頂向下的編程方式,將問題分解為一個個小的模塊,便于理解和編寫。
- 每個模塊相對獨立,出現問題時可以單獨調試,降低了調試難度。
- 面向過程編程適合解決簡單、邏輯性強的問題,對于初學者來說,學習成本較低。
面試總結
感覺:
- 面試官有引導,大多問的是八股,會的就回答的比較流暢,不熟悉的就磕磕巴巴
- 面試官給的反饋基礎還行,但是深度不夠,對剛剛設計模式的對比回答不滿意,和mybatis的原理回答也不太滿意
不足之處:
- 對框架還是不夠熟練,回答不夠全面,經常被面試官問還有呢,但就回答不上來了
- 網絡這方面看了就忘,面試官建議講項目從以下四點:項目的背景、核心解決思路、技術上的挑戰點、最后取得什么結果。