java面試準備之java基礎

一、equals 和hashCode方法的區別與聯繫

1、equals方法用於比較對象的內容是否相等(覆蓋以後)
2、hashcode方法只有在集合中用到
3、當覆蓋了equals方法時,比較對象是否相等將通過覆蓋後的equals方法進行比較(判斷對象的內容是否相等)。
4、將對象放入到集合中時,首先判斷要放入對象的hashcode值與集合中的任意一個元素的hashcode值是否相等,如果不相等直接將該對象放入集合中。如果hashcode值相等,然後再通過equals方法判斷要放入對象與集合中的任意一個對象是否相等,如果equals判斷不相等,直接將該元素放入到集合中,否則不放入。

二、StringBuilder 和StringBuffer

在java中如果有大量拼接字符串的需求的話,應該使用StringBuilder和StringBuffer類,它們可以避免不必要的String對象產生(String被final修飾不可變),以提高程序性能,他們兩者作用類似StringBuffer對方法加了同步鎖或者對調用的方法加了同步鎖,所以是線程安全的。StringBuilder並沒有對方法進行加同步鎖,所以是非線程安全的。StringBuilder與StringBuffer有公共父類AbstractStringBuilder(抽象類)

三、final的用法

1.用來修飾數據,包括成員變量和局部變量,該變量只能被賦值一次且它的值無法被改變。對於成員變量來講,我們必須在聲明時或者構造方法中對它賦值;
2.用來修飾方法參數,表示在變量的生存期中它的值不能被改變;
3.修飾方法,表示該方法無法被重寫;
4.修飾類,表示該類無法被繼承。

四、java中面向對象思想及特徵

面向對象這種程序設計模式它將現實世界中的一切事物都看作是對象,它強調從對象出發,以對象爲中心用人類的思維方式來認識和思考問題。每個對象都具有各自的狀態特徵(也可以稱爲屬性)及行爲特徵(方法),java就是通過對象之間行爲的交互來解決問題的。
封裝:(面向對象編程核心思想):封裝就是將對象的屬性和行爲特徵包裝到一個程序單元(即類)中,把實現細節隱藏起來,通過公用的方法來展現類對外提供的功能,提高了類的內聚性,降低了對象之間的耦合性。
**繼承:**對原有類的拓展,指從已有的類中派生新的類,新的類能夠吸收已有類的數據屬性和行爲,並能夠擴展新的功能。
多態:是在繼承的基礎上。是指允許不同類的對象對同一消息做出響應,根據對象創建實例的不同調用不同的方法,本質是編譯時和運行時才決定對象的行爲。例如,子類類型的對象可以賦值給父類類型的引用變量,但運行時仍表現子類的行爲特徵(在內存中運行的,new的實例)。也就是說,同一種類型的對象執行同一個方法時可以表現出不同的行爲特徵。
方法重載:是指同一個類中的多個方法具有相同的名字,但這些方法具有不同的參數列表,即參數的數量或參數類型不能完全相同
方法重寫:是存在子父類之間的,子類定義的方法與父類中的方法具有相同的方法名字,相同的參數表和相同的返回類型 ,子類函數的訪問修飾權限不能少於父類的。

五、反射和註解

Java 反射機制是在運行狀態中,對於任意一個類,都能夠獲得這個類的所有屬性和方法,對於任意一個對象都能夠調用它的任意一個屬性和方法。這種在運行時動態的獲取信息以及動態調用對象的方法的功能稱爲Java 的反射機制。
1.反射的用處
通過反射運行配置文件內容
通過反射越過泛型檢查
泛型用在編譯期,編譯過後泛型擦除(消失掉)。所以是可以通過反射越過泛型檢查的
有一個String泛型的集合,怎樣能向這個集合中添加一個Integer類型的值?
通過反射獲取並修改數組信息
2.註解
從 JDK5.0 開始,Java 增加了對元數據(MetaData)的支持,也就是Annotation(註釋)
• Annotation其實就是代碼裏的特殊標記,這些標記可以在編譯,類加載, 運行時被讀取,並執行相應的處理.通過使用Annotation,程序員可以在不改變原有邏輯的情況下,在源文件中嵌入一些補充信息.
• Annotation 可以像修飾符一樣被使用,可用於修飾包,類,構造器,方法,成員變量,參數,局部變量的聲明,這些信息被保存在Annotation的 “name=value”對中.
• Annotation能被用來爲程序元素(類,方法,成員變量等)設置元數據
@Documented –註解是否將包含在JavaDoc中 @Retention –什麼時候使用該註解 @Target –註解用於什麼地方 @Inherited –是否允許子類繼承該註解
自定義 Annotation 定義新的 Annotation類型使用@interface關鍵字

六、集合框架

List:有序存放、允許重複、可以存放不同類型的對象
Set:無序存放、不允許重複、可以存放不同類型的對象
Map:映射,俗稱鍵值對
TreeMap、hashMap、hashTable區別
 TreeMap是有序的,HashMap和HashTable是無序的。
 Hashtable的方法是同步的,HashMap的方法不是同步的。這是兩者最主要的區別。
 Hashtable是線程安全的,HashMap不是線程安全的。HashMap效率較高,Hashtable效率較低。
 Hashtable不允許null值,HashMap允許null值(key和value都允許)
 父類不同:Hashtable的父類是Dictionary,HashMap的父類是AbstractMap
 Hashtable中hash數組默認大小是11,增加的方式是 old*2+1。
 HashMap中hash數組的默認大小是16,而且一定是2的指數。

(一)ArrayList實現原理

ArrayList作爲List的典型實現,完全實現了List的全部接口功能,它是基於數組實現的List類,它封裝了一個Object[]類型的數組,長度可以動態的增長。如果在創建ArrayList時沒有指定Object[]數組的長度,它默認創建一個長度爲10的數組,當新添加的元素已經沒有位置存放的時候,ArrayList就會自動進行擴容,擴容的長度爲原來長度的1.5倍。它的線程是不安全的。

(二)LinkedList實現原理

 LinkedList是List接口的雙向鏈表非同步實現,並允許包括null在內的所有元素。
 底層的數據結構是基於雙向鏈表的,該數據結構我們稱爲節點
 雙向鏈表節點對應的類Entry的實例,Entry中包含成員變量:previous,next,element。其中,previous是該節點的上一個節點,next是該節點的下一個節點,element是該節點所包含的值。
 LinkedList是基於雙向循環鏈表實現的,除了可以當做鏈表來操作外,它還可以當做棧、隊列和雙端隊列來使用。
 LinkedList同樣是非線程安全的,只在單線程下適合使用。
 LinkedList實現了Serializable接口,因此它支持序列化,能夠通過序列化傳輸,實現了Cloneable接口,能被克隆。
 LinkedList是基於鏈表實現的,因此插入刪除效率高,查找效率低(雖然有一個加速動作)

(三)HashMap實現原理

1.HashMap的底層數據結構是什麼?
jdk1.2-1.7 數組+鏈表 jdk1.8 數組+鏈表+紅黑樹
2.HashMap中增刪改查操作的底部實現原理是什麼?
put數據
(1)第一步:調用put方法傳入鍵值對
(2)第二步:使用hash算法計算hash值
(3)第三步:根據hash值確定存放的位置,判斷是否和其他鍵值對位置發生了衝突
(4)第四步:若沒有發生衝突,直接存放在數組中即可
(5)第五步:若發生了衝突,還要判斷此時的數據結構是什麼?
(6)第六步:若此時的數據結構是紅黑樹,那就直接插入紅黑樹中
(7)第七步:若此時的數據結構是鏈表,判斷插入之後是否大於等於8
(8)第八步:插入之後大於8了,就要先調整爲紅黑樹,在插入
(9)第九步:插入之後不大於8,那麼就直接插入到鏈表尾部即可。
get數據
(1)先計算出key哈希值。
(2)通過哈希值定位到Key存在哪個數組下標。
(3)找到後看數組下標裏面有沒有節點。
(4)有節點的話區分節點數據是紅黑樹還是鏈表,然後分別使用對應數據結構的查找方法。
(5)根據查找的key和節點裏面存的Key 值判斷兩個key是不是equas() ,equas則返回對應的節點,否則繼續匹配下一個節點,直到匹配成功返回節點,或者沒有節點配後返回null;
3.HashMap是如何實現擴容的?
先計算 新的hash表容量和新的容量閥值,然後初始化一個新的hash表,將舊的鍵值對重新映射在新的hash表裏。如果在舊的hash表裏涉及到紅黑樹,那麼在映射到新的hash表中還涉及到紅黑樹的拆分負載因子=0.75越大則散列表的裝填程度越高,也就是能容納更多的元素,元素多了,鏈表大了,所以此時索引效率就會降低。反之,負載因子越小則鏈表中的數據量就越稀疏,此時會對空間造成爛費,但是此時索引效率高。
在外層遍歷node數組,對於每一個table[j],判斷該node擴容之後,是屬於低位部分(原數組),還是高位部分(擴容部分數組)。判斷的方式就是位與舊數組的長度,如果爲0則代表的是低位數組,因爲index的值小於舊數組長度,位與的結果就是0;相反,如果不爲零,則爲高位部分數組。低位數組,添加到以loHead爲頭的鏈表中,高位數組添加到以hiHead爲頭的數組中。鏈表遍歷結束,分別設置新哈希表的index位置和(index+舊錶長度)位置的值。非常的巧妙。
4.HashMap是如何解決hash衝突的?
拉鍊法,根據哈希值確定存儲的位置,如果沒有值直接存入,有值判斷值是否相等,相等新值替換舊值,不相等存入鏈表尾部
5.HashMap爲什麼是非線程安的?
源碼裏面方法全部都是非線程安全的,你根本找不到synchronized這樣的關鍵字。保證不了線程安全。於是出現了ConcurrentHashMap。

(四)HashTable實現原理

 Hashtable是基於哈希表的Map接口的同步實現,不允許使用null值和null鍵
 底層使用數組實現,數組中每一項是個單鏈表,即數組和鏈表的結合體
 Hashtable在底層將key-value當成一個整體進行處理,這個整體就是一個Entry對象。Hashtable底層採用一個Entry[]數組來保存所有的key-value對,當需要存儲一個Entry對象時,會根據key的hash算法來決定其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當需要取出一個Entry時,也會根據key的hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Entry。
 synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔

(五)ConcurrentHashMap實現原理

 ConcurrentHashMap允許多個修改操作併發進行,其關鍵在於使用了鎖分離技術。
 它使用了多個鎖來控制對hash表的不同段進行的修改,每個段其實就是一個小的hashtable,它們有自己的鎖。只要多個併發發生在不同的段上,它們就可以併發進行。
 ConcurrentHashMap在底層將key-value當成一個整體進行處理,這個整體就是一個Entry對象。Hashtable底層採用一個Entry[]數組來保存所有的key-value對,當需要存儲一個Entry對象時,會根據key的hash算法來決定其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當需要取出一個Entry時,也會根據key的hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Entry。
 與HashMap不同的是,ConcurrentHashMap使用多個子Hash表,也就是段(Segment)
 ConcurrentHashMap完全允許多個讀操作併發進行,讀操作並不需要加鎖。如果使用傳統的技術,如HashMap中的實現,如果允許可以在hash鏈的中間添加或刪除元素,讀操作不加鎖將得到不一致的數據。ConcurrentHashMap實現技術是保證HashEntry幾乎是不可變的。
CAS算法:
 CAS是英文單詞Compare And Swap的縮寫,翻譯過來就是比較並替換。
 CAS機制當中使用了3個基本操作數:內存地址V,舊的預期值A,要修改的新值B。
 更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,纔會將內存地址V對應的值修改爲B
從思想上來說,Synchronized屬於悲觀鎖,悲觀地認爲程序中的併發情況嚴重,所以嚴防死守。CAS屬於樂觀鎖,樂觀地認爲程序中的併發情況不那麼嚴重,所以讓線程不斷去嘗試更新。
ConcurrentHashMap是一個線程安全的Map集合,可以應對高併發的場景,保證線程安全。相比較HashTable,它的鎖粒度更加的細化,因爲HashTable的方法都是用Synchronized修飾的,效率灰常的底下。
1.8之前ConcurrentHashMap使用鎖分段技術,將數據分成一段段的存儲,每一個數據段配置一把鎖,相互之間不影響,而1.8之後摒棄了Segment(鎖段)的概念,啓用了全新的實現,也就是利用CAS+Synchronized來保證併發更新的安全,底層採用的依然是數組+鏈表+紅黑樹。

(六)HashSet實現原理

 HashSet由哈希表(實際上是一個HashMap實例)支持,不保證set的迭代順序,並允許使用null元素。
 基於HashMap實現,API也是對HashMap的行爲進行了封裝
HashSet是Set接口的典型實現,HashSet按照Hash算法來存儲集合中的元素。存在以下特點:
• 不能保證元素的順序,元素是無序的
• HashSet不是同步的,需要外部保持線程之間的同步問題
• 集合元素值允許爲null

(七)LinkedHashMap實現原理

 LinkedHashMap繼承於HashMap,底層使用哈希表和雙向鏈表來保存所有元素,並且它是非同步,允許使用null值和null鍵。
 基本操作與父類HashMap相似,通過重寫HashMap相關方法,重新定義了數組中保存的元素Entry,來實現自己的鏈接列表特性。該Entry除了保存當前對象的引用外,還保存了其上一個元素before和下一個元素after的引用,從而構成了雙向鏈接列表。

(八)LinkedHashSet

LinkedHashSet是具有可預知迭代順序的Set接口的哈希表和鏈接列表實現。此實現與HashSet的不同之處在於,後者維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可爲插入順序或是訪問順序。
注意,此實現不是同步的。如果多個線程同時訪問鏈接的哈希Set,而其中至少一個線程修改了該Set,則它必須保持外部同步。
對於LinkedHashSet而言,它繼承與HashSet、又基於LinkedHashMap來實現的。
LinkedHashSet底層使用LinkedHashMap來保存所有元素,它繼承與HashSet,其所有的方法操作上又與HashSet相同,因此LinkedHashSet 的實現上非常簡單,只提供了四個構造方法,並通過傳遞一個標識參數,調用父類的構造器,底層構造一個LinkedHashMap來實現,在相關操作上與父類HashSet的操作相同,直接調用父類HashSet的方法即可。

(九)TreeMap實現原理

TreeMap實現了SotredMap接口,它是有序的集合。而且是一個紅黑樹結構,每個key-value都作爲一個紅黑樹的節點。如果在調用TreeMap的構造函數時沒有指定比較器,則根據key執行自然排序。這點會在接下來的代碼中做說明,如果指定了比較器則按照比較器來進行排序。

七、快速失敗和安全失敗

1、快速失敗(fail—fast)
在用迭代器遍歷一個集合對象時,如果遍歷過程中對集合對象的內容進行了修改(增加、刪除、修改),則會拋出Concurrent Modification Exception。
原理:迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個 modCount 變量。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素之前,都會檢測modCount變量是否爲expectedmodCount值,是的話就返回遍歷;否則拋出異常,終止遍歷。
注意:這裏異常的拋出條件是檢測到 modCount!=expectedmodCount 這個條件。如果集合發生變化時修改modCount值剛好又設置爲了expectedmodCount值,則異常不會拋出。因此,不能依賴於這個異常是否拋出而進行併發操作的編程,這個異常只建議用於檢測併發修改的bug。
場景:java.util包下的集合類都是快速失敗的,不能在多線程下發生併發修改(迭代過程中被修改)。
2、安全失敗(fail—safe)
採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先複製原有集合內容,在拷貝的集合上進行遍歷。
原理:由於迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,所以不會觸發Concurrent Modification Exception。
缺點:基於拷貝內容的優點是避免了Concurrent Modification Exception,但同樣地,迭代器並不能訪問到修改後的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。
場景:java.util.concurrent包下的容器都是安全失敗,可以在多線程下併發使用,併發修改。

八、抽象類和接口

抽象類:包含抽象方法的類或者被abstract關鍵字修飾的類
包含抽象方法的類稱爲抽象類,但並不意味着抽象類中只能有抽象方法,它和普通類一樣,同樣可以擁有成員變量和普通的成員方法。注意,抽象類和普通類的主要有三點區別:
  1)抽象方法必須爲public或者protected(因爲如果爲private,則不能被子類繼承,子類便無法實現該方法),缺省情況下默認爲public。
  2)抽象類不能用來創建對象;
  3)如果一個類繼承於一個抽象類,則子類必須實現父類的抽象方法。如果子類沒有實現父類的抽象方法,則必須將子類也定義爲爲abstract類。
接口:英文稱作interface,在軟件工程中,接口泛指供別人調用的方法或者函數
抽象類和接口的區別
1)抽象類可以提供成員方法的實現細節,而接口中只能存在public abstract 方法;
2)抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是public static final類型的;
3)接口中不能含有靜態代碼塊以及靜態方法,而抽象類可以有靜態代碼塊和靜態方法;
4)一個類只能繼承一個抽象類,而一個類卻可以實現多個接口。
接口中可以含有 變量和方法。但是要注意,接口中的變量會被隱式地指定爲public static final變量(並且只能是public static final變量,用private修飾會報編譯錯誤),而方法會被隱式地指定爲public abstract方法且只能是public abstract方法(用其他關鍵字,比如private、protected、static、 final等修飾會報編譯錯誤),並且接口中所有的方法不能有具體的實現,也就是說,接口中的方法必須都是抽象方法。從這裏可以隱約看出接口和抽象類的區別,接口是一種極度抽象的類型,它比抽象類更加“抽象”,並且一般情況下不在接口中定義變量。

九、BIO、NIO、AIO

IO的方式通常分爲幾種,同步阻塞的BIO、同步非阻塞的NIO、異步非阻塞的AIO。
BIO:同步並阻塞,服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務器端就需要啓動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
NIO:同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O請求時才啓動一個線程進行處理。NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。
AIO:異步非阻塞,服務器實現模式爲一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理.AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與併發操作,編程比較複雜,JDK7開始支持。
BIO與NIO不同
1.BIO與NIO一個比較重要的不同,是我們使用BIO的時候往往會引入多線程,每個連接一個單獨的線程;而NIO則是使用單線程或者只使用少量的多線程,每個連接共用一個線程。
2.IO是面向流的,NIO是面向緩衝區的。
3.IO的各種流是阻塞的,NIO是非阻塞模式。
NIO的最重要的地方是當一個連接創建後,不需要對應一個線程,這個連接會被註冊到多路複用器上面,所以所有的連接只需要一個線程就可以搞定,當這個線程中的多路複用器進行輪詢的時候,發現連接上有請求的話,纔開啓一個線程進行處理,也就是一個請求一個線程模式。
AIO與NIO不同,當進行讀寫操作時,只須直接調用API的read或write方法即可。這兩種方法均爲異步的,對於讀操作而言,當有流可讀取時,操作系統會將可讀的流傳入read方法的緩衝區,並通知應用程序;對於寫操作而言,當操作系統將write方法傳遞的流寫入完畢時,操作系統主動通知應用程序。 即可以理解爲,read/write方法都是異步的,完成後會主動調用回調函數。 在JDK1.7中,這部分內容被稱作NIO.2
BIO是一個連接一個線程。
NIO是一個請求一個線程。
AIO是一個有效請求一個線程。

十、Java中有幾種類型的流?

答:字節流和字符流。字節流繼承於InputStream、OutputStream,字符流繼承於Reader、Writer。在java.io 包中還有許多其他的流,主要是爲了提高性能和使用方便。關於Java的I/O需要注意的有兩點:一是兩種對稱性(輸入和輸出的對稱性,字節和字符的對稱性);二是兩種設計模式(適配器模式和裝潢模式)。另外Java中的流不同於C#的是它只有一個維度一個方向。
編程實現文件拷貝。(這個題目在筆試的時候經常出現,下面的代碼給出了兩種實現方案)

public static void fileCopy(String source, String target) throws IOException {
        try (InputStream in = new FileInputStream(source)) {
            try (OutputStream out = new FileOutputStream(target)) {
                byte[] buffer = new byte[4096];
                int bytesToRead;
                while((bytesToRead = in.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesToRead);
                }
            }
        }
    }
    public static void fileCopyNIO(String source, String target) throws IOException {
        try (FileInputStream in = new FileInputStream(source)) {
            try (FileOutputStream out = new FileOutputStream(target)) {
                FileChannel inChannel = in.getChannel();
                FileChannel outChannel = out.getChannel();
                ByteBuffer buffer = ByteBuffer.allocate(4096);
                while(inChannel.read(buffer) != -1) {
                    buffer.flip();
                    outChannel.write(buffer);
                    buffer.clear();
                }
            }
        }
}

十一、異常和錯誤

Error(錯誤)表示系統級的錯誤和程序不必處理的異常,一般是指與虛擬機相關的問題,是java運行環境中的內部錯誤或者硬件問題。比如:內存資源不足等。對於這種錯誤,程序基本無能爲力,除了退出運行外別無選擇,它是由Java虛擬機拋出的。
Exception(違例)表示需要捕捉或者需要程序進行處理的異常,它處理的是因爲程序設計的瑕疵而引起的問題或者在外的輸入等引起的一般性問題,是程序必須處理的。
Exception又分爲運行時異常,受檢查異常。
運行時異常,表示無法讓程序恢復的異常,導致的原因通常是因爲執行了錯誤的操作,建議終止程序,因此,編譯器不檢查這些異常。
受檢查異常,是表示程序可以處理的異常,也即表示程序可以修復(由程序自己接受異常並且做出處理), 所以稱之爲受檢查異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章