集合專題
● 說一下你了解的Java集合?
● HashMap與Hashtable的區別?
● 哈希碰撞/哈希沖突你有了解嗎,它是什么,怎么解決的?
● ArrayList和LinkedList的不同?
● ArrayList和Vector的區別?
● HashMap的數據存儲和實現原理你了解多少?
● HashMap的長度為什么是2的冪次方?
● HashSet和HashMap的區別與聯系?
● Hashtable和ConcurrentHashMap的區別?
● ConcurrentHashMap線程安全的具體實現原理?
● 怎么獲取一個線程安全的ArrayList?
● TreeMap/TreeSet底層是什么數據結構,怎么實現的自動排序?
● HashMap集合的put和get方法的實現原理?
● Comparable和Comparator的區別?
● for循環和foreach遍歷集合哪個更快?
● 如何快速的遍歷map集合,哪種方式最快?
● HashSet、TreeSet、LinkedHashSet區別?
● 在什么場景下要重寫equals()和hashCode()方法?
● HashMap和TreeMap有什么不同?
● Collections和Collection有哪些區別?
● 如何對一組對象進行排序?
● Collection接口的remove()方法和Iterator接口的remove()方法區別?
● Java集合中List、Set、Map的區別?
● Iterator和ListIterator的區別是什么?
● 快速失敗(fail-fast)和安全失敗(fail-safe)的區別是什么?
● 什么是Java優先級隊列(Priority Queue)?
PriorityQueue是一個基于優先級堆的無界隊列,它的元素是按照自然順序(natural order)排序的。在創建的時候,我們可以給它提供一個負責給元素排序的比較器。PriorityQueue不允許null值,因為他們沒有自然順序,或者說他們沒有任何的相關聯的比較器。最后,PriorityQueue不是線程安全的,入隊和出隊的時間復雜度是O(log(n))。
● Enumeration接口和Iterator接口的區別有哪些?
Enumeration速度是Iterator的2倍,同時占用更少的內存。但是,Iterator遠遠比Enumeration安全,因為其他線程不能夠修改正在被Iterator遍歷的集合里面的對象。同時,Iterator允許調用者刪除底層集合里面的元素,這對Enumeration來說是不可能的。
● 在迭代一個集合的時候,如何避免ConcurrentModificationException?
在遍歷一個集合的時候,我們可以使用并發集合類來避免ConcurrentModificationException,比如使用CopyOnWriteArrayList,而不是ArrayList。
● 哪些集合類是線程安全的?
Vector、Hashtable、Properties和Stack是同步類,所以它們是線程安全的,可以在多線程環境下使用。Java1.5并發包下包括一些集合類,允許迭代時修改,因為它們都工作在集的克隆上。所以它們在多線程環境中是安全的。
● 并發集合類是什么?
Java1.5并發包(java.util.concurrent)包含線程安全集合類,允許在迭代時修改集合。迭代器被設計為fail-fast的,會 拋出ConcurrentModificationException。一部分類為:CopyOnWriteArrayList、 ConcurrentHashMap、CopyOnWriteArraySet。
● BlockingQueue是什么?
java.util.concurrent.BlockingQueue是一個隊列,在進行檢索或移除一個元素的時候,它會等待隊列變為非空;當在添加一個元素時,它會等待隊列中的可用空間。BlockingQueue接口是Java集合框架的一部分,主要用于實現生產者-消費者模式。我們不需要擔心等待生產者有可用的空間,或消費者有可用的對象,因為它都在BlockingQueue的實現類中被處理了。Java提供了集中BlockingQueue的實現,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。
● 大寫的O是什么,舉幾個例子?
大寫的O描述的是數據結構中一個算法的性能。Collection類就是實際的數據結構,我們通常基于時間、內存和性能,使用大寫的O來選擇集合實現。比如:例子1:ArrayList的get(index i)是一個常量時間操作,它不依賴list中元素的數量,所以它的性能是O(1)。例子2:一個對于數組或列表的線性搜索的性能是O(n),因為我們需要 遍歷所有的元素來查找需要的元素。
● 你用過存儲過程嗎?
之前用過存儲過程,近期的項目沒有使用存儲過程,由于存儲過程具有數據庫特色,mysql有自己一套語法機制,oracle數據庫則有另一套語法機制,雖然使用存儲過程會提高執行速度,但使用了存儲過程后,項目的數據庫很難平滑的移植,例如項目A在開發的時候使用了mysql存儲過程,假設將來將A項目的數據庫更改為B項目則是很困難的,故我們開發的時候為了保證數據庫的可移植性,就沒有使用存儲過程。
注意:游標、觸發器、存儲過程,都是存儲過程相關的。
● 千萬級數據,你如何提高查詢效率?
● 你對SQL優化有了解嗎,可以描述一些你用過的優化策略嗎?
● 索引的實現原理?
● 索引的分類?
● 索引在什么情況下失效?
● 內連接和外連接有什么區別?
● 數據庫設計范式?
第一范式:每個表都應該有主鍵,并且每個字段要求原子性不可再分。
第二范式:建立在第一范式基礎之上,所有非主鍵字段必須完全依賴主鍵,不能產生部分依賴。
第三范式:建立在第二范式基礎之上,所有非主鍵字段必須直接依賴主鍵,不能產生傳遞依賴。
設計范式的最終目的是:減少數據的冗余。但在實際的開發中,我們以滿足客戶的需求為目的,有的時候也會拿冗余來換取速度。(建議把這句話說上,體現工作經驗)
● MySQL默認的事務隔離級別?
可重復讀(Repeatable Read)
● 事務的隔離級別有哪些?
讀未提交(Read UnCommitted)、讀提交(Read Committed)、可重復讀(Repeatable Read)、序列化(Serializable)
● 在什么情況下使用having過濾?
從效率方面考慮,建議優先使用where進行過濾,如果專門是對分組之后的數據進行過濾,才會使用having。
● 事務的特性有哪些?
原則性(A)、一致性(C)、隔離性(I)、持久性(D)。
原子性表示事務是最小的工作單元不可再分。
一致性表示事務必須同時成功或者同時失敗。
隔離性表示事務A和事務B之間具有隔離。
持久性是事務最終結束的保障。
JVM專題
● Java GC的工作原理你有了解嗎?
● System.gc()和Runtime.gc()會做什么事情?
這兩個方法用來提示JVM要進行垃圾回收。但是,立即開始還是延遲進行垃圾回收是取決于JVM的。
● 如果對象的引用被置為null,垃圾收集器是否會立即釋放對象占用的內存?
不會,在下一個垃圾回收周期中,這個對象將是可被回收的。
● 你聽說過哪些GC算法?
● 你知道哪些垃圾回收機制?
● 你對JVM調優有了解嗎,可以說一下嗎?
● JVM的內存結構你有了解嗎(運行時數據區)?
● Java的堆結構是什么樣子的?
JVM的堆是運行時數據區,所有類的實例和數組都是在堆上分配內存。它在JVM啟動的時候被創建。對象所占的堆內存是由自動內存管理系統也就是垃圾收集器回收。堆內存是由存活和死亡的對象組成的。存活的對象是應用可以訪問的,不會被垃圾回收。死亡的對象是應用不可訪問尚且還沒有被垃圾收集器回收掉的對象。一直到垃圾收集器把這些對象回收掉之前,他們會一直占據堆內存空間。
● 串行(serial)收集器和吞吐量(throughput)收集器的區別是什么?
吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等規模和大規模數據的應用程序。而串行收集器對大多數的小應用(在現代處理器上需要大概100M左右的內存)就足夠了。
● JVM的永久代中會發生垃圾回收么?
垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是為什么正確的永久代大小對避免Full GC是非常重要的原因。
● 什么是分布式垃圾回收(DGC)?它是如何工作的?
DGC叫做分布式垃圾回收。RMI使用DGC來做自動垃圾回收。因為RMI包含了跨虛擬機的遠程對象的引用,垃圾回收是很困難的。DGC使用引用計數算法來給遠程對象提供自動內存管理。
● 你在實際項目中使用過哪些設計模式?
在實際的開發中也用過很多設計模式,例如GoF的設計模式,JavaEE的設計模式,例如在項目中寫過BaseController,所有的Controller都繼承這個Controller,這個BaseController當中定義核心算法骨架,具體的實現延遲到子類中完成,這個就符合模板方法設計模式。
另外還使用了JavaEE設計模式DAO,持久層統一使用該模式。還有在分頁查詢的時候封裝了PaginationVO,這里使用了JavaEE的VO模式。
在實際的項目中也使用了過濾器、攔截器等,這些都是符合GoF責任鏈設計模式的。還有使用Spring AOP控制事務,AOP底層使用了動態代理模式。
在P2P項目中,生成合同文件的時候,我們使用了兩個設計模式,一個是門面模式,一個是策略模式。門面模式體現在我們提供了生成合同文件的統一接口,策略模式體現在合同文件可以最終生成到PC端,也可以生成到H5端等。
● 你對設計模式有了解嗎,什么是設計模式,常見設計模式你知道哪些?
設計模式就是可以重復利用的解決方案。常見的設計模式有:單例模式、裝飾器模式、適配器模式、責任鏈模式、代理模式、策略模式、觀察者模式等。
單例模式:解決對象創建問題,屬于創建型設計模式,保證創建的實例只有1個,用于節省內存的開銷,符合單例模式的對象不會被垃圾回收器回收,所以在實際開發中通常使用單例模式來實現緩存,因為緩存需要避免被GC回收。
裝飾器模式:IO流當中使用了大量的裝飾器模式,裝飾器模式屬于結構型設計模式,例如關閉流的時候只需要關閉最外層流即可。
適配器模式:Servlet規范中的GenericServlet使用了適配器模式,適配器模式屬于結構型設計模式,其中GenericServlet屬于適配器,所有Servlet類繼承GenericServlet,而不需要直接實現Servlet接口,這樣代碼會更加優雅(該詞匯出自閻宏的《Java與模式》一書)。另外過濾器Filter、監聽器Listener都符合適配器模式。
責任鏈設計模式:Filter過濾器符合責任鏈設計模式,包括SpringMVC中的攔截器也是符合該設計模式的,責任鏈設計模式屬于行為型設計模式,再不改變java源代碼的基礎之上,可以實現方法的動態調用組合。
代理模式:Spring AOP就使用了動態代理機制。代理模式屬于結構型設計模式。
策略模式:比較器Comparator就體現了策略模式,面向接口編程,更換比較器即可更換比較規則,策略模式屬于行為型設計模式。
觀察者模式:Servlet規范中的Listener監聽器就實現了觀察者模式,另外MVC架構模式也屬于符合觀察者設計模式,觀察者設計模式屬于行為型設計模式。
● 你知道哪些開發原則?
據我了解,軟件的開發原則有六大原則。
開閉原則:開閉原則是面向對象的可復用設計的第一塊基石,它是最重要的面向對象設計原則。開閉原則(Open-Closed Principle, OCP):一個軟件實體應當對擴展開放,對修改關閉。即軟件實體應盡量在不修改原有代碼的情況下進行擴展。
單一職責原則:單一職責原則是最簡單的面向對象設計原則,它用于控制類的粒度大小。單一職責原則定義如下:單一職責原則(Single Responsibility Principle, SRP):一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因。單一職責原則是實現高內聚、低耦合的指導方針,它是最簡單但又最難運用的原則,需要設計人員發現類的不同職責并將其分離,而發現類的多重職責需要設計人員具有較強的分析設計能力和相關實踐經驗。
接口隔離原則:接口隔離原則(Interface Segregation Principle, ISP):使用多個專門的接口,而不使用單一的總接口,即客戶端不應該依賴那些它不需要的接口。
里氏代換原則:里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的對象。里氏代換原則告訴我們,在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類對象的話,那么它不一定能夠使用基類對象。例如:我喜歡動物,那我一定喜歡狗,因為狗是動物的子類;但是我喜歡狗,不能據此斷定我喜歡動物,因為我并不喜歡老鼠,雖然它也是動物。
迪米特法則:一個軟件實體應當盡可能少地與其他實體發生相互作用。迪米特法則還有幾種定義形式,包括:不要和“陌生人”說話、只與你的直接朋友通信等。
依賴倒轉原則:依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不應該依賴于細節,細節應當依賴于抽象。換言之,要針對接口編程,而不是針對實現編程。依賴倒轉原則要求我們在程序代碼中傳遞參數時或在關聯關系中,盡量引用層次高的抽象層類,即使用接口和抽象類進行變量類型聲明、參數類型聲明、方法返回類型聲明,以及數據類型的轉換等,而不要用具體類來做這些事情。為了確保該原則的應用,一個具體類應當只實現接口或抽象類中聲明過的方法,而不要給出多余的方法,否則將無法調用到在子類中增加的新方法。
● GoF設計模式分類?
創建型、結構型、行為型。
創建型的有:單例模式、工廠模式等。
結構型的有:適配器、裝飾器、代理模式等。
行為型的有:策略模式、觀察者模式、責任鏈模式等。
● JavaEE的設計模式知道那些?
DAO、POJO、DTO、VO、BO等。
● 怎么寫一個線程安全的單例模式?
//餓漢(絕對線程安全,但是對象在未使用時初始化,占用內存)
public final class EagerSingleton {
private static EagerSingleton singObj = new EagerSingleton();
private EagerSingleton(){
}
public static EagerSingleton getSingleInstance(){
return singObj;
}
}
//懶漢(多線程并發時,存在線程安全問題)
public final class LazySingleton {
private static LazySingleton singObj = null;
private LazySingleton(){
}
public static LazySingleton getSingleInstance(){
if(null == singObj ) singObj = new LazySingleton();
return singObj;
}
}
//懶漢加synchronized(線程安全的,但是同步的是整個方法,并發度差)
public final class ThreadSafeSingleton {
private static ThreadSafeSingleton singObj = null;
private ThreadSafeSingleton(){
}
public static Synchronized ThreadSafeSingleton getSingleInstance(){
if(null == singObj ) singObj = new ThreadSafeSingleton();
return singObj;
}
}
//雙重檢查鎖(如果發生指令重排序,則會出現線程不安全)
public final class DoubleCheckedSingleton {
private static DoubleCheckedSingletonsingObj = null;
private DoubleCheckedSingleton(){
}
public static DoubleCheckedSingleton getSingleInstance(){
if(null == singObj ) {
Synchronized(DoubleCheckedSingleton.class){
if(null == singObj) // (如果發生指令重排序,則會出現線程不安全)
singObj = new DoubleCheckedSingleton();
}
}
return singObj;
}
}
//雙重檢查鎖(加volatile關鍵字)
public final class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingletonsingObj = null;
private DoubleCheckedSingleton(){
}
public static DoubleCheckedSingleton getSingleInstance(){
if(null == singObj ) {
Synchronized(DoubleCheckedSingleton.class){
if(null == singObj)
singObj = new DoubleCheckedSingleton();
}
}
return singObj;
}
}
// 靜態內部類方式
public class SingletonPattern {
private SingletonPattern() {
}
private static class SingletonPatternHolder {
private static final SingletonPattern singletonPattern = new SingletonPattern();
}
public static SingletonPattern getInstance() {
return SingletonPatternHolder.singletonPattern;
}
}
● 單例模式的優缺點及使用場景?
(1)優點:
在單例模式中,活動的單例只有一個實例,對單例類的所有實例化得到的都是相同的一個實例。這樣就防止其它對象對自己的實例化,確保所有的對象都訪問一個實例。
提供了對唯一實例的受控訪問。
由于在系統內存中只存在一個對象,因此可以節約系統資源,當需要頻繁創建和銷毀的對象時單例模式無疑可以提高系統的性能。
避免對共享資源的多重占用。
(2)缺點:
不適用于變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態。
由于單利模式中沒有抽象層,因此單例類的擴展有很大的困難。
單例類的職責過重,在一定程度上違背了“單一職責原則”。
濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認為是垃圾而被回收,這將導致對象狀態的丟失。
(3)使用注意事項:
使用時不能用反射模式創建單例,否則會實例化一個新的對象。
使用懶單例模式時注意線程安全問題。
餓單例模式和懶單例模式構造方法都是私有的,因而是不能被繼承的,有些單例模式可以被繼承(如登記式模式)。
(4)經典使用場景:
網站的計數器,一般也是采用單例模式實現,否則難以同步。
應用程序的日志應用,一般都何用單例模式實現,這一般是由于共享的日志文件一直處于打開狀態,因為只能有一個實例去操作,否則內容不好追加。
Web應用的配置對象的讀取,一般也應用單例模式,這個是由于配置文件是共享的資源。
數據庫連接池的設計一般也是采用單例模式,因為數據庫連接是一種數據庫資源。數據庫軟件系統中使用數據庫連接池,主要是節省打開或者關閉數據庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因為何用單例模式來維護,就可以大大降低這種損耗。
多線程的線程池的設計一般也是采用單例模式,這是由于線程池要方便對池中的線程進行控制。
● 什么是享元模式,哪里用了?
享元模式通過共享對象來避免創建太多的對象。為了使用享元模式,你需要確保你的對象是不可變的,這樣你才能安全的共享。JDK 中 String 池、Integer 池以及 Long 池都是很好的使用了享元模式的例子。
● 描述一下接口和抽象類你是如何選用的?
接口和抽象類其實表示事物與事物之間的聯系的一種關系的體現。接口更多的體現的是like A的關系,而抽象類更多的是is A的關系。如果這兩個類他們之間確實無形中體現出is A的關系,比如貓和狗都是動物的一種,則可以寫抽象類。而如果這兩個類它們之間的行為很像,則它們體現出了一種Like A的關系,如媒婆代理別人去相親,那么本身就體現了一種方法,則體現出了接口的關系。也就是說接口更偏向于描述行為。