# 相關概念 ## 面向對象的三個特征 封裝,繼承,多态.這個應該是人人皆知.有時候也會加上抽象. ## 多态的好處 允許不同類對象對同一消息做出響應,即同一消息可以根據發送對象的不同而采用多種不同的行為方式(發送消息就是函數調用).主要有以下優點: 1. 可替換性:多态對已存在代碼具有可替換性. 2. 可擴充性:增加新的子類不影響已經存在的類結構. 3. 接口性:多态是超類通過方法簽名,向子類提供一個公共接口,由子類來完善或者重寫它來實作的. 4. 靈活性: 5. 簡化性: ### 代碼中如何實作多态 實作多态主要有以下三種方式: 1. 接口實作 2. 繼承父類重寫方法 3. 同一類中進行方法重載 ### 虛拟機是如何實作多态的 動态綁定技術(dynamic binding),執行期間判斷所引用對象的實際類型,根據實際類型調用對應的方法. ## 接口的意義 接口的意義用三個詞就可以概括:規範,擴充,回調. ## 抽象類的意義 抽象類的意義可以用三句話來概括: 1. 為其他子類提供一個公共的類型 2. 封裝子類中重複定義的内容 3. 定義抽象方法,子類雖然有不同的實作,但是定義時一緻的 ## 接口和抽象類的差別 |比較|抽象類|接口| |----|------|----| |預設方法|抽象類可以有預設的方法實作|,java 8之前,接口中不存在方法的實作.| |實作方式|子類使用extends關鍵字來繼承抽象類.如果子類不是抽象類,子類需要提供抽象類中所聲明方法的實作.|子類使用implements來實作接口,需要提供接口中所有聲明的實作. |構造器|抽象類中可以有構造器,|接口中不能| |和正常類差別|抽象類不能被執行個體化|接口則是完全不同的類型 |通路修飾符|抽象方法可以有public,protected和default等修飾|接口預設是public,不能使用其他修飾符 |多繼承|一個子類隻能存在一個父類|一個子類可以存在多個接口 |添加新方法|想抽象類中添加新方法,可以提供預設的實作,是以可以不修改子類現有的代碼|如果往接口中添加新方法,則子類中需要實作該方法.| ## 父類的靜态方法能否被子類重寫 不能.重寫隻适用于執行個體方法,不能用于靜态方法,而子類當中含有和父類相同簽名的靜态方法,我們一般稱之為隐藏. ## 什麼是不可變對象 不可變對象指對象一旦被建立,狀态就不能再改變。任何修改都會建立一個新的對象,如 String、Integer及其它包裝類。 ## 靜态變量和執行個體變量的差別? 靜态變量存儲在方法區,屬于類所有.執行個體變量存儲在堆當中,其引用存在目前線程棧. ## 能否建立一個包含可變對象的不可變對象? 當然可以建立一個包含可變對象的不可變對象的,你隻需要謹慎一點,不要共享可變對象的引用就可以了,如果需要變化時,就傳回原對象的一個拷貝。最常見的例子就是對象中包含一個日期對象的引用. ## java 建立對象的幾種方式 1. 采用new 2. 通過反射 3. 采用clone 4. 通過序列化機制 前2者都需要顯式地調用構造方法. 造成耦合性最高的恰好是第一種,是以你發現無論什麼架構,隻要涉及到解耦必先減少new的使用. ## switch中能否使用string做參數 在idk 1.7之前,switch隻能支援byte,short,char,int或者其對應的封裝類以及Enum類型。從idk 1.7之後switch開始支援String. ## switch能否作用在byte,long上? 可以用在byte上,但是不能用在long上. ## String s1="ab",String s2="a"+"b",String s3="a",String s4="b",s5=s3+s4請問s5==s2傳回什麼? 傳回false.在編譯過程中,編譯器會将s2直接優化為"ab",會将其放置在常量池當中,s5則是被建立在堆區,相當于s5=new String("ab"); ## 你對String對象的intern()熟悉麼? intern()方法會首先從常量池中查找是否存在該常量值,如果常量池中不存在則現在常量池中建立,如果已經存在則直接傳回. 比如 String s1="aa"; String s2=s1.intern(); System.out.print(s1==s2);//傳回false ## Object中有哪些公共方法? 1. `equals()` 2. `clone()` 3. `getClass()` 4. `notify(),notifyAll(),wait()` 5. `toString` ## java當中的四種引用 強引用,軟引用,弱引用,虛引用.不同的引用類型主要展現在GC上: 1. 強引用:如果一個對象具有強引用,它就不會被垃圾回收器回收。即使目前記憶體空間不足,JVM也不會回收它,而是抛出 OutOfMemoryError 錯誤,使程式異常終止。如果想中斷強引用和某個對象之間的關聯,可以顯式地将引用指派為null,這樣一來的話,JVM在合适的時間就會回收該對象 2. 軟引用:在使用軟引用時,如果記憶體的空間足夠,軟引用就能繼續被使用,而不會被垃圾回收器回收,隻有在記憶體不足時,軟引用才會被垃圾回收器回收。 3. 弱引用:具有弱引用的對象擁有的生命周期更短暫。因為當 JVM 進行垃圾回收,一旦發現弱引用對象,無論目前記憶體空間是否充足,都會将弱引用回收。不過由于垃圾回收器是一個優先級較低的線程,是以并不一定能迅速發現弱引用對象 4. 虛引用:顧名思義,就是形同虛設,如果一個對象僅持有虛引用,那麼它相當于沒有引用,在任何時候都可能被垃圾回收器回收。 更多了解參見[深入對象引用](http://blog.csdn.net/dd864140130/article/details/49885811) ## WeakReference與SoftReference的差別? 這點在四種引用類型中已經做了解釋,這裡簡單說明一下即可: 雖然 WeakReference 與 SoftReference 都有利于提高 GC 和 記憶體的效率,但是 WeakReference ,一旦失去最後一個強引用,就會被 GC 回收,而軟引用雖然不能阻止被回收,但是可以延遲到 JVM 記憶體不足的時候。 ## 為什麼要有不同的引用類型 不像C語言,我們可以控制記憶體的申請和釋放,在Java中有時候我們需要适當的控制對象被回收的時機,是以就誕生了不同的引用類型,可以說不同的引用類型實則是對GC回收時機不可控的妥協.有以下幾個使用場景可以充分的說明: 1. 利用軟引用和弱引用解決OOM問題:用一個HashMap來儲存圖檔的路徑和相應圖檔對象關聯的軟引用之間的映射關系,在記憶體不足時,JVM會自動回收這些緩存圖檔對象所占用的空間,進而有效地避免了OOM的問題. 2. 通過軟引用實作Java對象的高速緩存:比如我們建立了一Person的類,如果每次需要查詢一個人的資訊,哪怕是幾秒中之前剛剛查詢過的,都要重新建構一個執行個體,這将引起大量Person對象的消耗,并且由于這些對象的生命周期相對較短,會引起多次GC影響性能。此時,通過軟引用和 HashMap 的結合可以建構高速緩存,提供性能. ## java中==和`eqauls()`的差別,`equals()`和`hashcode的差別 ==是運算符,用于比較兩個變量是否相等,而equals是Object類的方法,用于比較兩個對象是否相等.預設Object類的equals方法是比較兩個對象的位址,此時和==的結果一樣.換句話說:基本類型比較用==,比較的是他們的值.預設下,對象用==比較時,比較的是記憶體位址,如果需要比較對象内容,需要重寫equal方法 ##`equals()`和`hashcode()`的聯系 `hashCode()`是Object類的一個方法,傳回一個哈希值.如果兩個對象根據equal()方法比較相等,那麼調用這兩個對象中任意一個對象的hashCode()方法必須産生相同的哈希值. 如果兩個對象根據eqaul()方法比較不相等,那麼産生的哈希值不一定相等(碰撞的情況下還是會相等的.) ## a.hashCode()有什麼用?與a.equals(b)有什麼關系 hashCode() 方法是相應對象整型的 hash 值。它常用于基于 hash 的集合類,如 Hashtable、HashMap、LinkedHashMap等等。它與 equals() 方法關系特别緊密。根據 Java 規範,使用 equal() 方法來判斷兩個相等的對象,必須具有相同的 hashcode。 将對象放入到集合中時,首先判斷要放入對象的hashcode是否已經在集合中存在,不存在則直接放入集合.如果hashcode相等,然後通過equal()方法判斷要放入對象與集合中的任意對象是否相等:如果equal()判斷不相等,直接将該元素放入集合中,否則不放入. ## 有沒有可能兩個不相等的對象有相同的hashcode 有可能,兩個不相等的對象可能會有相同的 hashcode 值,這就是為什麼在 hashmap 中會有沖突。如果兩個對象相等,必須有相同的hashcode 值,反之不成立. ## 可以在hashcode中使用随機數字嗎? 不行,因為同一對象的 hashcode 值必須是相同的 ## a==b與a.equals(b)有什麼差別 如果a 和b 都是對象,則 a==b 是比較兩個對象的引用,隻有當 a 和 b 指向的是堆中的同一個對象才會傳回 true,而 a.equals(b) 是進行邏輯比較,是以通常需要重寫該方法來提供邏輯一緻性的比較。例如,String 類重寫 equals() 方法,是以可以用于兩個不同對象,但是包含的字母相同的比較。 ## `3*0.1==0.3`傳回值是什麼 false,因為有些浮點數不能完全精确的表示出來。 ## a=a+b與a+=b有什麼差別嗎? +=操作符會進行隐式自動類型轉換,此處a+=b隐式的将加操作的結果類型強制轉換為持有結果的類型,而a=a+b則不會自動進行類型轉換.如: byte a = 127; byte b = 127; b = a + b; // error : cannot convert from int to byte b += a; // ok (譯者注:這個地方應該表述的有誤,其實無論 a+b 的值為多少,編譯器都會報錯,因為 a+b 操作會将 a、b 提升為 int 類型,是以将 int 類型指派給 byte 就會編譯出錯) ## short s1= 1; s1 = s1 + 1; 該段代碼是否有錯,有的話怎麼改? 有錯誤,short類型在進行運算時會自動提升為int類型,也就是說`s1+1`的運算結果是int類型. ##short s1= 1; s1 += 1; 該段代碼是否有錯,有的話怎麼改? +=操作符會自動對右邊的表達式結果強轉比對左邊的資料類型,是以沒錯. ## & 和 &&的差別 首先記住&是位操作,而&&是邏輯運算符.另外需要記住邏輯運算符具有短路特性,而&不具備短路特性. ```java public class Test{ static String name; public static void main(String[] args){ if(name!=null&userName.equals("")){ System.out.println("ok"); }else{ System.out.println("erro"); } } } ``` 以上代碼将會抛出空指針異常. ## 一個.java檔案内部可以有類?(非内部類) 隻能有一個public公共類,但是可以有多個default修飾的類. ## 如何正确的退出多層嵌套循環. 1. 使用标号和break; 2. 通過在外層循環中添加辨別符 ## 内部類的作用 内部類可以有多個執行個體,每個執行個體都有自己的狀态資訊,并且與其他外圍對象的資訊互相獨立.在單個外圍類當中,可以讓多個内部類以不同的方式實作同一接口,或者繼承同一個類.建立内部類對象的時刻不依賴于外部類對象的建立.内部類并沒有令人疑惑的”is-a”關系,它就像是一個獨立的實體. 内部類提供了更好的封裝,除了該外圍類,其他類都不能通路 ## final,finalize和finally的不同之處 final 是一個修飾符,可以修飾變量、方法和類。如果 final 修飾變量,意味着該變量的值在初始化後不能被改變。finalize 方法是在對象被回收之前調用的方法,給對象自己最後一個複活的機會,但是什麼時候調用 finalize 沒有保證。finally 是一個關鍵字,與 try 和 catch 一起用于異常的處理。finally 塊一定會被執行,無論在 try 塊中是否有發生異常。 ## clone()是哪個類的方法? java.lang.Cloneable 是一個标示性接口,不包含任何方法,clone 方法在 object 類中定義。并且需要知道 clone() 方法是一個本地方法,這意味着它是由 c 或 c++ 或 其他本地語言實作的。 ## 深拷貝和淺拷貝的差別是什麼? 淺拷貝:被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺拷貝僅僅複制所考慮的對象,而不複制它所引用的對象。 深拷貝:被複制對象的所有變量都含有與原來的對象相同的值,而那些引用其他對象的變量将指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深拷貝把要複制的對象所引用的對象都複制了一遍。 ## static都有哪些用法? 幾乎所有的人都知道static關鍵字這兩個基本的用法:靜态變量和靜态方法.也就是被static所修飾的變量/方法都屬于類的靜态資源,類執行個體所共享. 除了靜态變量和靜态方法之外,static也用于靜态塊,多用于初始化操作: ```java public calss PreCache{ static{ //執行相關操作 } } ``` 此外static也多用于修飾内部類,此時稱之為靜态内部類. 最後一種用法就是靜态導包,即`import static`.import static是在JDK 1.5之後引入的新特性,可以用來指定導入某個類中的靜态資源,并且不需要使用類名.資源名,可以直接使用資源名,比如: ```java import static java.lang.Math.*; public class Test{ public static void main(String[] args){ //System.out.println(Math.sin(20));傳統做法 System.out.println(sin(20)); } } ``` ## final有哪些用法 final也是很多面試喜歡問的地方,能回答下以下三點就不錯了: 1.被final修飾的類不可以被繼承 2.被final修飾的方法不可以被重寫 3.被final修飾的變量不可以被改變.如果修飾引用,那麼表示引用不可變,引用指向的内容可變. 4.被final修飾的方法,JVM會嘗試将其内聯,以提高運作效率 5.被final修飾的常量,在編譯階段會存入常量池中. 回答出編譯器對final域要遵守的兩個重排序規則更好: 1.在構造函數内對一個final域的寫入,與随後把這個被構造對象的引用指派給一個引用變量,這兩個操作之間不能重排序. 2.初次讀一個包含final域的對象的引用,與随後初次讀這個final域,這兩個操作之間不能重排序. ---------- #資料類型相關 ## java中int char,long各占多少位元組? |類型|位數|位元組數| |-|-|-| |short|2|16| |int|4|32| |long|8|64| |float|4|32 |double|8|64| |char|2|16| ## 64位的JVM當中,int的長度是多少? Java 中,int 類型變量的長度是一個固定值,與平台無關,都是 32 位。意思就是說,在 32 位 和 64 位 的Java 虛拟機中,int 類型的長度是相同的。 ## int和Integer的差別 Integer是int的包裝類型,在拆箱和裝箱中,二者自動轉換.int是基本類型,直接存數值,而integer是對象,用一個引用指向這個對象. ## int 和Integer誰占用的記憶體更多? Integer 對象會占用更多的記憶體。Integer是一個對象,需要存儲對象的中繼資料。但是 int 是一個原始類型的資料,是以占用的空間更少。 ## String,StringBuffer和StringBuilder差別 String是字元串常量,final修飾;StringBuffer字元串變量(線程安全); StringBuilder 字元串變量(線程不安全). ### String和StringBuffer String和StringBuffer主要差別是性能:String是不可變對象,每次對String類型進行操作都等同于産生了一個新的String對象,然後指向新的String對象.是以盡量不在對String進行大量的拼接操作,否則會産生很多臨時對象,導緻GC開始工作,影響系統性能. StringBuffer是對對象本身操作,而不是産生新的對象,是以在有大量拼接的情況下,我們建議使用StringBuffer. 但是需要注意現在JVM會對String拼接做一定的優化: `String s=“This is only ”+”simple”+”test”`會被虛拟機直接優化成`String s=“This is only simple test”`,此時就不存在拼接過程. ### StringBuffer和StringBuilder StringBuffer是線程安全的可變字元串,其内部實作是可變數組.StringBuilder是jdk 1.5新增的,其功能和StringBuffer類似,但是非線程安全.是以,在沒有多線程問題的前提下,使用StringBuilder會取得更好的性能. ## 什麼是編譯器常量?使用它有什麼風險? 公共靜态不可變(public static final )變量也就是我們所說的編譯期常量,這裡的 public 可選的。實際上這些變量在編譯時會被替換掉,因為編譯器知道這些變量的值,并且知道這些變量在運作時不能改變。這種方式存在的一個問題是你使用了一個内部的或第三方庫中的公有編譯時常量,但是這個值後面被其他人改變了,但是你的用戶端仍然在使用老的值,甚至你已經部署了一個新的jar。為了避免這種情況,當你在更新依賴 JAR 檔案時,確定重新編譯你的程式。 ## java當中使用什麼類型表示價格比較好? 如果不是特别關心記憶體和性能的話,使用BigDecimal,否則使用預定義精度的 double 類型。 ## 如何将byte轉為String 可以使用 String 接收 byte[] 參數的構造器來進行轉換,需要注意的點是要使用的正确的編碼,否則會使用平台預設編碼,這個編碼可能跟原來的編碼相同,也可能不同。 ## 可以将int強轉為byte類型麼?會産生什麼問題? 我們可以做強制轉換,但是Java中int是32位的而byte是8 位的,是以,如果強制轉化int類型的高24位将會被丢棄,byte 類型的範圍是從-128到128 ---------- # 關于垃圾回收 ## 你知道哪些垃圾回收算法? 垃圾回收從理論上非常容易了解,具體的方法有以下幾種: 1. 标記-清除 2. 标記-複制 3. 标記-整理 4. 分代回收 更詳細的内容參見[深入了解垃圾回收算法](http://blog.csdn.net/dd864140130/article/details/50084471) ##如何判斷一個對象是否應該被回收 這就是所謂的對象存活性判斷,常用的方法有兩種:1.引用計數法;2:對象可達性分析.由于引用計數法存在互相引用導緻無法進行GC的問題,是以目前JVM虛拟機多使用對象可達性分析算法. ##簡單的解釋一下垃圾回收 Java 垃圾回收機制最基本的做法是分代回收。記憶體中的區域被劃分成不同的世代,對象根據其存活的時間被儲存在對應世代的區域中。一般的實作是劃分成3個世代:年輕、年老和永久。記憶體的配置設定是發生在年輕世代中的。當一個對象存活時間足夠長的時候,它就會被複制到年老世代中。對于不同的世代可以使用不同的垃圾回收算法。進行世代劃分的出發點是對應用中對象存活時間進行研究之後得出的統計規律。一般來說,一個應用中的大部分對象的存活時間都很短。比如局部變量的存活時間就隻在方法的執行過程中。基于這一點,對于年輕世代的垃圾回收算法就可以很有針對性. ## 調用System.gc()會發生什麼? 通知GC開始工作,但是GC真正開始的時間不确定. ---------- # 程序,線程相關 ## 說說程序,線程,協程之間的差別 簡而言之,程序是程式運作和資源配置設定的基本機關,一個程式至少有一個程序,一個程序至少有一個線程.程序在執行過程中擁有獨立的記憶體單元,而多個線程共享記憶體資源,減少切換次數,進而效率更高.線程是程序的一個實體,是cpu排程和分派的基本機關,是比程式更小的能獨立運作的基本機關.同一程序中的多個線程之間可以并發執行. ## 你了解守護線程嗎?它和非守護線程有什麼差別 程式運作完畢,jvm會等待非守護線程完成後關閉,但是jvm不會等待守護線程.守護線程最典型的例子就是GC線程 ## 什麼是多線程上下文切換 多線程的上下文切換是指CPU控制權由一個已經正在運作的線程切換到另外一個就緒并等待擷取CPU執行權的線程的過程。 ## 建立兩種線程的方式?他們有什麼差別? 通過實作java.lang.Runnable或者通過擴充java.lang.Thread類.相比擴充Thread,實作Runnable接口可能更優.原因有二: 1. Java不支援多繼承.是以擴充Thread類就代表這個子類不能擴充其他類.而實作Runnable接口的類還可能擴充另一個類. 2. 類可能隻要求可執行即可,是以繼承整個Thread類的開銷過大. ## Thread類中的start()和run()方法有什麼差別? start()方法被用來啟動新建立的線程,而且start()内部調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法的時候,隻會是在原來的線程中調用,沒有新的線程啟動,start()方法才會啟動新線程。 ##怎麼檢測一個線程是否持有對象螢幕 Thread類提供了一個holdsLock(Object obj)方法,當且僅當對象obj的螢幕被某條線程持有的時候才會傳回true,注意這是一個static方法,這意味着"某條線程"指的是目前線程。 ## Runnable和Callable的差別 Runnable接口中的run()方法的傳回值是void,它做的事情隻是純粹地去執行run()方法中的代碼而已;Callable接口中的call()方法是有傳回值的,是一個泛型,和Future、FutureTask配合可以用來擷取異步執行的結果。 這其實是很有用的一個特性,因為多線程相比單線程更難、更複雜的一個重要原因就是因為多線程充滿着未知性,某條線程是否執行了?某條線程執行了多久?某條線程執行的時候我們期望的資料是否已經指派完畢?無法得知,我們能做的隻是等待這條多線程的任務執行完畢而已。而Callable+Future/FutureTask卻可以友善擷取多線程運作的結果,可以在等待時間太長沒擷取到需要的資料的情況下取消該線程的任務 ## 什麼導緻線程阻塞 阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒),學過作業系統的同學對它一定已經很熟悉了。Java 提供了大量方法來支援阻塞,下面讓我們逐一分析。 |方法|說明| |---|----| |sleep()|sleep() 允許 指定以毫秒為機關的一段時間作為參數,它使得線程在指定的時間内進入阻塞狀态,不能得到CPU 時間,指定的時間一過,線程重新進入可執行狀态。 典型地,sleep() 被用在等待某個資源就緒的情形:測試發現條件不滿足後,讓線程阻塞一段時間後重新測試,直到條件滿足為止| |suspend() 和 resume()|兩個方法配套使用,suspend()使得線程進入阻塞狀态,并且不會自動恢複,必須其對應的resume() 被調用,才能使得線程重新進入可執行狀态。典型地,suspend() 和 resume() 被用在等待另一個線程産生的結果的情形:測試發現結果還沒有産生後,讓線程阻塞,另一個線程産生了結果後,調用 resume() 使其恢複。| |yield() |yield() 使目前線程放棄目前已經分得的CPU 時間,但不使目前線程阻塞,即線程仍處于可執行狀态,随時可能再次分得 CPU 時間。調用 yield() 的效果等價于排程程式認為該線程已執行了足夠的時間進而轉到另一個線程| |wait() 和 notify()|兩個方法配套使用,wait() 使得線程進入阻塞狀态,它有兩種形式,一種允許 指定以毫秒為機關的一段時間作為參數,另一種沒有參數,前者當對應的 notify() 被調用或者超出指定時間時線程重新進入可執行狀态,後者則必須對應的 notify() 被調用.| ## wait(),notify()和suspend(),resume()之間的差別 初看起來它們與 suspend() 和 resume() 方法對沒有什麼分别,但是事實上它們是截然不同的。差別的核心在于,前面叙述的所有方法,阻塞時都不會釋放占用的鎖(如果占用了的話),而這一對方法則相反。上述的核心差別導緻了一系列的細節上的差別。 首先,前面叙述的所有方法都隸屬于 Thread 類,但是這一對卻直接隸屬于 Object 類,也就是說,所有對象都擁有這一對方法。初看起來這十分不可思議,但是實際上卻是很自然的,因為這一對方法阻塞時要釋放占用的鎖,而鎖是任何對象都具有的,調用任意對象的 wait() 方法導緻線程阻塞,并且該對象上的鎖被釋放。而調用 任意對象的notify()方法則導緻從調用該對象的 wait() 方法而阻塞的線程中随機選擇的一個解除阻塞(但要等到獲得鎖後才真正可執行)。 其次,前面叙述的所有方法都可在任何位置調用,但是這一對方法卻必須在 synchronized 方法或塊中調用,理由也很簡單,隻有在synchronized 方法或塊中目前線程才占有鎖,才有鎖可以釋放。同樣的道理,調用這一對方法的對象上的鎖必須為目前線程所擁有,這樣才有鎖可以釋放。是以,這一對方法調用必須放置在這樣的 synchronized 方法或塊中,該方法或塊的上鎖對象就是調用這一對方法的對象。若不滿足這一條件,則程式雖然仍能編譯,但在運作時會出現IllegalMonitorStateException 異常。 wait() 和 notify() 方法的上述特性決定了它們經常和synchronized關鍵字一起使用,将它們和作業系統程序間通信機制作一個比較就會發現它們的相似性:synchronized方法或塊提供了類似于作業系統原語的功能,它們的執行不會受到多線程機制的幹擾,而這一對方法則相當于 block 和wakeup 原語(這一對方法均聲明為 synchronized)。它們的結合使得我們可以實作作業系統上一系列精妙的程序間通信的算法(如信号量算法),并用于解決各種複雜的線程間通信問題。 關于 wait() 和 notify() 方法最後再說明兩點: 第一:調用 notify() 方法導緻解除阻塞的線程是從因調用該對象的 wait() 方法而阻塞的線程中随機選取的,我們無法預料哪一個線程将會被選擇,是以程式設計時要特别小心,避免因這種不确定性而産生問題。 第二:除了 notify(),還有一個方法 notifyAll() 也可起到類似作用,唯一的差別在于,調用 notifyAll() 方法将把因調用該對象的 wait() 方法而阻塞的所有線程一次性全部解除阻塞。當然,隻有獲得鎖的那一個線程才能進入可執行狀态。 談到阻塞,就不能不談一談死鎖,略一分析就能發現,suspend() 方法和不指定逾時期限的 wait() 方法的調用都可能産生死鎖。遺憾的是,Java 并不在語言級别上支援死鎖的避免,我們在程式設計中必須小心地避免死鎖。 以上我們對 Java 中實作線程阻塞的各種方法作了一番分析,我們重點分析了 wait() 和 notify() 方法,因為它們的功能最強大,使用也最靈活,但是這也導緻了它們的效率較低,較容易出錯。實際使用中我們應該靈活使用各種方法,以便更好地達到我們的目的。 ## 産生死鎖的條件 1.互斥條件:一個資源每次隻能被一個程序使用。 2.請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。 3.不剝奪條件:程序已獲得的資源,在末使用完之前,不能強行剝奪。 4.循環等待條件:若幹程序之間形成一種頭尾相接的循環等待資源關系。 ## 為什麼wait()方法和notify()/notifyAll()方法要在同步塊中被調用 這是JDK強制的,wait()方法和notify()/notifyAll()方法在調用前都必須先獲得對象的鎖 ## wait()方法和notify()/notifyAll()方法在放棄對象螢幕時有什麼差別 wait()方法和notify()/notifyAll()方法在放棄對象螢幕的時候的差別在于:wait()方法立即釋放對象螢幕,notify()/notifyAll()方法則會等待線程剩餘代碼執行完畢才會放棄對象螢幕。 ## wait()與sleep()的差別 關于這兩者已經在上面進行詳細的說明,這裡就做個概括好了: - sleep()來自Thread類,和wait()來自Object類.調用sleep()方法的過程中,線程不會釋放對象鎖。而 調用 wait 方法線程會釋放對象鎖 - sleep()睡眠後不出讓系統資源,wait讓其他線程可以占用CPU - sleep(milliseconds)需要指定一個睡眠時間,時間一到會自動喚醒.而wait()需要配合notify()或者notifyAll()使用 ## 為什麼wait,nofity和nofityAll這些方法不放在Thread類當中 一個很明顯的原因是JAVA提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。如果線程需要等待某些鎖那麼調用對象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個鎖就不明顯了。簡單的說,由于wait,notify和notifyAll都是鎖級别的操作,是以把他們定義在Object類中因為鎖屬于對象。 ##怎麼喚醒一個阻塞的線程 如果線程是因為調用了wait()、sleep()或者join()方法而導緻的阻塞,可以中斷線程,并且通過抛出InterruptedException來喚醒它;如果線程遇到了IO阻塞,無能為力,因為IO是作業系統實作的,Java代碼并沒有辦法直接接觸到作業系統。 ##什麼是多線程的上下文切換 多線程的上下文切換是指CPU控制權由一個已經正在運作的線程切換到另外一個就緒并等待擷取CPU執行權的線程的過程。 ## synchronized和ReentrantLock的差別 synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質差別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴充性展現在幾點上: (1)ReentrantLock可以對擷取鎖的等待時間進行設定,這樣就避免了死鎖 (2)ReentrantLock可以擷取各種鎖的資訊 (3)ReentrantLock可以靈活地實作多路通知 另外,二者的鎖機制其實也是不一樣的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word. ## FutureTask是什麼 這個其實前面有提到過,FutureTask表示一個異步運算的任務。FutureTask裡面可以傳入一個Callable的具體實作類,可以對這個異步運算的任務的結果進行等待擷取、判斷是否已經完成、取消任務等操作。當然,由于FutureTask也是Runnable接口的實作類,是以FutureTask也可以放入線程池中。 ## 一個線程如果出現了運作時異常怎麼辦? 如果這個異常沒有被捕獲的話,這個線程就停止執行了。另外重要的一點是:如果這個線程持有某個某個對象的螢幕,那麼這個對象螢幕會被立即釋放 ## Java當中有哪幾種鎖 1. 自旋鎖: 自旋鎖在JDK1.6之後就預設開啟了。基于之前的觀察,共享資料的鎖定狀态隻會持續很短的時間,為了這一小段時間而去挂起和恢複線程有點浪費,是以這裡就做了一個處理,讓後面請求鎖的那個線程在稍等一會,但是不放棄處理器的執行時間,看看持有鎖的線程能否快速釋放。為了讓線程等待,是以需要讓線程執行一個忙循環也就是自旋操作。在jdk6之後,引入了自适應的自旋鎖,也就是等待的時間不再固定了,而是由上一次在同一個鎖上的自旋時間及鎖的擁有者狀态來決定 2. 偏向鎖: 在JDK1.之後引入的一項鎖優化,目的是消除資料在無競争情況下的同步原語。進一步提升程式的運作性能。 偏向鎖就是偏心的偏,意思是這個鎖會偏向第一個獲得他的線程,如果接下來的執行過程中,改鎖沒有被其他線程擷取,則持有偏向鎖的線程将永遠不需要再進行同步。偏向鎖可以提高帶有同步但無競争的程式性能,也就是說他并不一定總是對程式運作有利,如果程式中大多數的鎖都是被多個不同的線程通路,那偏向模式就是多餘的,在具體問題具體分析的前提下,可以考慮是否使用偏向鎖。 3. 輕量級鎖: 為了減少獲得鎖和釋放鎖所帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,是以在Java SE1.6裡鎖一共有四種狀态,無鎖狀态,偏向鎖狀态,輕量級鎖狀态和重量級鎖狀态,它會随着競争情況逐漸更新。鎖可以更新但不能降級,意味着偏向鎖更新成輕量級鎖後不能降級成偏向鎖 ## 如何在兩個線程間共享資料 通過線上程之間共享對象就可以了,然後通過wait/notify/notifyAll、await/signal/signalAll進行喚起和等待,比方說阻塞隊列BlockingQueue就是為線程之間共享資料而設計的 ## 如何正确的使用wait()?使用if還是while? wait() 方法應該在循環調用,因為當線程擷取到 CPU 開始執行的時候,其他條件可能還沒有滿足,是以在處理前,循環檢測條件是否滿足會更好。下面是一段标準的使用 wait 和 notify 方法的代碼: ``` synchronized (obj) { while (condition does not hold) obj.wait(); // (Releases lock, and reacquires on wakeup) ... // Perform action appropriate to condition } ``` ## 什麼是線程局部變量ThreadLocal 線程局部變量是局限于線程内部的變量,屬于線程自身所有,不在多個線程間共享。Java提供ThreadLocal類來支援線程局部變量,是一種實作線程安全的方式。但是在管理環境下(如 web 伺服器)使用線程局部變量的時候要特别小心,在這種情況下,工作線程的生命周期比任何應用變量的生命周期都要長。任何線程局部變量一旦在工作完成後沒有釋放,Java 應用就存在記憶體洩露的風險。 ## ThreadLoal的作用是什麼? 簡單說ThreadLocal就是一種以空間換時間的做法在每個Thread裡面維護了一個ThreadLocal.ThreadLocalMap把資料進行隔離,資料不共享,自然就沒有線程安全方面的問題了. ## 生産者消費者模型的作用是什麼? (1)通過平衡生産者的生産能力和消費者的消費能力來提升整個系統的運作效率,這是生産者消費者模型最重要的作用 (2)解耦,這是生産者消費者模型附帶的作用,解耦意味着生産者和消費者之間的聯系少,聯系越少越可以獨自發展而不需要收到互相的制約 ## 寫一個生産者-消費者隊列 可以通過阻塞隊列實作,也可以通過wait-notify來實作. ### 使用阻塞隊列來實作 ``` //消費者 public class Producer implements Runnable{ private final BlockingQueue<Integer> queue; public Producer(BlockingQueue q){ this.queue=q; } @Override public void run() { try { while (true){ Thread.sleep(1000);//模拟耗時 queue.put(produce()); } }catch (InterruptedException e){ } } private int produce() { int n=new Random().nextInt(10000); System.out.println("Thread:" + Thread.currentThread().getId() + " produce:" + n); return n; } } //消費者 public class Consumer implements Runnable { private final BlockingQueue<Integer> queue; public Consumer(BlockingQueue q){ this.queue=q; } @Override public void run() { while (true){ try { Thread.sleep(2000);//模拟耗時 consume(queue.take()); }catch (InterruptedException e){ } } } private void consume(Integer n) { System.out.println("Thread:" + Thread.currentThread().getId() + " consume:" + n); } } //測試 public class Main { public static void main(String[] args) { BlockingQueue<Integer> queue=new ArrayBlockingQueue<Integer>(100); Producer p=new Producer(queue); Consumer c1=new Consumer(queue); Consumer c2=new Consumer(queue); new Thread(p).start(); new Thread(c1).start(); new Thread(c2).start(); } } ``` ### 使用wait-notify來實作 該種方式應該最經典,這裡就不做說明了 ##如果你送出任務時,線程池隊列已滿,這時會發生什麼 如果你使用的LinkedBlockingQueue,也就是無界隊列的話,沒關系,繼續添加任務到阻塞隊列中等待執行,因為LinkedBlockingQueue可以近乎認為是一個無窮大的隊列,可以無限存放任務;如果你使用的是有界隊列比方說ArrayBlockingQueue的話,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,則會使用拒絕政策RejectedExecutionHandler處理滿了的任務,預設是AbortPolicy。 ##為什麼要使用線程池 避免頻繁地建立和銷毀線程,達到線程對象的重用。另外,使用線程池還可以根據項目靈活地控制并發的數目。 ##java中用到的線程排程算法是什麼 搶占式。一個線程用完CPU之後,作業系統會根據線程優先級、線程饑餓情況等資料算出一個總的優先級并配置設定下一個時間片給某個線程執行。 ##Thread.sleep(0)的作用是什麼 由于Java采用搶占式的線程排程算法,是以可能會出現某條線程常常擷取到CPU控制權的情況,為了讓某些優先級比較低的線程也能擷取到CPU控制權,可以使用Thread.sleep(0)手動觸發一次作業系統配置設定時間片的操作,這也是平衡CPU控制權的一種操作。 ##什麼是CAS CAS,全稱為Compare and Swap,即比較-替換。假設有三個操作數:記憶體值V、舊的預期值A、要修改的值B,當且僅當預期值A和記憶體值V相同時,才會将記憶體值修改為B并傳回true,否則什麼都不做并傳回false。當然CAS一定要volatile變量配合,這樣才能保證每次拿到的變量是主記憶體中最新的那個值,否則舊的預期值A對某條線程來說,永遠是一個不會變的值A,隻要某次CAS操作失敗,永遠都不可能成功 ##什麼是樂觀鎖和悲觀鎖 樂觀鎖:樂觀鎖認為競争不總是會發生,是以它不需要持有鎖,将比較-替換這兩個動作作為一個原子操作嘗試去修改記憶體中的變量,如果失敗則表示發生沖突,那麼就應該有相應的重試邏輯。 悲觀鎖:悲觀鎖認為競争總是會發生,是以每次對某資源進行操作時,都會持有一個獨占的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。 ## ConcurrentHashMap的并發度是什麼? ConcurrentHashMap的并發度就是segment的大小,預設為16,這意味着最多同時可以有16條線程操作ConcurrentHashMap,這也是ConcurrentHashMap對Hashtable的最大優勢,任何情況下,Hashtable能同時有兩條線程擷取Hashtable中的資料嗎? ## ConcurrentHashMap的工作原理 ConcurrentHashMap在jdk 1.6和jdk 1.8實作原理是不同的. ### jdk 1.6: ConcurrentHashMap是線程安全的,但是與Hashtablea相比,實作線程安全的方式不同。Hashtable是通過對hash表結構進行鎖定,是阻塞式的,當一個線程占有這個鎖時,其他線程必須阻塞等待其釋放鎖。ConcurrentHashMap是采用分離鎖的方式,它并沒有對整個hash表進行鎖定,而是局部鎖定,也就是說當一個線程占有這個局部鎖時,不影響其他線程對hash表其他地方的通路。 具體實作:ConcurrentHashMap内部有一個Segment<K,V>數組,該Segment對象可以充當鎖。Segment對象内部有一個HashEntry<K,V>數組,于是每個Segment可以守護若幹個桶(HashEntry),每個桶又有可能是一個HashEntry連接配接起來的連結清單,存儲發生碰撞的元素。 每個ConcurrentHashMap在預設并發級下會建立包含16個Segment對象的數組,每個數組有若幹個桶,當我們進行put方法時,通過hash方法對key進行計算,得到hash值,找到對應的segment,然後對該segment進行加鎖,然後調用segment的put方法進行存儲操作,此時其他線程就不能通路目前的segment,但可以通路其他的segment對象,不會發生阻塞等待。 ### jdk 1.8 在jdk 8中,ConcurrentHashMap不再使用Segment分離鎖,而是采用一種樂觀鎖CAS算法來實作同步問題,但其底層還是“數組+連結清單->紅黑樹”的實作。 ## CyclicBarrier和CountDownLatch差別 這兩個類非常類似,都在java.util.concurrent下,都可以用來表示代碼運作到某個點上,二者的差別在于: - CyclicBarrier的某個線程運作到某個點上之後,該線程即停止運作,直到所有的線程都到達了這個點,所有線程才重新運作;CountDownLatch則不是,某線程運作到某個點上之後,隻是給某個數值-1而已,該線程繼續運作 - CyclicBarrier隻能喚起一個任務,CountDownLatch可以喚起多個任務 - CyclicBarrier可重用,CountDownLatch不可重用,計數值為0該CountDownLatch就不可再用了 ## java中的++操作符線程安全麼? 不是線程安全的操作。它涉及到多個指令,如讀取變量值,增加,然後存儲回記憶體,這個過程可能會出現多個線程交差 ## 你有哪些多線程開發良好的實踐? 1. 給線程命名 2. 最小化同步範圍 3. 優先使用volatile 4. 盡可能使用更高層次的并發工具而非wait和notify()來實作線程通信,如BlockingQueue,Semeaphore 5. 優先使用并發容器而非同步容器. 6. 考慮使用線程池 ---------- #關于volatile關鍵字 ## 可以建立Volatile數組嗎? Java 中可以建立 volatile類型數組,不過隻是一個指向數組的引用,而不是整個數組。如果改變引用指向的數組,将會受到volatile 的保護,但是如果多個線程同時改變數組的元素,volatile标示符就不能起到之前的保護作用了 ## volatile能使得一個非原子操作變成原子操作嗎? 一個典型的例子是在類中有一個 long 類型的成員變量。如果你知道該成員變量會被多個線程通路,如計數器、價格等,你最好是将其設定為 volatile。為什麼?因為 Java 中讀取 long 類型變量不是原子的,需要分成兩步,如果一個線程正在修改該 long 變量的值,另一個線程可能隻能看到該值的一半(前 32 位)。但是對一個 volatile 型的 long 或 double 變量的讀寫是原子。 一種實踐是用 volatile 修飾 long 和 double 變量,使其能按原子類型來讀寫。double 和 long 都是64位寬,是以對這兩種類型的讀是分為兩部分的,第一次讀取第一個 32 位,然後再讀剩下的 32 位,這個過程不是原子的,但 Java 中 volatile 型的 long 或 double 變量的讀寫是原子的。volatile 修複符的另一個作用是提供記憶體屏障(memory barrier),例如在分布式架構中的應用。簡單的說,就是當你寫一個 volatile 變量之前,Java 記憶體模型會插入一個寫屏障(write barrier),讀一個 volatile 變量之前,會插入一個讀屏障(read barrier)。意思就是說,在你寫一個 volatile 域時,能保證任何線程都能看到你寫的值,同時,在寫之前,也能保證任何數值的更新對所有線程是可見的,因為記憶體屏障會将其他所有寫的值更新到緩存。 ## volatile類型變量提供什麼保證? volatile 主要有兩方面的作用:1.避免指令重排2.可見性保證.例如,JVM 或者 JIT為了獲得更好的性能會對語句重排序,但是 volatile 類型變量即使在沒有同步塊的情況下指派也不會與其他語句重排序。 volatile 提供 happens-before 的保證,確定一個線程的修改能對其他線程是可見的。某些情況下,volatile 還能提供原子性,如讀 64 位資料類型,像 long 和 double 都不是原子的(低32位和高32位),但 volatile 類型的 double 和 long 就是原子的. ---------- #關于集合 ## Java中的集合及其繼承關系 關于集合的體系是每個人都應該爛熟于心的,尤其是對我們經常使用的List,Map的原理更該如此.這裡我們看這張圖即可: ![這裡寫圖檔描述](https://img-blog.csdn.net/20141105193133812?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGQ4NjQxNDAxMzA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 更多内容可見[集合類總結](http://write.blog.csdn.net/postedit/40826423) ## poll()方法和remove()方法差別? poll() 和 remove() 都是從隊列中取出一個元素,但是 poll() 在擷取元素失敗的時候會傳回空,但是 remove() 失敗的時候會抛出異常。 ## LinkedHashMap和PriorityQueue的差別 PriorityQueue 是一個優先級隊列,保證最高或者最低優先級的的元素總是在隊列頭部,但是 LinkedHashMap 維持的順序是元素插入的順序。當周遊一個 PriorityQueue 時,沒有任何順序保證,但是 LinkedHashMap 課保證周遊順序是元素插入的順序。 ## WeakHashMap與HashMap的差別是什麼? WeakHashMap 的工作與正常的 HashMap 類似,但是使用弱引用作為 key,意思就是當 key 對象沒有任何引用時,key/value 将會被回收。 ## ArrayList和LinkedList的差別? 最明顯的差別是 ArrrayList底層的資料結構是數組,支援随機通路,而 LinkedList 的底層資料結構是雙向循環連結清單,不支援随機通路。使用下标通路一個元素,ArrayList 的時間複雜度是 O(1),而 LinkedList 是 O(n)。 ## ArrayList和Array有什麼差別? 1. Array可以容納基本類型和對象,而ArrayList隻能容納對象。 2. Array是指定大小的,而ArrayList大小是固定的 ## ArrayList和HashMap預設大小? 在 Java 7 中,ArrayList 的預設大小是 10 個元素,HashMap 的預設大小是16個元素(必須是2的幂)。這就是 Java 7 中 ArrayList 和 HashMap 類的代碼片段 ``` private static final int DEFAULT_CAPACITY = 10; //from HashMap.java JDK 7 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 ``` ## Comparator和Comparable的差別? Comparable 接口用于定義對象的自然順序,而 comparator 通常用于定義使用者定制的順序。Comparable 總是隻有一個,但是可以有多個 comparator 來定義對象的順序。 ## 如何實作集合排序? 你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有順序的的集合,如 list,然後通過 Collections.sort() 來排序。 ## 如何列印數組内容 你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法來列印數組。由于數組沒有實作 toString() 方法,是以如果将數組傳遞給 System.out.println() 方法,将無法列印出數組的内容,但是 Arrays.toString() 可以列印每個元素。 ## LinkedList的是單向連結清單還是雙向? 雙向循環清單,具體實作自行查閱源碼. ## TreeMap是實作原理 采用紅黑樹實作,具體實作自行查閱源碼. ## 周遊ArrayList時如何正确移除一個元素 該問題的關鍵在于面試者使用的是 ArrayList 的 remove() 還是 Iterator 的 remove()方法。這有一段示例代碼,是使用正确的方式來實作在周遊的過程中移除元素,而不會出現 ConcurrentModificationException 異常的示例代碼。 ## 什麼是ArrayMap?它和HashMap有什麼差別? ArrayMap是Android SDK中提供的,非Android開發者可以略過. ArrayMap是用兩個數組來模拟map,更少的記憶體占用空間,更高的效率. 具體參考這篇文章:[ArrayMap VS HashMap](http://lvable.com/?p=217%5D) ## HashMap的實作原理 1 HashMap概述: HashMap是基于哈希表的Map接口的非同步實作。此實作提供所有可選的映射操作,并允許使用null值和null鍵。此類不保證映射的順序,特别是它不保證該順序恒久不變。 2 HashMap的資料結構: 在java程式設計語言中,最基本的結構就是兩種,一個是數組,另外一個是模拟指針(引用),所有的資料結構都可以用這兩個基本結構來構造的,HashMap也不例外。HashMap實際上是一個“連結清單散列”的資料結構,即數組和連結清單的結合體。 當我們往Hashmap中put元素時,首先根據key的hashcode重新計算hash值,根絕hash值得到這個元素在數組中的位置(下标),如果該數組在該位置上已經存放了其他元素,那麼在這個位置上的元素将以連結清單的形式存放,新加入的放在鍊頭,最先加入的放傳入連結尾.如果數組中該位置沒有元素,就直接将該元素放到數組的該位置上. 需要注意Jdk 1.8中對HashMap的實作做了優化,當連結清單中的節點資料超過八個之後,該連結清單會轉為紅黑樹來提高查詢效率,從原來的O(n)到O(logn) ## 你了解Fail-Fast機制嗎 Fail-Fast即我們常說的快速失敗,更多内容參看[fail-fast機制](http://blog.csdn.net/chenssy/article/details/38151189) ## Fail-fast和Fail-safe有什麼差別 Iterator的fail-fast屬性與目前的集合共同起作用,是以它不會受到集合中任何改動的影響。Java.util包中的所有集合類都被設計為fail->fast的,而java.util.concurrent中的集合類都為fail-safe的。當檢測到正在周遊的集合的結構被改變時,Fail-fast疊代器抛出ConcurrentModificationException,而fail-safe疊代器從不抛出ConcurrentModificationException。 ---------- # 關于日期 ## SimpleDateFormat是線程安全的嗎? 非常不幸,DateFormat 的所有實作,包括 SimpleDateFormat 都不是線程安全的,是以你不應該在多線程式中使用,除非是在對外線程安全的環境中使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不這麼做,在解析或者格式化日期的時候,可能會擷取到一個不正确的結果。是以,從日期、時間處理的所有實踐來說,我強力推薦 joda-time 庫。 ## 如何格式化日期? Java 中,可以使用 SimpleDateFormat 類或者 joda-time 庫來格式日期。DateFormat 類允許你使用多種流行的格式來格式化日期。參見答案中的示例代碼,代碼中示範了将日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。 ---------- # 關于異常 ## 簡單描述java異常體系 相比沒有人不了解異常體系,關于異常體系的更多資訊可以見:[白話異常機制](http://blog.csdn.net/dd864140130/article/details/42504189) ## 什麼是異常鍊 詳情直接參見[白話異常機制](http://blog.csdn.net/dd864140130/article/details/42504189),不做解釋了. ## throw和throws的差別 throw用于主動抛出java.lang.Throwable 類的一個執行個體化對象,意思是說你可以通過關鍵字 throw 抛出一個 Error 或者 一個Exception,如:`throw new IllegalArgumentException(“size must be multiple of 2″)`, 而throws 的作用是作為方法聲明和簽名的一部分,方法被抛出相應的異常以便調用者能處理。Java 中,任何未處理的受檢查異常強制在 throws 子句中聲明。 ---------- ## 關于序列化 ## Java 中,Serializable 與 Externalizable 的差別 Serializable 接口是一個序列化 Java 類的接口,以便于它們可以在網絡上傳輸或者可以将它們的狀态儲存在磁盤上,是 JVM 内嵌的預設序列化方式,成本高、脆弱而且不安全。Externalizable 允許你控制整個序列化過程,指定特定的二進制格式,增加安全機制。 ---------- #關于JVM ## JVM特性 平台無關性. Java語言的一個非常重要的特點就是與平台的無關性。而使用Java虛拟機是實作這一特點的關鍵。一般的進階語言如果要在不同的平台上運作,至少需要編譯成不同的目标代碼。而引入Java語言虛拟機後,Java語言在不同平台上運作時不需要重新編譯。Java語言使用模式Java虛拟機屏蔽了與具體平台相關的資訊,使得Java語言編譯程式隻需生成在Java虛拟機上運作的目标代碼(位元組碼),就可以在多種平台上不加修改地運作。Java虛拟機在執行位元組碼時,把位元組碼解釋成具體平台上的機器指令執行。 ## 簡單解釋一下類加載器 有關類加載器一般會問你四種類加載器的應用場景以及雙親委派模型,更多的内容參看[深入了解JVM加載器](http://blog.csdn.net/dd864140130/article/details/49817357) ## 簡述堆和棧的差別 VM 中堆和棧屬于不同的記憶體區域,使用目的也不同。棧常用于儲存方法幀和局部變量,而對象總是在堆上配置設定。棧通常都比堆小,也不會在多個線程之間共享,而堆被整個 JVM 的所有線程共享。 ## 簡述JVM記憶體配置設定 1. 基本資料類型比變量和對象的引用都是在棧配置設定的 2. 堆記憶體用來存放由new建立的對象和數組 3. 類變量(static修飾的變量),程式在一加載的時候就在堆中為類變量配置設定記憶體,堆中的記憶體位址存放在棧中 4. 執行個體變量:當你使用java關鍵字new的時候,系統在堆中開辟并不一定是連續的空間配置設定給變量,是根據零散的堆記憶體位址,通過雜湊演算法換算為一長串數字以表征這個變量在堆中的"實體位置”,執行個體變量的生命周期--當執行個體變量的引用丢失後,将被GC(垃圾回收器)列入可回收“名單”中,但并不是馬上就釋放堆中記憶體 5. 局部變量: 由聲明在某方法,或某代碼段裡(比如for循環),執行到它的時候在棧中開辟記憶體,當局部變量一但脫離作用域,記憶體立即釋放 ---------- #其他 ## java當中采用的是大端還是小端? ## XML解析的幾種方式和特點 DOM,SAX,PULL三種解析方式: - DOM:消耗記憶體:先把xml文檔都讀到記憶體中,然後再用DOM API來通路樹形結構,并擷取資料。這個寫起來很簡單,但是很消耗記憶體。要是資料過大,手機不夠牛逼,可能手機直接當機 - SAX:解析效率高,占用記憶體少,基于事件驅動的:更加簡單地說就是對文檔進行順序掃描,當掃描到文檔(document)開始與結束、元素(element)開始與結束、文檔(document)結束等地方時通知事件處理函數,由事件處理函數做相應動作,然後繼續同樣的掃描,直至文檔結束。 - PULL:與 SAX 類似,也是基于事件驅動,我們可以調用它的next()方法,來擷取下一個解析事件(就是開始文檔,結束文檔,開始标簽,結束标簽),當處于某個元素時可以調用XmlPullParser的getAttributte()方法來擷取屬性的值,也可調用它的nextText()擷取本節點的值。 ## JDK 1.7特性 然 JDK 1.7 不像 JDK 5 和 8 一樣的大版本,但是,還是有很多新的特性,如 try-with-resource 語句,這樣你在使用流或者資源的時候,就不需要手動關閉,Java 會自動關閉。Fork-Join 池某種程度上實作 Java 版的 Map-reduce。允許 Switch 中有 String 變量和文本。菱形操作符(\<\>)用于類型推斷,不再需要在變量聲明的右邊申明泛型,是以可以寫出可讀寫更強、更簡潔的代碼 ## JDK 1.8特性 java 8 在 Java 曆史上是一個開創新的版本,下面 JDK 8 中 5 個主要的特性: Lambda 表達式,允許像對象一樣傳遞匿名函數 Stream API,充分利用現代多核 CPU,可以寫出很簡潔的代碼 Date 與 Time API,最終,有一個穩定、簡單的日期和時間庫可供你使用 擴充方法,現在,接口中可以有靜态、預設方法。 重複注解,現在你可以将相同的注解在同一類型上使用多次。 ## Maven和ANT有什麼差別? 雖然兩者都是建構工具,都用于建立 Java 應用,但是 Maven 做的事情更多,在基于“約定優于配置”的概念下,提供标準的Java 項目結構,同時能為應用自動管理依賴(應用中所依賴的 JAR 檔案. ## JDBC最佳實踐 - 優先使用批量操作來插入和更新資料 - 使用PreparedStatement來避免SQL漏洞 - 使用資料連接配接池 - 通過列名來擷取結果集 ## IO操作最佳實踐 1. 使用有緩沖的IO類,不要單獨讀取位元組或字元 2. 使用NIO和NIO 2或者AIO,而非BIO 3. 在finally中關閉流 4. 使用記憶體映射檔案擷取更快的IO |