參考:http://blog.csdn.net/jackfrued/article/details/44921941java
說未經容許不轉載,我只好參考了。程序員
1.面向對象的特徵有哪些方面?面試
2.訪問修飾符權限算法
權限分爲:當前類,同包,子類,其餘包編程
public都可;protected其餘包不可;default同包下的能夠;private只有本身能夠。設計模式
3.String是基本數據類型嗎api
答:不是。java中8中基本類型:byte,short,int,long,float,double,char,boolean;除了基本類型(primitive type)和枚舉類型(enumeration type),剩下的都是引用類型(reference type)。數組
4.float f=3.4緩存
錯誤,默認是double的,須要強轉,或者f=3.4f;安全
5.int和integer
爲了將基本數據類型當作對象操做,Integer爲包裝類(wrapper class)。
Integer緩存爲-128到127.因此,這個範圍內的Integer對象是同一個,==爲true。其餘爲false。
6.&和&&
&連接的操做符都要計算。&&是短路運算,即當前面表達式有錯誤就中止計算。
7.解釋內存中的棧(stack)、堆(heap)、和靜態區(static area)的 用法
答:一般咱們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用內存中的棧空間;而經過new關鍵字和構造器建立的對象放在堆空間;程序中的字面量(literal)如直接書寫的100、「hello"和常量都是放在靜態區中。棧空間操做起來最快但棧很小,一般大量的對象都是放在堆空間,理論上整個內存沒有被其餘進程使用的空間甚至磁盤上的虛擬內存均可以當作堆空間來使用。
String str = new String("hello");
上面,str放棧,用new出來的字符串對象放堆上,而「hello」這個字面量放在靜態區。
java6開始使用「逃逸分析」的技術,能夠將一些局部對象放在棧上提高對象操做性能。
8.switch是否能夠用在byte,long,String?
答:java5前只能夠:byte、short、char、int。5後增長enum,7後增長String.
9.最有效率的方法計算2乘以8
答:2<<3(左移3至關於乘以2的3次方,右移3至關於除以2的3次方)
補充:咱們爲編寫的類重寫hashCode方法時,可能會看到以下所示的代碼,其實咱們不太理解爲何要使用這樣的乘法運算來產生哈希碼(散列碼),並且爲何這個數是個素數,爲何一般選擇31這個數?前兩個問題的答案你能夠本身百度一下,選擇31是由於能夠用移位和減法運算來代替乘法,從而獲得更好的性能。說到這裏你可能已經想到了:31 * num 等價於(num << 5) - num,左移5位至關於乘以2的5次方再減去自身就至關於乘以31,如今的VM都能自動完成這個優化。
10.數組有沒有length()方法,String有沒有length()方法?
答:數組沒有length()方法,有length屬性。String有length()方法。js中字符串是length屬性。
11.構造器constructor是否能夠override?
答:構造器不能被繼承,所以不能被重寫,但能夠被重載。
12.兩個對象值相同(x.equals(y)==true),但卻能夠有不一樣的hash code,這句話對不對?
答:不對。equals的hashcode必須相同。
13.是否能夠繼承String類
答:String類是final類,不能夠被繼承。繼承String是個錯誤的行爲,應該用關聯關係(Has-A)和依賴關係(Use A)而不是繼承關係(Is-A).
14.當一個對象被看成參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏究竟是值傳遞仍是引用傳遞?
答:是值傳遞。Java語言的方法調用只支持參數的值傳遞。當一個對象實例做爲一個參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的屬性能夠在被調用過程當中被改變,但對對象引用的改變是不會影響到調用者的。
15.String==
@Test public void str_c(){ String a = "hehe"; String b = "he"+"he"; String c = new String("hehe"); String d = new String("hehe"); System.out.println(a==b);//true System.out.println(a==c);//false System.out.println(a==a.intern());//true System.out.println(c==d);//false }
16.重載(Overload)和重寫(Override)的區別。重載的方法可否根據返回類型進行區分?
答:方法的重載和重寫都是實現多態的方式,區別在於前者實現的是編譯時的多態性,然後者實現的是運行時的多態性。重載發生在一個類中,同名的方法若是有不一樣的參數列表(參數類型不一樣、參數個數不一樣或者兩者都不一樣)則視爲重載;重寫發生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里氏代換原則)。重載對返回類型沒有特殊的要求。
2二、char 型變量中能不能存貯一箇中文漢字,爲何?
答:char類型能夠存儲一箇中文漢字,由於Java中使用的編碼是Unicode(不選擇任何特定的編碼,直接使用字符在字符集中的編號,這是統一的惟一方法),一個char類型佔2個字節(16比特),因此放一箇中文是沒問題的。
23.抽象類(abstract class)和接口(interface)有什麼異同?
答:抽象類和接口都不能實例化,但能夠定義抽象類和接口類型的引用。一個類若是繼承了某個抽象類或者實現了某個接口都須要對其中的抽象方法所有進行實現,不然該類仍然須要聲明爲抽象類。接口比抽象類更加抽象,由於抽象類中能夠定義構造器,能夠有抽象方法和具體方法,而接口中不能定義構造器並且其中的方法所有都是抽象方法。抽象類中的成員能夠是privae,默認,protected,public,而接口中的成員變量所有是public。抽象類中能夠定義成員變量,而接口中定義的成員白嬢實際上都是常量。有抽象方法的類必須被聲明爲抽象類,抽象類未必有抽象方法。
2四、靜態嵌套類(Static Nested Class)和內部類(Inner Class)的不一樣?
答:Static Nested Class是被聲明爲靜態(static)的內部類,它能夠不依賴於外部類實例被實例化。而一般的內部類須要在外部類實例化後才能實例化。
2五、Java 中會存在內存泄漏嗎,請簡單描述。
答:理論上Java由於有垃圾回收機制(GC)不會存在內存泄露問題(這也是Java被普遍使用於服務器端編程的一個重要緣由);然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被GC回收,所以也會致使內存泄露的發生。例如Hibernate的Session(一級緩存)中的對象屬於持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,若是不及時關閉(close)或清空(flush)一級緩存就可能致使內存泄露。下面例子中的代碼也會致使內存泄露。
import java.util.Arrays; import java.util.EmptyStackException; public class MyStack<T> { private T[] elements; private int size = 0; private static final int INIT_CAPACITY = 16; public MyStack() { elements = (T[]) new Object[INIT_CAPACITY]; } public void push(T elem) { ensureCapacity(); elements[size++] = elem; } public T pop() { if(size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if(elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } }
上面的代碼實現了一個棧(先進後出(FILO))結構,乍看之下彷佛沒有什麼明顯的問題,它甚至能夠經過你編寫的各類單元測試。然而其中的pop方法卻存在內存泄露的問題,當咱們用pop方法彈出棧中的對象時,該對象不會被看成垃圾回收,即便使用棧的程序再也不引用這些對象,由於棧內部維護着對這些對象的過時引用(obsolete reference)。在支持垃圾回收的語言中,內存泄露是很隱蔽的,這種內存泄露其實就是無心識的對象保持。若是一個對象引用被無心識的保留起來了,那麼垃圾回收器不會處理這個對象,也不會處理該對象引用的其餘對象,即便這樣的對象只有少數幾個,也可能會致使不少的對象被排除在垃圾回收以外,從而對性能形成重大影響,極端狀況下會引起Disk Paging(物理內存與硬盤的虛擬內存交換數據),甚至形成OutOfMemoryError。
2六、抽象的(abstract)方法是否可同時是靜態的(static),是否可同時是本地方法(native),是否可同時被synchronized修飾?
答:都不能。抽象方法須要子類重寫,而靜態的方法是沒法被重寫的,所以兩者是矛盾的。本地方法是由本地代碼(如C代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。synchronized和方法的實現細節有關,抽象方法不涉及實現細節,所以也是相互矛盾的。
2七、闡述靜態變量和實例變量的區別。
答:靜態變量是被static修飾符修飾的變量,也稱爲類變量,它屬於類,不屬於類的任何一個對象,一個類無論建立多少個對象,靜態變量在內存中有且僅有一個拷貝;實例變量必須依存於某一實例,須要先建立對象而後經過對象才能訪問到它。靜態變量能夠實現讓多個對象共享內存。
補充:在Java開發中,上下文類和工具類中一般會有大量的靜態成員。
28.繼承中變量的覆蓋和輸出
class Base { String a = "父類a"; String b = "父類b"; static void a() { System.out.println("父類的靜態A"); } void b() { System.out.println("父類的B"+b); } void c(){ System.out.println("父類的成員變量a:"+a); } void d(){ System.out.println("父成員變量b:"+b); } } class Inherit extends Base { String a = "zi類a"; String b = "zi類b"; static void a() { System.out.println("子類的靜態C"); } void b() { System.out.println("子類b:"+b); } void c(){ System.out.println("子類的成員變量a:"+a); } public static void main(String args[]) { Base b = new Base(); Inherit inherit = new Inherit(); Base c = new Inherit(); System.out.println("父類==================="); b.a();//父類的靜態A b.b();//父類的B System.out.println("子類==================="); inherit.a();//子類掩蓋了父類的靜態方法,子類的靜態C inherit.b();//子類b:zi類b,打印本身的 inherit.c();//子類的成員變量a:zi類a,打印本身的 inherit.d();//父成員變量b:父類b,調用父類的d方法,而且d方法裏的成員變量a也是b的 System.out.println("父類指向子類============"); c.a();//父類的靜態A c.b();//子類b:zi類b c.c();//子類的成員變量a:zi類a c.d();//父成員變量b:父類b } }
子類覆蓋了父類的方法,而且覆蓋了父類的成員變量,而且在覆蓋的方法中調用了這個覆蓋的成員變量。這時候,調用這個覆蓋的方法會調用覆蓋的成員變量。若是子類只覆蓋了成員變量,沒有覆蓋方法,調用這個方法會調用父類的成員變量,儘管這個成員變量被覆蓋了。
2八、是否能夠從一個靜態(static)方法內部發出對非靜態(non-static)方法的調用?
答:不能夠,靜態方法只能訪問靜態成員,由於非靜態方法的調用要先建立對象,在調用靜態方法時可能對象並無被初始化。
2九、如何實現對象克隆?
答:有兩種方式:
1). 實現Cloneable接口並重寫Object類中的clone()方法;
2). 實現Serializable接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆,代碼以下。
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class MyUtil { private MyUtil() { throw new AssertionError(); } public static <T> T clone(T obj) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bout); oos.writeObject(obj); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin); return (T) ois.readObject(); // 說明:調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義 // 這兩個基於內存的流只要垃圾回收器清理對象就可以釋放資源,這一點不一樣於對外部資源(如文件流)的釋放 } }
注意:基於序列化和反序列化實現的克隆不只僅是深度克隆,更重要的是經過泛型限定,能夠檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來老是優於把問題留到運行時。
30、GC是什麼?爲何要有GC?
答:GC是垃圾收集的意思,內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會致使程序或系統的不穩定甚至崩潰,Java提供的GC功能能夠自動監測對象是否超過做用域從而達到自動回收內存的目的,Java語言沒有提供釋放已分配內存的顯示操做方法。Java程序員不用擔憂內存管理,由於垃圾收集器會自動進行管理。要請求垃圾收集,能夠調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM能夠屏蔽掉顯示的垃圾回收調用。
垃圾回收能夠有效的防止內存泄露,有效的使用可使用的內存。垃圾回收器一般是做爲一個單獨的低優先級的線程運行,不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或全部對象進行垃圾回收。在Java誕生初期,垃圾回收是Java最大的亮點之一,由於服務器端的編程須要有效的防止內存泄露問題,然而時過境遷,現在Java的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶一般以爲iOS的系統比Android系統有更好的用戶體驗,其中一個深層次的緣由就在於Android系統中垃圾回收的不可預知性。
補充:垃圾回收機制有不少種,包括:分代複製垃圾回收、標記垃圾回收、增量垃圾回收等方式。標準的Java進程既有棧又有堆。棧保存了原始型局部變量,堆保存了要建立的對象。Java平臺對堆內存回收和再利用的基本算法被稱爲標記和清除,可是Java對其進行了改進,採用「分代式垃圾收集」。這種方法會跟Java對象的生命週期將堆內存劃分爲不一樣的區域,在垃圾收集過程當中,可能會將對象移動到不一樣區域:
- 伊甸園(Eden):這是對象最初誕生的區域,而且對大多數對象來講,這裏是它們惟一存在過的區域。
- 倖存者樂園(Survivor):從伊甸園倖存下來的對象會被挪到這裏。
- 終身頤養園(Tenured):這是足夠老的倖存對象的歸宿。年輕代收集(Minor-GC)過程是不會觸及這個地方的。當年輕代收集不能把對象放進終身頤養園時,就會觸發一次徹底收集(Major-GC),這裏可能還會牽扯到壓縮,以便爲大對象騰出足夠的空間。
與垃圾回收相關的JVM參數:
- -Xms / -Xmx — 堆的初始大小 / 堆的最大大小
- -Xmn — 堆中年輕代的大小
- -XX:-DisableExplicitGC — 讓System.gc()不產生任何做用
- -XX:+PrintGCDetails — 打印GC的細節
- -XX:+PrintGCDateStamps — 打印GC操做的時間戳
- -XX:NewSize / XX:MaxNewSize — 設置新生代大小/新生代最大大小
- -XX:NewRatio — 能夠設置老生代和新生代的比例
- -XX:PrintTenuringDistribution — 設置每次新生代GC後輸出倖存者樂園中對象年齡的分佈
- -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設置老年代閥值的初始值和最大值
- -XX:TargetSurvivorRatio:設置倖存區的目標使用率
3一、String s = new String("xyz");建立了幾個字符串對象?
答:兩個對象,一個是靜態區的"xyz",一個是用new建立在堆上的對象。
3二、接口是否可繼承(extends)接口?抽象類是否可實現(implements)接口?抽象類是否可繼承具體類(concrete class)?
答:接口能夠繼承接口,並且支持多重繼承。抽象類能夠實現(implements)接口,抽象類可繼承具體類也能夠繼承抽象類。
3五、內部類能夠引用它的包含類(外部類)的成員嗎?有沒有什麼限制?
答:一個內部類對象能夠訪問建立它的外部類對象的成員,包括私有成員。
3六、Java 中的final關鍵字有哪些用法?
答:(1)修飾類:表示該類不能被繼承;(2)修飾方法:表示方法不能被重寫;(3)修飾變量:表示變量只能一次賦值之後值不能被修改(常量)。
4六、try{}裏有一個return語句,那麼緊跟在這個try後的finally{}裏的代碼會不會被執行,何時被執行,在return前仍是後?
答:會執行,在方法返回調用者前執行。
注意:在finally中改變返回值的作法是很差的,由於若是存在finally代碼塊,try中的return語句不會立馬返回調用者,而是記錄下返回值待finally代碼塊執行完畢以後再向調用者返回其值,而後若是在finally中修改了返回值,就會返回修改後的值。顯然,在finally中返回或者修改返回值會對程序形成很大的困擾
4九、列出一些你常見的運行時異常?
答:
- ArithmeticException(算術異常)
- ClassCastException (類轉換異常)
- IllegalArgumentException (非法參數異常)
- IndexOutOfBoundsException (下標越界異常)
- NullPointerException (空指針異常)
- SecurityException (安全異常)
50、闡述final、finally、finalize的區別。
答:
- final:修飾符(關鍵字)有三種用法:若是一個類被聲明爲final,意味着它不能再派生出新的子類,即不能被繼承,所以它和abstract是反義詞。將變量聲明爲final,能夠保證它們在使用中不被改變,被聲明爲final的變量必須在聲明時給定初值,而在之後的引用中只能讀取不可修改。被聲明爲final的方法也一樣只能使用,不能在子類中被重寫。
- finally:一般放在try…catch…的後面構造老是執行代碼塊,這就意味着程序不管正常執行仍是發生異常,這裏的代碼只要JVM不關閉都能執行,能夠將釋放外部資源的代碼寫在finally塊中。
- finalize:Object類中定義的方法,Java中容許使用finalize()方法在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。這個方法是由垃圾收集器在銷燬對象時調用的,經過重寫finalize()方法能夠整理系統資源或者執行其餘清理工做。
5二、List、Set、Map是否繼承自Collection接口?
答:List、Set 是,Map 不是。Map是鍵值對映射容器,與List和Set有明顯的區別,而Set存儲的零散的元素且不容許有重複元素(數學中的集合也是如此),List是線性結構的容器,適用於按數值索引訪問元素的情形。
5三、闡述ArrayList、Vector、LinkedList的存儲性能和特性。
答:ArrayList 和Vector都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增長和插入元素,它們都容許直接按序號索引元素,可是插入元素要涉及數組元素移動等內存操做,因此索引數據快而插入數據慢,Vector中的方法因爲添加了synchronized修飾,所以Vector是線程安全的容器,但性能上較ArrayList差,所以已是Java中的遺留容器。LinkedList使用雙向鏈表實現存儲(將內存中零散的內存單元經過附加的引用關聯起來,造成一個能夠按序號索引的線性結構,這種鏈式存儲方式與數組的連續存儲方式相比,內存的利用率更高),按序號索引數據須要進行前向或後向遍歷,可是插入數據時只須要記錄本項的先後項便可,因此插入速度較快。Vector屬於遺留容器(Java早期的版本中提供的容器,除此以外,Hashtable、Dictionary、BitSet、Stack、Properties都是遺留容器),已經不推薦使用,可是因爲ArrayList和LinkedListed都是非線程安全的,若是遇到多個線程操做同一個容器的場景,則能夠經過工具類Collections中的synchronizedList方法將其轉換成線程安全的容器後再使用(這是對裝潢模式的應用,將已有對象傳入另外一個類的構造器中建立新的對象來加強實現)。
補充:遺留容器中的Properties類和Stack類在設計上有嚴重的問題,Properties是一個鍵和值都是字符串的特殊的鍵值對映射,在設計上應該是關聯一個Hashtable並將其兩個泛型參數設置爲String類型,可是Java API中的Properties直接繼承了Hashtable,這很明顯是對繼承的濫用。這裏複用代碼的方式應該是Has-A關係而不是Is-A關係,另外一方面容器都屬於工具類,繼承工具類自己就是一個錯誤的作法,使用工具類最好的方式是Has-A關係(關聯)或Use-A關係(依賴)。同理,Stack類繼承Vector也是不正確的。Sun公司的工程師們也會犯這種低級錯誤,讓人唏噓不已。
5四、Collection和Collections的區別?
答:Collection是一個接口,它是Set、List等容器的父接口;Collections是個一個工具類,提供了一系列的靜態方法來輔助容器操做,這些方法包括對容器的搜索、排序、線程安全化等等。
5五、List、Map、Set三個接口存取元素時,各有什麼特色?
答:List以特定索引來存取元素,能夠有重複元素。Set不能存放重複元素(用對象的equals()方法來區分元素是否重複)。Map保存鍵值對(key-value pair)映射,映射關係能夠是一對一或多對一。Set和Map容器都有基於哈希存儲和排序樹的兩種實現版本,基於哈希存儲的版本理論存取時間複雜度爲O(1),而基於排序樹版本的實如今插入或刪除元素時會按照元素或元素的鍵(key)構成排序樹從而達到排序和去重的效果。
5六、TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?
答:TreeSet要求存放的對象所屬的類必須實現Comparable接口,該接口提供了比較元素的compareTo()方法,當插入元素時會回調該方法比較元素的大小。TreeMap要求存放的鍵值對映射的鍵必須實現Comparable接口從而根據鍵對元素進行排序。Collections工具類的sort方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對象比較實現Comparable接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,可是要求傳入第二個參數,參數是Comparator接口的子類型(須要重寫compare方法實現元素的比較),至關於一個臨時定義的排序規則,其實就是經過接口注入比較元素大小的算法,也是對回調模式的應用(Java中對函數式編程的支持)。
5七、Thread類的sleep()方法和對象的wait()方法均可以讓線程暫停執行,它們有什麼區別?
答:sleep()方法(休眠)是線程類(Thread)的靜態方法,調用此方法會讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其餘線程,可是對象的鎖依然保持,所以休眠時間結束後會自動恢復(線程回到就緒狀態,請參考第66題中的線程狀態轉換圖)。wait()是Object類的方法,調用對象的wait()方法致使當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的notify()方法(或notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lock pool),若是線程從新得到對象的鎖就能夠進入就緒狀態。
5八、線程的sleep()方法和yield()方法有什麼區別?
答:
① sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
② 線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;
③ sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
④ sleep()方法比yield()方法(跟操做系統CPU調度相關)具備更好的可移植性。
60、請說出與線程同步以及線程調度相關的方法。
答:
- wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;
- sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常;
- notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM肯定喚醒哪一個線程,並且與優先級無關;
- notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;
提示:關於Java多線程和併發編程的問題,建議你們看個人另外一篇文章《關於Java併發編程的總結和思考》。
補充:Java 5經過Lock接口提供了顯式的鎖機制(explicit lock),加強了靈活性以及對線程的協調。Lock接口中定義了加鎖(lock())和解鎖(unlock())的方法,同時還提供了newCondition()方法來產生用於線程之間通訊的Condition對象;此外,Java 5還提供了信號量機制(semaphore),信號量能夠用來限制對某個共享資源進行訪問的線程的數量。在對資源進行訪問以前,線程必須獲得信號量的許可(調用Semaphore對象的acquire()方法);在完成對資源的訪問後,線程必須向信號量歸還許可(調用Semaphore對象的release()方法)。
6一、編寫多線程程序有幾種實現方式?
答:Java 5之前實現多線程有兩種實現方法:一種是繼承Thread類;另外一種是實現Runnable接口。兩種方式都要經過重寫run()方法來定義線程的行爲,推薦使用後者,由於Java中的繼承是單繼承,一個類有一個父類,若是繼承了Thread類就沒法再繼承其餘類了,顯然使用Runnable接口更爲靈活。
補充:Java 5之後建立線程還有第三種方式:實現Callable接口,該接口中的call方法能夠在線程執行結束時產生一個返回值
6四、啓動一個線程是調用run()仍是start()方法?
答:啓動一個線程是調用start()方法,使線程所表明的虛擬處理機處於可運行狀態,這意味着它能夠由JVM 調度並執行,這並不意味着線程就會當即運行。run()方法是線程啓動後要進行回調(callback)的方法。
6五、什麼是線程池(thread pool)?
答:在面向對象編程中,建立和銷燬對象是很費時間的,由於建立一個對象要獲取內存資源或者其它更多資源。在Java中更是如此,虛擬機將試圖跟蹤每個對象,以便可以在對象銷燬後進行垃圾回收。因此提升服務程序效率的一個手段就是儘量減小建立和銷燬對象的次數,特別是一些很耗資源的對象建立和銷燬,這就是」池化資源」技術產生的緣由。線程池顧名思義就是事先建立若干個可執行的線程放入一個池(容器)中,須要的時候從池中獲取線程不用自行建立,使用完畢不須要銷燬線程而是放回池中,從而減小建立和銷燬線程對象的開銷。
Java 5+中的Executor接口定義一個執行線程的工具。它的子類型即線程池接口是ExecutorService。要配置一個線程池是比較複雜的,尤爲是對於線程池的原理不是很清楚的狀況下,所以在工具類Executors面提供了一些靜態工廠方法,生成一些經常使用的線程池,以下所示:
- newSingleThreadExecutor:建立一個單線程的線程池。這個線程池只有一個線程在工做,也就是至關於單線程串行執行全部任務。若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它。此線程池保證全部任務的執行順序按照任務的提交順序執行。
- newFixedThreadPool:建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結束,那麼線程池會補充一個新線程。
- newCachedThreadPool:建立一個可緩存的線程池。若是線程池的大小超過了處理任務所須要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增長時,此線程池又能夠智能的添加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系統(或者說JVM)可以建立的最大線程大小。
- newScheduledThreadPool:建立一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。
- newSingleThreadExecutor:建立一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。
第60題的例子中演示了經過Executors工具類建立線程池並使用線程池執行線程的代碼。若是但願在服務器上使用線程池,強烈建議使用newFixedThreadPool方法來建立線程池,這樣能得到更好的性能。
6八、Java中如何實現序列化,有什麼意義?
答:序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化。能夠對流化後的對象進行讀寫操做,也可將流化後的對象傳輸於網絡之間。序列化是爲了解決對象流讀寫操做時可能引起的問題(若是不進行序列化可能會存在數據亂序的問題)。
要實現序列化,須要讓一個類實現Serializable接口,該接口是一個標識性接口,標註該類對象是可被序列化的,而後使用一個輸出流來構造一個對象輸出流並經過writeObject(Object)方法就能夠將實現對象寫出(即保存其狀態);若是須要反序列化則能夠用一個輸入流創建對象輸入流,而後經過readObject方法從流中讀取對象。序列化除了可以實現對象的持久化以外,還可以用於對象的深度克隆(能夠參考第29題)。
6九、Java中有幾種類型的流?
答:字節流和字符流。字節流繼承於InputStream、OutputStream,字符流繼承於Reader、Writer。在java.io 包中還有許多其餘的流,主要是爲了提升性能和使用方便。關於Java的I/O須要注意的有兩點:一是兩種對稱性(輸入和輸出的對稱性,字節和字符的對稱性);二是兩種設計模式(適配器模式和裝潢模式)。另外Java中的流不一樣於C#的是它只有一個維度一個方向。
面試題 - 編程實現文件拷貝。(這個題目在筆試的時候常常出現,下面的代碼給出了兩種實現方案)
public static void fileCopy(String source,String targer) throws IOException { try(InputStream in = new FileInputStream(source)) { try(OutputStream out = new FileOutputStream(targer)) { byte[] buffer = new byte[3096]; int byteToRead; while((byteToRead = in.read(buffer))!=-1){ out.write(buffer,0,byteToRead); } } } } 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(); } } } }
注意:上面用到Java 7的TWR,使用TWR後能夠不用在finally中釋放外部資源 ,從而讓代碼更加優雅。
70、寫一個方法,輸入一個文件名和一個字符串,統計這個字符串在這個文件中出現的次數。
/** *統計給定文件中字符word的個數 */ public static int countWordInFile(String filename,String word) throws IOException { int count = 0; try(FileReader fr = new FileReader(filename)){ try(BufferedReader br = new BufferedReader(fr)){ String line = null; while ((line = br.readLine())!=null){ int index = -1; while (line.length()>=word.length() && (index=line.indexOf(word))>=0){ count++; line = line.substring(index+word.length()); } } } } return count; }
7一、如何用Java代碼列出一個目錄下全部的文件?
/** * 列出當前文件夾下的文件 */ public void fileList(String source){ File file = new File(source); for (File temp : file.listFiles()) { if (temp.isFile()){ System.out.println(temp.getName()); } } }
若是須要對文件夾繼續展開,代碼以下所示:
/** * 列出文件夾下的全部文件,深刻 */ private static void _walkDirectory(File f,int level) throws IOException { if (f.isDirectory()){ writeTofile(f, level); for (File temp : f.listFiles()) { _walkDirectory(temp,level+1); } }else { writeTofile(f, level); } } private static void writeTofile(File f, int level) throws IOException { try(BufferedWriter bw = new BufferedWriter(new FileWriter(new File("F:\\listFile.txt"),true))){ for (int i = 0; i < level - 1; i++) { System.out.print("----"); bw.write("----"); } System.out.println("|"+f.getName()); bw.write("|"+f.getName()); bw.newLine(); bw.flush(); } } public static void showDirectory(File f) throws IOException { _walkDirectory(f,0); } @Test public void testShow(){ try { showDirectory(new File("D:\\MyApp")); } catch (IOException e) { e.printStackTrace(); } }
在java7中可使用NIO.2的api來作:
/** * 列出文件夾下的全部文件,深刻 */ @Test public void listFiles() throws IOException { Path path = Paths.get("D:\\MyApp"); Files.walkFileTree(path,new SimpleFileVisitor<Path>(){ @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs){ System.out.println(file.getFileName().toString()); return FileVisitResult.CONTINUE; } }); }
7二、用Java的套接字編程實現一個多線程的回顯(echo)服務器。
答:
/** * socket多線程回顯 * Created by mrf on 2016/3/17. */ public class EchoServer { private static final int ECHO_SERVER_PORT = 6789; public static void main(String[] args) { try(ServerSocket server = new ServerSocket(ECHO_SERVER_PORT)) { System.out.println("================================="); System.out.println("============服務啓動 ============="); while (true){ Socket client = server.accept(); new Thread(new ClientHandler(client)).start(); } } catch (IOException e) { e.printStackTrace(); } } private static class ClientHandler implements Runnable{ private Socket client; public ClientHandler(Socket client){ this.client = client; } @Override public void run() { try(BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); PrintWriter pw = new PrintWriter(client.getOutputStream()) ) { String msg = br.readLine(); System.out.println("收到"+client.getInetAddress()+"發送的:"+msg); pw.println(msg); pw.flush(); } catch (IOException e) { e.printStackTrace(); }finally { try { client.close(); } catch (IOException e) { e.printStackTrace(); } } } } } /** * 測試 */ class EchoClient{ public static void main(String[] args) throws IOException { Socket client = new Socket("localhost",6789); Scanner sc = new Scanner(System.in); System.out.println("請輸入內容:"); String msg = sc.nextLine(); sc.close(); PrintWriter pw = new PrintWriter(client.getOutputStream()); pw.println(msg); pw.flush(); BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); System.out.println(br.readLine()); } }
7三、XML文檔定義有幾種形式?它們之間有何本質區別?解析XML文檔有哪幾種方式?
答:xml文檔定義分爲DTD和Schema兩種形式,兩者都是對xml語法的約束,其本質區別在於Schema自己也是一個xml文件,能夠被xml解析器解析,並且能夠爲xml承載的數據定義類型,約束能力較之DTD更強大。對xml的解析主要有
dom(文檔對象模型,Document Object Model)、SAX(Simple API for xml)和StAx(java6中引入的新的解析xml的方式,Streaming API for xml),其中dom處理大型文件時其性能降低的很是厲害,這個問題是由DOM樹結構佔用的內存較多形成的,並且dom解析方式必須在解析文件以前把整個文件裝入內存,適合對xml的隨機訪問(典型的空間換時間);sax是事件驅動的xml解析方法,它順序讀取xml文件,不須要一次所有裝載整個文件。檔遇到像文件開頭,文檔結束,或者標籤開頭與標籤結束時,它會觸發一個事件,用戶經過事件回調代碼來處理xml文件,適合對xml的順序訪問;顧名思義,StAx把重點放在流上,實際上StAX與其餘解析方式的本質區別就在於應用程序可以把xml作爲一個事件流來處理。將xml作爲一組事件來處理的想法並不新穎(sax就是這樣作的),但不一樣之處在於StAx容許應用程序代碼把這些事件逐個拉出來,而不用提供在解析器方便時從解析器中接收事件的處理程序。