40道Java基礎常見面試題及詳細答案
最近看到網上流傳着各種面試經驗及面試題,往往都是一大堆技術題目貼上去,但是沒有答案。
爲此我業餘時間整理了40道Java基礎常見的面試題及詳細答案,望各路大牛發現不對的地方不吝賜教,留言即可。
- 八種基本數據類型的大小,以及他們的封裝類
- 引用數據類型
- Switch能否用string做參數
- equals與==的區別
- 自動裝箱,常量池
- Object有哪些公用方法
- Java的四種引用,強弱軟虛,用到的場景
- Hashcode的作用
- HashMap的hashcode的作用
- 爲什麼重載hashCode方法?
- ArrayList、LinkedList、Vector的區別
- String、StringBuffer與StringBuilder的區別
- Map、Set、List、Queue、Stack的特點與用法
- HashMap和HashTable的區別
- JDK7與JDK8中HashMap的實現
- HashMap和ConcurrentHashMap的區別,HashMap的底層源碼
- ConcurrentHashMap能完全替代HashTable嗎
- 爲什麼HashMap是線程不安全的
- 如何線程安全的使用HashMap
- 多併發情況下HashMap是否還會產生死循環
- TreeMap、HashMap、LindedHashMap的區別
- Collection包結構,與Collections的區別
- try?catch?finally,try裏有return,finally還執行麼
- Excption與Error包結構,OOM你遇到過哪些情況,SOF你遇到過哪些情況
- Java(OOP)面向對象的三個特徵與含義
- Override和Overload的含義去區別
- Interface與abstract類的區別
- Static?class?與non?static?class的區別
- java多態的實現原理
- foreach與正常for循環效率對比
- Java?IO與NIO
- java反射的作用於原理
- 泛型常用特點
- 解析XML的幾種方式的原理與特點:DOM、SAX
- Java1.7與1.8,1.9,10 新特性
- 設計模式:單例、工廠、適配器、責任鏈、觀察者等等
- JNI的使用
- AOP是什麼
- OOP是什麼
- AOP與OOP的區別
八種基本數據類型的大小,以及他們的封裝類
八種基本數據類型:int、short、float、double、long、boolean、byte、char。
封裝類分別是:Integer、Short、Float、Double、Long、Boolean、Byte、Character。
引用數據類型
引用數據類型是由類的編輯器定義的,他們是用於訪問對象的。這些變量被定義爲不可更改的特定類型。
例如:Employee, Puppy 等等
- 類對象和數組變量就是這種引用數據類型。
- 任何引用數據類型的默認值都爲空。
- 一個引用數據類型可以被用於任何聲明類型和兼容類型的對象。
Switch能否用string做參數
jdk7之前 switch 只能支持 byte、short、char、int 這幾個基本數據類型和其對應的封裝類型。
switch後面的括號裏面只能放int類型的值,但由於byte,short,char類型,它們會?自動?轉換爲int類型(精精度小的向大的轉化),所以它們也支持。
jdk1.7後 整形,枚舉類型,字符串都可以。
原理
1
2
3
4
5
6
7
8
9
10
11
|
switch (expression) // 括號裏是一個表達式,結果是個整數{ case constant1: // case 後面的標號,也是個整數 group of statements 1 ; break ; case constant2: group of statements 2 ; break ; ... default : default group of statements } |
jdk1.7後,整形,枚舉類,字符串都可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class TestString { static String string = "123" ; public static void main(String[] args) { switch (string) { case "123" : System.out.println( "123" ); break ; case "abc" : System.out.println( "abc" ); break ; default : System.out.println( "defauls" ); break ; } } } |
爲什麼jdk1.7後又可以用string類型作爲switch參數呢?
其實,jdk1.7並沒有新的指令來處理switch string,而是通過調用switch中string.hashCode,將string轉換爲int從而進行判斷。
equals與==的區別
使用==比較原生類型如:boolean、int、char等等,使用equals()比較對象。
1、==是判斷兩個變量或實例是不是指向同一個內存空間。 equals是判斷兩個變量或實例所指向的內存空間的值是不是相同。
2、==是指對內存地址進行比較。 equals()是對字符串的內容進行比較。
3、==指引用是否相同。 equals()指的是值是否相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public static void main(String[] args) { String a = new String( "ab" ); // a 爲一個引用 String b = new String( "ab" ); // b爲另一個引用,對象的內容一樣 String aa = "ab" ; // 放在常量池中 String bb = "ab" ; // 從常量池中查找 System.out.println(aa == bb); // true System.out.println(a == b); // false,非同一對象 System.out.println(a.equals(b)); // true System.out.println( 42 == 42.0 ); // true } public static void main(String[] args) { Object obj1 = new Object(); Object obj2 = new Object(); System.out.println(obj1.equals(obj2)); //false System.out.println(obj1==obj2); //false obj1=obj2; System.out.println(obj1==obj2); //true System.out.println(obj2==obj1); //true } |
自動裝箱,常量池
自動裝箱 在jdk?1.5之前,如果你想要定義一個value爲100的Integer對象,則需要如下定義:
1
2
3
4
5
6
|
Integer i = new Integer( 100 ); int intNum1 = 100 ; //普通變量 Integer intNum2 = intNum1; //自動裝箱 int intNum3 = intNum2; //自動拆箱 Integer intNum4 = 100 ; //自動裝箱 |
上面的代碼中,intNum2爲一個Integer類型的實例,intNum1爲Java中的基礎數據類型,將intNum1賦值給intNum2便是自動裝箱;而將intNum2賦值給intNum3則是自動拆箱。
八種基本數據類型: boolean byte char shrot int long float double ,所生成的變量相當於常量。
基本類型包裝類:Boolean Byte Character Short Integer Long Float Double。
自動拆箱和自動裝箱定義:
自動裝箱是將一個java定義的基本數據類型賦值給相應封裝類的變量。 拆箱與裝箱是相反的操作,自動拆箱則是將一個封裝類的變量賦值給相應基本數據類型的變量。
Object有哪些公用方法
Object是所有類的父類,任何類都默認繼承Object
clone 保護方法,實現對象的淺複製,只有實現了Cloneable接口纔可以調用該方法,否則拋出CloneNotSupportedException異常。
equals 在Object中與==是一樣的,子類一般需要重寫該方法。
hashCode 該方法用於哈希查找,重寫了equals方法一般都要重寫hashCode方法。這個方法在一些具有哈希功能的Collection中用到。
getClass final方法,獲得運行時類型
wait 使當前線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具有該對象的鎖。 wait() 方法一直等待,直到獲得鎖或者被中斷。 wait(long timeout) 設定一個超時間隔,如果在規定時間內沒有獲得鎖就返回。
調用該方法後當前線程進入睡眠狀態,直到以下事件發生
1、其他線程調用了該對象的notify方法。 2、其他線程調用了該對象的notifyAll方法。 3、其他線程調用了interrupt中斷該線程。 4、時間間隔到了。 5、此時該線程就可以被調度了,如果是被中斷的話就拋出一個InterruptedException異常。
notify 喚醒在該對象上等待的某個線程。
notifyAll 喚醒在該對象上等待的所有線程。
toString 轉換成字符串,一般子類都有重寫,否則打印句柄。
Java的四種引用,強弱軟虛,用到的場景
從JDK1.2版本開始,把對象的引用分爲四種級別,從而使程序能更加靈活的控制對象的生命週期。這四種級別由高到低依次爲:強引用、軟引用、弱引用和虛引用。
1、強引用
最普遍的一種引用方式,如String s = “abc”,變量s就是字符串“abc”的強引用,只要強引用存在,則垃圾回收器就不會回收這個對象。
2、軟引用(SoftReference)
用於描述還有用但非必須的對象,如果內存足夠,不回收,如果內存不足,則回收。一般用於實現內存敏感的高速緩存,軟引用可以和引用隊列ReferenceQueue聯合使用,如果軟引用的對象被垃圾回收,JVM就會把這個軟引用加入到與之關聯的引用隊列中。
3、弱引用(WeakReference)
弱引用和軟引用大致相同,弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。
4、虛引用(PhantomReference)
就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。 虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
虛引用與軟引用和弱引用的一個區別在於:
虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。
Hashcode的作用
http://blog.csdn.net/seu_calvin/article/details/52094115
1、HashCode的特性
(1)HashCode的存在主要是用於查找的快捷性,如Hashtable,HashMap等,HashCode經常用於確定對象的存儲地址。
(2)如果兩個對象相同,?equals方法一定返回true,並且這兩個對象的HashCode一定相同。
(3)兩個對象的HashCode相同,並不一定表示兩個對象就相同,即equals()不一定爲true,只能夠說明這兩個對象在一個散列存儲結構中。
(4)如果對象的equals方法被重寫,那麼對象的HashCode也儘量重寫。
2、HashCode作用
Java中的集合有兩類,一類是List,再有一類是Set。前者集合內的元素是有序的,元素可以重複;後者元素無序,但元素不可重複。
equals方法可用於保證元素不重複,但如果每增加一個元素就檢查一次,若集合中現在已經有1000個元素,那麼第1001個元素加入集合時,就要調用1000次equals方法。這顯然會大大降低效率。?於是,Java採用了哈希表的原理。
哈希算法也稱爲散列算法,是將數據依特定算法直接指定到一個地址上。
這樣一來,當集合要添加新的元素時,先調用這個元素的HashCode方法,就一下子能定位到它應該放置的物理位置上。
(1)如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進行任何比較了。
(2)如果這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存了。
(3)不相同的話,也就是發生了Hash key相同導致衝突的情況,那麼就在這個Hash key的地方產生一個鏈表,將所有產生相同HashCode的對象放到這個單鏈表上去,串在一起(很少出現)。
這樣一來實際調用equals方法的次數就大大降低了,幾乎只需要一兩次。
如何理解HashCode的作用:
從Object角度看,JVM每new一個Object,它都會將這個Object丟到一個Hash表中去,這樣的話,下次做Object的比較或者取這個對象的時候(讀取過程),它會根據對象的HashCode再從Hash表中取這個對象。這樣做的目的是提高取對象的效率。若HashCode相同再去調用equal。
3、HashCode實踐(如何用來查找)
HashCode是用於查找使用的,而equals是用於比較兩個對象是否相等的。
(1)例如內存中有這樣的位置
0 1 2 3 4 5 6 7
而我有個類,這個類有個字段叫ID,我要把這個類存放在以上8個位置之一,如果不用HashCode而任意存放,那麼當查找時就需要到這八個位置裏挨個去找,或者用二分法一類的算法。
但以上問題如果用HashCode就會使效率提高很多 定義我們的HashCode爲ID%8,比如我們的ID爲9,9除8的餘數爲1,那麼我們就把該類存在1這個位置,如果ID是13,求得的餘數是5,那麼我們就把該類放在5這個位置。依此類推。
(2)但是如果兩個類有相同的HashCode,例如9除以8和17除以8的餘數都是1,也就是說,我們先通過?HashCode來判斷兩個類是否存放某個桶裏,但這個桶裏可能有很多類,那麼我們就需要再通過equals在這個桶裏找到我們要的類。
請看下面這個例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class HashTest { private int i; public int getI() { return i; } public void setI( int i) { this .i = i; } public int hashCode() { return i % 10 ; } public final static void main(String[] args) { HashTest a = new HashTest(); HashTest b = new HashTest(); a.setI( 1 ); b.setI( 1 ); Set<HashTest> set = new HashSet<HashTest>(); set.add(a); set.add(b); System.out.println(a.hashCode() == b.hashCode()); System.out.println(a.equals(b)); System.out.println(set); } } |
輸出結果爲:
1
2
3
|
true False [HashTest @1 , HashTest @1 ] |
以上這個示例,我們只是重寫了HashCode方法,從上面的結果可以看出,雖然兩個對象的HashCode相等,但是實際上兩個對象並不是相等,因爲我們沒有重寫equals方法,那麼就會調用Object默認的equals方法,顯示這是兩個不同的對象。
這裏我們將生成的對象放到了HashSet中,而HashSet中只能夠存放唯一的對象,也就是相同的(適用於equals方法)的對象只會存放一個,但是這裏實際上是兩個對象ab都被放到了HashSet中,這樣HashSet就失去了他本身的意義了。
下面我們繼續重寫equals方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public class HashTest { private int i; public int getI() { return i; } public void setI( int i) { this .i = i; } public boolean equals(Object object) { if (object == null ) { return false ; } if (object == this ) { return true ; } if (!(object instanceof HashTest)) { return false ; } HashTest other = (HashTest) object; if (other.getI() == this .getI()) { return true ; } return false ; } public int hashCode() { return i % 10 ; } public final static void main(String[] args) { HashTest a = new HashTest(); HashTest b = new HashTest(); a.setI( 1 ); b.setI( 1 ); Set<HashTest> set = new HashSet<HashTest>(); set.add(a); set.add(b); System.out.println(a.hashCode() == b.hashCode()); System.out.println(a.equals(b)); System.out.println(set); } } |
輸出結果如下所示。
從結果我們可以看出,現在兩個對象就完全相等了,HashSet中也只存放了一份對象。
注意:
hashCode()只是簡單示例寫的,真正的生產換將不是這樣的
1
2
3
|
true true [HashTest @1 ] |
HashMap的hashcode的作用
hashCode的存在主要是用於查找的快捷性,如Hashtable,HashMap等,hashCode是用來在散列存儲結構中確定對象的存儲地址的。
如果兩個對象相同,就是適用於equals(java.lang.Object) 方法,那麼這兩個對象的hashCode一定要相同。
如果對象的equals方法被重寫,那麼對象的hashCode也儘量重寫,並且產生hashCode使用的對象,一定要和equals方法中使用的一致,否則就會違反上面提到的第2點。
兩個對象的hashCode相同,並不一定表示兩個對象就相同,也就是不一定適用於equals(java.lang.Object) 方法,只能夠說明這兩個對象在散列存儲結構中,如Hashtable,他們“存放在同一個籃子裏”。
什麼時候需要重寫?
一般的地方不需要重載hashCode,只有當類需要放在HashTable、HashMap、HashSet等等hash結構的集合時纔會重載hashCode,那麼爲什麼要重載hashCode呢?
要比較兩個類的內容屬性值,是否相同時候,根據hashCode 重寫規則,重寫類的 指定字段的hashCode(),equals()方法。
例如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public class EmpWorkCondition{ /** * 員工ID */ private Integer empId; /** * 員工服務總單數 */ private Integer orderSum; @Override public boolean equals(Object o) { if ( this == o) { return true ; } if (o == null || getClass() != o.getClass()) { return false ; } EmpWorkCondition that = (EmpWorkCondition) o; return Objects.equals(empId, that.empId); } @Override public int hashCode() { return Objects.hash(empId); } // 省略 getter setter } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public static void main(String[] args) { List<EmpWorkCondition> list1 = new ArrayList<EmpWorkCondition>(); EmpWorkCondition emp1 = new EmpWorkCondition(); emp1.setEmpId( 100 ); emp1.setOrderSum( 90000 ); list1.add(emp1); List<EmpWorkCondition> list2 = new ArrayList<EmpWorkCondition>(); EmpWorkCondition emp2 = new EmpWorkCondition(); emp2.setEmpId( 100 ); list2.add(emp2); System.out.println(list1.contains(emp2)); } |
輸出結果:true
上面的方法,做的事情就是,比較兩個集合中的,實體類對象屬性值,是否一致
OrderSum 不在比較範圍內,因爲沒有重寫它的,equals()和hashCode()方法
爲什麼要重載equal方法?
因爲Object的equal方法默認是兩個對象的引用的比較,意思就是指向同一內存,地址則相等,否則不相等;如果你現在需要利用對象裏面的值來判斷是否相等,則重載equal方法。
爲什麼重載hashCode方法?
一般的地方不需要重載hashCode,只有當類需要放在HashTable、HashMap、HashSet等等hash結構的集合時纔會重載hashCode,那麼爲什麼要重載hashCode呢?
如果你重寫了equals,比如說是基於對象的內容實現的,而保留hashCode的實現不變,那麼很可能某兩個對象明明是“相等”,而hashCode卻不一樣。
這樣,當你用其中的一個作爲鍵保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一個作爲鍵值去查找他們的時候,則根本找不到。
爲什麼equals()相等,hashCode就一定要相等,而hashCode相等,卻不要求equals相等?
1、因爲是按照hashCode來訪問小內存塊,所以hashCode必須相等。 2、HashMap獲取一個對象是比較key的hashCode相等和equal爲true。
之所以hashCode相等,卻可以equal不等,就比如ObjectA和ObjectB他們都有屬性name,那麼hashCode都以name計算,所以hashCode一樣,但是兩個對象屬於不同類型,所以equal爲false。
爲什麼需要hashCode?
1、通過hashCode可以很快的查到小內存塊。 2、通過hashCode比較比equal方法快,當get時先比較hashCode,如果hashCode不同,直接返回false。
ArrayList、LinkedList、Vector的區別
List的三個子類的特點
ArrayList:
- 底層數據結構是數組,查詢快,增刪慢。
- 線程不安全,效率高。
Vector:
- 底層數據結構是數組,查詢快,增刪慢。
- 線程安全,效率低。
- Vector相對ArrayList查詢慢(線程安全的)。
- Vector相對LinkedList增刪慢(數組結構)。
LinkedList
- 底層數據結構是鏈表,查詢慢,增刪快。
- 線程不安全,效率高。
Vector和ArrayList的區別
- Vector是線程安全的,效率低。
- ArrayList是線程不安全的,效率高。
- 共同點:底層數據結構都是數組實現的,查詢快,增刪慢。
ArrayList和LinkedList的區別
- ArrayList底層是數組結果,查詢和修改快。
- LinkedList底層是鏈表結構的,增和刪比較快,查詢和修改比較慢。
共同點:都是線程不安全的
List有三個子類使用
- 查詢多用ArrayList。
- 增刪多用LinkedList。
- 如果都多ArrayList。
String、StringBuffer與StringBuilder的區別
String:適用於少量的字符串操作的情況。 StringBuilder:適用於單線程下在字符緩衝區進行大量操作的情況。 StringBuffer:適用多線程下在字符緩衝區進行大量操作的情況。 StringBuilder:是線程不安全的,而StringBuffer是線程安全的。
這三個類之間的區別主要是在兩個方面,即運行速度和線程安全這兩方面。 首先說運行速度,或者說是執行速度,在這方面運行速度快慢爲:StringBuilder > StringBuffer > String。
String最慢的原因
String爲字符串常量,而StringBuilder和StringBuffer均爲字符串變量,即String對象一旦創建之後該對象是不可更改的,但後兩者的對象是變量,是可以更改的。
再來說線程安全
在線程安全上,StringBuilder是線程不安全的,而StringBuffer是線程安全的。
如果一個StringBuffer對象在字符串緩衝區被多個線程使用時,StringBuffer中很多方法可以帶有synchronized關鍵字,所以可以保證線程是安全的,但StringBuilder的方法則沒有該關鍵字,所以不能保證線程安全,有可能會出現一些錯誤的操作。所以如果要進行的操作是多線程的,那麼就要使用StringBuffer,但是在單線程的情況下,還是建議使用速度比較快的StringBuilder。
Map、Set、List、Queue、Stack的特點與用法
Map
- Map是鍵值對,鍵Key是唯一不能重複的,一個鍵對應一個值,值可以重複。
- TreeMap可以保證順序。
- HashMap不保證順序,即爲無序的。
- Map中可以將Key和Value單獨抽取出來,其中KeySet()方法可以將所有的keys抽取正一個Set。而Values()方法可以將map中所有的values抽取成一個集合。
Set
- 不包含重複元素的集合,set中最多包含一個null元素。
- 只能用Lterator實現單項遍歷,Set中沒有同步方法。
List
- 有序的可重複集合。
- 可以在任意位置增加刪除元素。
- 用Iterator實現單向遍歷,也可用ListIterator實現雙向遍歷。
Queue
- Queue遵從先進先出原則。
- 使用時儘量避免add()和remove()方法,而是使用offer()來添加元素,使用poll()來移除元素,它的優點是可以通過返回值來判斷是否成功。
- LinkedList實現了Queue接口。
- Queue通常不允許插入null元素。
Stack
- Stack遵從後進先出原則。
- Stack繼承自Vector。
- 它通過五個操作對類Vector進行擴展,允許將向量視爲堆棧,它提供了通常的push和pop操作,以及取堆棧頂點的peek()方法、測試堆棧是否爲空的empty方法等。
用法
- 如果涉及堆棧,隊列等操作,建議使用List。
- 對於快速插入和刪除元素的,建議使用LinkedList。
- 如果需要快速隨機訪問元素的,建議使用ArrayList。
更爲精煉的總結
Collection 是對象集合, Collection 有兩個子接口 List 和 Set
List 可以通過下標 (1,2..) 來取得值,值可以重複。 Set 只能通過遊標來取值,並且值是不能重複的。
ArrayList , Vector , LinkedList 是 List 的實現類
- ArrayList 是線程不安全的, Vector 是線程安全的,這兩個類底層都是由數組實現的。
- LinkedList 是線程不安全的,底層是由鏈表實現的。
Map 是鍵值對集合
- HashTable 和 HashMap 是 Map 的實現類。
- HashTable 是線程安全的,不能存儲 null 值。
- HashMap 不是線程安全的,可以存儲 null 值。
Stack類:繼承自Vector,實現一個後進先出的棧。提供了幾個基本方法,push、pop、peak、empty、search等。
Queue接口:提供了幾個基本方法,offer、poll、peek等。已知實現類有LinkedList、PriorityQueue等。
HashMap和HashTable的區別
https://segmentfault.com/a/1190000008101567
Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現,它們都是集合中將數據無序存放的。
1、hashMap去掉了HashTable?的contains方法,但是加上了containsValue()和containsKey()方法
HashTable Synchronize同步的,線程安全,HashMap不允許空鍵值爲空?,效率低。 HashMap 非Synchronize線程同步的,線程不安全,HashMap允許空鍵值爲空?,效率高。 Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現,它們都是集合中將數據無序存放的。
Hashtable的方法是同步的,HashMap未經同步,所以在多線程場合要手動同步HashMap這個區別就像Vector和ArrayList一樣。
查看Hashtable的源代碼就可以發現,除構造函數外,Hashtable的所有 public 方法聲明中都有 synchronized 關鍵字,而HashMap的源代碼中則連 synchronized 的影子都沒有,當然,註釋除外。
2、Hashtable不允許 null 值(key 和 value 都不可以),HashMap允許 null 值(key和value都可以)。
3、兩者的遍歷方式大同小異,Hashtable僅僅比HashMap多一個elements方法。
1
2
3
4
5
6
7
|
Hashtable table = new Hashtable(); table.put( "key" , "value" ); Enumeration em = table.elements(); while (em.hasMoreElements()) { String obj = (String) em.nextElement(); System.out.println(obj); } |
4、HashTable使用Enumeration,HashMap使用Iterator
從內部機制實現上的區別如下:
- 哈希值的使用不同,Hashtable直接使用對象的hashCode
1
2
|
int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF ) % tab.length; |
而HashMap重新計算hash值,而且用與代替求模:
1
2
3
4
5
6
7
8
9
10
11
12
|
int hash = hash(k); int i = indexFor(hash, table.length); static int hash(Object x) { int h = x.hashCode(); h += ~(h << 9 ); h ^= (h >>> 14 ); h += (h << 4 ); h ^= (h >>> 10 ); return h; } |
1
2
|
static int indexFor( int h, int length) { return h & (length- 1 ); |
- Hashtable中hash數組默認大小是11,增加的方式是 old*2+1。HashMap中hash數組的默認大小是16,而且一定是2的指數。
JDK7與JDK8中HashMap的實現
JDK7中的HashMap
HashMap底層維護一個數組,數組中的每一項都是一個Entry。
1
|
transient Entry<K,V>[] table; |
我們向 HashMap 中所放置的對象實際上是存儲在該數組當中。 而Map中的key,value則以Entry的形式存放在數組中。
1
2
3
4
5
|
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; |
總結一下map.put後的過程:
當向 HashMap 中 put 一對鍵值時,它會根據 key的 hashCode 值計算出一個位置, 該位置就是此對象準備往數組中存放的位置。
如果該位置沒有對象存在,就將此對象直接放進數組當中;如果該位置已經有對象存在了,則順着此存在的對象的鏈開始尋找(爲了判斷是否是否值相同,map不允許<key,value>鍵值對重複), 如果此鏈上有對象的話,再去使用 equals方法進行比較,如果對此鏈上的每個對象的 equals 方法比較都爲 false,則將該對象放到數組當中,然後將數組中該位置以前存在的那個對象鏈接到此對象的後面。
JDK8中的HashMap
JDK8中採用的是位桶+鏈表/紅黑樹(有關紅黑樹請查看紅黑樹)的方式,也是非線程安全的。當某個位桶的鏈表的長度達到某個閥值的時候,這個鏈表就將轉換成紅黑樹。
JDK8中,當同一個hash值的節點數不小於8時,將不再以單鏈表的形式存儲了,會被調整成一顆紅黑樹(上圖中null節點沒畫)。這就是JDK7與JDK8中HashMap實現的最大區別。
接下來,我們來看下JDK8中HashMap的源碼實現。
JDK中Entry的名字變成了Node,原因是和紅黑樹的實現TreeNode相關聯。
transient Node<K,V>[] table;
當衝突節點數不小於8-1時,轉換成紅黑樹。
static final int TREEIFY_THRESHOLD = 8;
HashMap和ConcurrentHashMap的區別,HashMap的底層源碼
爲了線程安全從ConcurrentHashMap代碼中可以看出,它引入了一個“分段鎖”的概念,具體可以理解爲把一個大的Map拆分成N個小的HashTable,根據key.hashCode()來決定把key放到哪個HashTable中。
Hashmap本質是數組加鏈表。根據key取得hash值,然後計算出數組下標,如果多個key對應到同一個下標,就用鏈表串起來,新插入的在前面。
ConcurrentHashMap:在hashMap的基礎上,ConcurrentHashMap將數據分爲多個segment,默認16個(concurrency level),然後每次操作對一個segment加鎖,避免多線程鎖的機率,提高併發效率。
總結
JDK6,7中的ConcurrentHashmap主要使用Segment來實現減小鎖粒度,把HashMap分割成若干個Segment,在put的時候需要鎖住Segment,get時候不加鎖,使用volatile來保證可見性,當要統計全局時(比如size),首先會嘗試多次計算modcount來確定,這幾次嘗試中,是否有其他線程進行了修改操作,如果沒有,則直接返回size。如果有,則需要依次鎖住所有的Segment來計算。
jdk7中ConcurrentHashmap中,當長度過長碰撞會很頻繁,鏈表的增改刪查操作都會消耗很長的時間,影響性能。
jdk8 中完全重寫了concurrentHashmap,代碼量從原來的1000多行變成了 6000多 行,實現上也和原來的分段式存儲有很大的區別。
JDK8中採用的是位桶+鏈表/紅黑樹(有關紅黑樹請查看紅黑樹)的方式,也是非線程安全的。當某個位桶的鏈表的長度達到某個閥值的時候,這個鏈表就將轉換成紅黑樹。
JDK8中,當同一個hash值的節點數不小於8時,將不再以單鏈表的形式存儲了,會被調整成一顆紅黑樹(上圖中null節點沒畫)。這就是JDK7與JDK8中HashMap實現的最大區別。
主要設計上的變化有以下幾點
1.jdk8不採用segment而採用node,鎖住node來實現減小鎖粒度。 2.設計了MOVED狀態 當resize的中過程中 線程2還在put數據,線程2會幫助resize。 3.使用3個CAS操作來確保node的一些操作的原子性,這種方式代替了鎖。 4.sizeCtl的不同值來代表不同含義,起到了控制的作用。
至於爲什麼JDK8中使用synchronized而不是ReentrantLock,我猜是因爲JDK8中對synchronized有了足夠的優化吧。
ConcurrentHashMap能完全替代HashTable嗎
hashTable雖然性能上不如ConcurrentHashMap,但並不能完全被取代,兩者的迭代器的一致性不同的,hash table的迭代器是強一致性的,而concurrenthashmap是弱一致的。
ConcurrentHashMap的get,clear,iterator 都是弱一致性的。 Doug Lea 也將這個判斷留給用戶自己決定是否使用ConcurrentHashMap。
ConcurrentHashMap與HashTable都可以用於多線程的環境,但是當Hashtable的大小增加到一定的時候,性能會急劇下降,因爲迭代時需要被鎖定很長的時間。因爲ConcurrentHashMap引入了分割(segmentation),不論它變得多麼大,僅僅需要鎖定map的某個部分,而其它的線程不需要等到迭代完成才能訪問map。簡而言之,在迭代的過程中,ConcurrentHashMap僅僅鎖定map的某個部分,而Hashtable則會鎖定整個map。
那麼既然ConcurrentHashMap那麼優秀,爲什麼還要有Hashtable的存在呢?ConcurrentHashMap能完全替代HashTable嗎?
HashTable雖然性能上不如ConcurrentHashMap,但並不能完全被取代,兩者的迭代器的一致性不同的,HashTable的迭代器是強一致性的,而ConcurrentHashMap是弱一致的。 ConcurrentHashMap的get,clear,iterator 都是弱一致性的。 Doug Lea 也將這個判斷留給用戶自己決定是否使用ConcurrentHashMap。
那麼什麼是強一致性和弱一致性呢?
get方法是弱一致的,是什麼含義?可能你期望往ConcurrentHashMap底層數據結構中加入一個元素後,立馬能對get可見,但ConcurrentHashMap並不能如你所願。換句話說,put操作將一個元素加入到底層數據結構後,get可能在某段時間內還看不到這個元素,若不考慮內存模型,單從代碼邏輯上來看,卻是應該可以看得到的。
下面將結合代碼和java內存模型相關內容來分析下put/get方法。put方法我們只需關注Segment#put,get方法只需關注Segment#get,在繼續之前,先要說明一下Segment裏有兩個volatile變量:count和table;HashEntry裏有一個volatile變量:value。
總結
ConcurrentHashMap的弱一致性主要是爲了提升效率,是一致性與效率之間的一種權衡。要成爲強一致性,就得到處使用鎖,甚至是全局鎖,這就與Hashtable和同步的HashMap一樣了。
爲什麼HashMap是線程不安全的
HashMap 在併發執行 put 操作時會引起死循環,導致 CPU 利用率接近100%。因爲多線程會導致 HashMap 的 Node 鏈表形成環形數據結構,一旦形成環形數據結構,Node 的 next 節點永遠不爲空,就會在獲取 Node 時產生死循環。
如何線程安全的使用HashMap
瞭解了 HashMap 爲什麼線程不安全,那現在看看如何線程安全的使用 HashMap。這個無非就是以下三種方式:
Hashtable ConcurrentHashMap Synchronized Map
Hashtable
例子
//Hashtable
Map<String, String> hashtable = new Hashtable<>();
//synchronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());
//ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
Hashtable
先稍微吐槽一下,爲啥命名不是 HashTable 啊,看着好難受不管了就裝作它叫HashTable 吧。這貨已經不常用了,就簡單說說吧。HashTable 源碼中是使用?synchronized?來保證線程安全的,比如下面的 get 方法和 put 方法:
public synchronized V get(Object key) {
// 省略實現
}
public synchronized V put(K key, V value) {
// 省略實現
}
所以當一個線程訪問 HashTable 的同步方法時,其他線程如果也要訪問同步方法,會被阻塞住。舉個例子,當一個線程使用 put 方法時,另一個線程不但不可以使用 put 方法,連 get 方法都不可以,好霸道啊!!!so~~,效率很低,現在基本不會選擇它了。
ConcurrentHashMap
ConcurrentHashMap 於 Java 7 的,和8有區別,在8中 CHM 摒棄了 Segment(鎖段)的概念,而是啓用了一種全新的方式實現,利用 CAS 算法,有時間會重新總結一下。
SynchronizedMap
synchronizedMap() 方法後會返回一個 SynchronizedMap 類的對象,而在 SynchronizedMap 類中使用了 synchronized 同步關鍵字來保證對 Map 的操作是線程安全的。
性能對比
這是要靠數據說話的時代,所以不能只靠嘴說 CHM 快,它就快了。寫個測試用例,實際的比較一下這三種方式的效率(源碼來源),下面的代碼分別通過三種方式創建 Map 對象,使用 ExecutorService 來併發運行5個線程,每個線程添加/獲取500K個元素。
Test started for: class java.util.Hashtable
2500K entried added/retrieved in 2018 ms
2500K entried added/retrieved in 1746 ms
2500K entried added/retrieved in 1806 ms
2500K entried added/retrieved in 1801 ms
2500K entried added/retrieved in 1804 ms
For class java.util.Hashtable the average time is 1835 ms
Test started for: class java.util.Collections$SynchronizedMap
2500K entried added/retrieved in 3041 ms
2500K entried added/retrieved in 1690 ms
2500K entried added/retrieved in 1740 ms
2500K entried added/retrieved in 1649 ms
2500K entried added/retrieved in 1696 ms
For class java.util.Collections$SynchronizedMap the average time is 1963 ms
Test started for: class java.util.concurrent.ConcurrentHashMap
2500K entried added/retrieved in 738 ms
2500K entried added/retrieved in 696 ms
2500K entried added/retrieved in 548 ms
2500K entried added/retrieved in 1447 ms
2500K entried added/retrieved in 531 ms
For class java.util.concurrent.ConcurrentHashMap the average time is 792 ms
ConcurrentHashMap 性能是明顯優於 Hashtable 和 SynchronizedMap 的,CHM 花費的時間比前兩個的一半還少。
多併發情況下HashMap是否還會產生死循環
今天本來想看下了ConcurrentHashMap的源碼,ConcurrentHashMap是Java 5中支持高併發、高吞吐量的線程安全HashMap實現。
在看很多博客在介紹ConcurrentHashMap之前,都說HashMap適用於單線程訪問,這是因爲HashMap的所有方法都沒有進行鎖同步,因此是線程不安全的,不僅如此,當多線程訪問的時候還容易產生死循環。
雖然自己在前幾天的時候看過HashMap的源碼,感覺思路啥啥的都還清楚,對於多線程訪問只知道HashMap是線程不安全的,但是不知道HashMap在多線程併發的情況下會產生死循環呢,爲什麼會產生,何種情況下才會產生死循環呢???
《Java困惑》:多併發情況下HashMap是否還會產生死循環。
既然會產生死循環,爲什麼併發情況下,還是用ConcurrentHashMap。 jdk 好像有,但是Jdk8 已經修復了這個問題。
TreeMap、HashMap、LindedHashMap的區別
LinkedHashMap可以保證HashMap集合有序,存入的順序和取出的順序一致。
TreeMap實現SortMap接口,能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator遍歷TreeMap時,得到的記錄是排過序的。
HashMap不保證順序,即爲無序的,具有很快的訪問速度。 HashMap最多隻允許一條記錄的鍵爲Null;允許多條記錄的值爲 Null。 HashMap不支持線程的同步。
我們在開發的過程中使用HashMap比較多,在Map中在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。
但如果您要按自然順序或自定義順序遍歷鍵,那麼TreeMap會更好。
如果需要輸出的順序和輸入的相同,那麼用LinkedHashMap 可以實現,它還可以按讀取順序來排列。
Collection包結構,與Collections的區別
Collection 是集合類的上級接口,子接口主要有Set、List 、Map。
Collecions 是針對集合類的一個幫助類, 提供了操作集合的工具方法,一系列靜態方法實現對各種集合的搜索、排序線性、線程安全化等操作。
例如
Map<String, Object> map4 = Collections.synchronizedMap(new HashMap<String, Object>()); 線程安全 的HashMap
Collections.sort(List<T> list, Comparator<? super T> c); 排序 List
Collection
Collection 是單列集合
List
元素是有序的、可重複。 有序的 collection,可以對列表中每個元素的插入位置進行精確地控制。 可以根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。 可存放重複元素,元素存取是有序的。
List接口中常用類
Vector:線程安全,但速度慢,已被ArrayList替代。底層數據結構是數組結構。 ArrayList:線程不安全,查詢速度快。底層數據結構是數組結構。 LinkedList:線程不安全。增刪速度快。底層數據結構是列表結構。
Set
Set接口中常用的類
Set(集) 元素無序的、不可重複。 取出元素的方法只有迭代器。不可以存放重複元素,元素存取是無序的。
HashSet:線程不安全,存取速度快。它是如何保證元素唯一性的呢?依賴的是元素的hashCode方法和euqals方法。 TreeSet:線程不安全,可以對Set集合中的元素進行排序。它的排序是如何進行的呢?通過compareTo或者compare方法中的來保證元素的唯一性。元素是以二叉樹的形式存放的。
Map
map是一個雙列集合
Hashtable:線程安全,速度快。底層是哈希表數據結構。是同步的。不允許null作爲鍵,null作爲值。
Properties:用於配置文件的定義和操作,使用頻率非常高,同時鍵和值都是字符串。是集合中可以和IO技術相結合的對象。
HashMap:線程不安全,速度慢。底層也是哈希表數據結構。是不同步的。允許null作爲鍵,null作爲值,替代了Hashtable。
LinkedHashMap: 可以保證HashMap集合有序。存入的順序和取出的順序一致。
TreeMap:可以用來對Map集合中的鍵進行排序
try?catch?finally,try裏有return,finally還執行麼
肯定會執行。finally{}塊的代碼。 只有在try{}塊中包含遇到System.exit(0)。 之類的導致Java虛擬機直接退出的語句纔會不執行。
當程序執行try{}遇到return時,程序會先執行return語句,但並不會立即返回——也就是把return語句要做的一切事情都準備好,也就是在將要返回、但並未返回的時候,程序把執行流程轉去執行finally塊,當finally塊執行完成後就直接返回剛纔return語句已經準備好的結果。
Excption與Error包結構。OOM你遇到過哪些情況,SO F你遇到過哪些情況
Throwable是 Java 語言中所有錯誤或異常的超類。 Throwable包含兩個子類: Error 和 Exception 。它們通常用於指示發生了異常情況。 Throwable包含了其線程創建時線程執行堆棧的快照,它提供了printStackTrace()等接口用於獲取堆棧跟蹤數據等信息。
Java將可拋出(Throwable)的結構分爲三種類型:
被檢查的異常(Checked Exception)。 運行時異常(RuntimeException)。 錯誤(Error)。
運行時異常RuntimeException
定義 : RuntimeException及其子類都被稱爲運行時異常。 特點 : Java編譯器不會檢查它 也就是說,當程序中可能出現這類異常時,倘若既”沒有通過throws聲明拋出它”,也”沒有用try-catch語句捕獲它”,還是會編譯通過。
例如,除數爲零時產生的ArithmeticException異常,數組越界時產生的IndexOutOfBoundsException異常,fail-fail機制產生的ConcurrentModificationException異常等,都屬於運行時異常。
堆內存溢出 OutOfMemoryError(OOM)
除了程序計數器外,虛擬機內存的其他幾個運行時區域都有發生OutOfMemoryError(OOM)異常的可能。
Java Heap 溢出。 一般的異常信息:java.lang.OutOfMemoryError:Java heap spacess。 java堆用於存儲對象實例,我們只要不斷的創建對象,並且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,就會在對象數量達到最大堆容量限制後產生內存溢出異常。
堆棧溢出 StackOverflow (SOF)
StackOverflowError 的定義: 當應用程序遞歸太深而發生堆棧溢出時,拋出該錯誤。 因爲棧一般默認爲1-2m,一旦出現死循環或者是大量的遞歸調用,在不斷的壓棧過程中,造成棧容量超過1m而導致溢出。
棧溢出的原因:
遞歸調用。 大量循環或死循環。 全局變量是否過多。 數組、List、map數據過大。
Java(OOP)面向對象的三個特徵與含義
封裝(高內聚低耦合 –>解耦)
封裝是指將某事物的屬性和行爲包裝到對象中,這個對象只對外公佈需要公開的屬性和行爲,而這個公佈也是可以有選擇性的公佈給其它對象。在java中能使用private、protected、public三種修飾符或不用(即默認defalut)對外部對象訪問該對象的屬性和行爲進行限制。
java的繼承(重用父類的代碼)
繼承是子對象可以繼承父對象的屬性和行爲,亦即父對象擁有的屬性和行爲,其子對象也就擁有了這些屬性和行爲。
java中的多態(父類引用指向子類對象)
多態是指父對象中的同一個行爲能在其多個子對象中有不同的表現。
有兩種多態的機制:編譯時多態、運行時多態。
1、方法的重載:重載是指同一類中有多個同名的方法,但這些方法有着不同的參數。,因此在編譯時就可以確定到底調用哪個方法,它是一種編譯時多態。 2、方法的重寫:子類可以覆蓋父類的方法,因此同樣的方法會在父類中與子類中有着不同的表現形式。
Override和Overload的含義去區別
重載 Overload方法名相同,參數列表不同(個數、順序、類型不同)與返回類型無關。 重寫 Override 覆蓋。 將父類的方法覆蓋。 重寫方法重寫:方法名相同,訪問修飾符只能大於被重寫的方法訪問修飾符,方法簽名個數,順序個數類型相同。
Override(重寫)
- 方法名、參數、返回值相同。
- 子類方法不能縮小父類方法的訪問權限。
- 子類方法不能拋出比父類方法更多的異常(但子類方法可以不拋出異常)。
- 存在於父類和子類之間。
- 方法被定義爲final不能被重寫。
Overload(重載)
- 參數類型、個數、順序至少有一個不相同。
- 不能重載只有返回值不同的方法名。
- 存在於父類和子類、同類中。
而重載的規則
1、必須具有不同的參數列表。 2、可以有不同的返回類型,只要參數列表不同就可以了。 3、可以有不同的訪問修飾符。 4、可以拋出不同的異常。
重寫方法的規則
1、參數列表必須完全與被重寫的方法相同,否則不能稱其爲重寫而是重載。 2、返回的類型必須一直與被重寫的方法的返回類型相同,否則不能稱其爲重寫而是重載。 3、訪問修飾符的限制一定要大於被重寫方法的訪問修飾符(public>protected>default>private)。 4、重寫方法一定不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的檢查型異常。
例如: 父類的一個方法申明瞭一個檢查異常IOException,在重寫這個方法是就不能拋出Exception,只能拋出IOException的子類異常,可以拋出非檢查異常。
Interface與abstract類的區別
Interface 只能有成員常量,只能是方法的聲明。 Abstract class可以有成員變量,可以聲明普通方法和抽象方法。
interface是接口,所有的方法都是抽象方法,成員變量是默認的public static final 類型。接口不能實例化自己。
abstract class是抽象類,至少包含一個抽象方法的累叫抽象類,抽象類不能被自身實例化,並用abstract關鍵字來修飾。
Static?class?與non?static?class的區別
static class(內部靜態類)
1、用static修飾的是內部類,此時這個內部類變爲靜態內部類;對測試有用。 2、內部靜態類不需要有指向外部類的引用。 3、靜態類只能訪問外部類的靜態成員,不能訪問外部類的非靜態成員。
non static class(非靜態內部類)
1、非靜態內部類需要持有對外部類的引用。 2、非靜態內部類能夠訪問外部類的靜態和非靜態成員。 3、一個非靜態內部類不能脫離外部類實體被創建。 4、一個非靜態內部類可以訪問外部類的數據和方法。
java多態的實現原理
foreach與正常for循環效率對比
用for循環arrayList 10萬次花費時間:5毫秒。 用foreach循環arrayList 10萬次花費時間:7毫秒。 用for循環linkList 10萬次花費時間:4481毫秒。 用foreach循環linkList 10萬次花費時間:5毫秒。
循環ArrayList時,普通for循環比foreach循環花費的時間要少一點。 循環LinkList時,普通for循環比foreach循環花費的時間要多很多。
當我將循環次數提升到一百萬次的時候,循環ArrayList,普通for循環還是比foreach要快一點;但是普通for循環在循環LinkList時,程序直接卡死。
ArrayList:ArrayList是採用數組的形式保存對象的,這種方式將對象放在連續的內存塊中,所以插入和刪除時比較麻煩,查詢比較方便。
LinkList:LinkList是將對象放在獨立的空間中,而且每個空間中還保存下一個空間的索引,也就是數據結構中的鏈表結構,插入和刪除比較方便,但是查找很麻煩,要從第一個開始遍歷。
結論:
需要循環數組結構的數據時,建議使用普通for循環,因爲for循環採用下標訪問,對於數組結構的數據來說,採用下標訪問比較好。
需要循環鏈表結構的數據時,一定不要使用普通for循環,這種做法很糟糕,數據量大的時候有可能會導致系統崩潰。
Java?IO與NIO
NIO是爲了彌補IO操作的不足而誕生的,NIO的一些新特性有:非阻塞I/O,選擇器,緩衝以及管道。管道(Channel),緩衝(Buffer) ,選擇器( Selector)是其主要特徵。
概念解釋
Channel——管道實際上就像傳統IO中的流,到任何目的地(或來自任何地方)的所有數據都必須通過一個 Channel 對象。一個 Buffer 實質上是一個容器對象。
每一種基本 Java 類型都有一種緩衝區類型:
ByteBuffer——byte
CharBuffer——char
ShortBuffer——short
IntBuffer——int
LongBuffer——long
FloatBuffer——float
DoubleBuffer——double
Selector——選擇器用於監聽多個管道的事件,使用傳統的阻塞IO時我們可以方便的知道什麼時候可以進行讀寫,而使用非阻塞通道,我們需要一些方法來知道什麼時候通道準備好了,選擇器正是爲這個需要而誕生的。
NIO和傳統的IO有什麼區別呢?
IO是面向流的,NIO是面向塊(緩衝區)的。
IO面向流的操作一次一個字節地處理數據。一個輸入流產生一個字節的數據,一個輸出流消費一個字節的數據。,導致了數據的讀取和寫入效率不佳。
NIO面向塊的操作在一步中產生或者消費一個數據塊。按塊處理數據比按(流式的)字節處理數據要快得多,同時數據讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。通俗來說,NIO採取了“預讀”的方式,當你讀取某一部分數據時,他就會猜測你下一步可能會讀取的數據而預先緩衝下來。
IO是阻塞的,NIO是非阻塞的
對於傳統的IO,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再幹任何事情了。
而對於NIO,使用一個線程發送讀取數據請求,沒有得到響應之前,線程是空閒的,此時線程可以去執行別的任務,而不是像IO中那樣只能等待響應完成。
NIO和IO適用場景
NIO是爲彌補傳統IO的不足而誕生的,但是尺有所短寸有所長,NIO也有缺點,因爲NIO是面向緩衝區的操作,每一次的數據處理都是對緩衝區進行的,那麼就會有一個問題,在數據處理之前必須要判斷緩衝區的數據是否完整或者已經讀取完畢,如果沒有,假設數據只讀取了一部分,那麼對不完整的數據處理沒有任何意義。所以每次數據處理之前都要檢測緩衝區數據。
那麼NIO和IO各適用的場景是什麼呢?
如果需要管理同時打開的成千上萬個連接,這些連接每次只是發送少量的數據,例如聊天服務器,這時候用NIO處理數據可能是個很好的選擇。
而如果只有少量的連接,而這些連接每次要發送大量的數據,這時候傳統的IO更合適。使用哪種處理數據,需要在數據的響應等待時間和檢查緩衝區數據的時間上作比較來權衡選擇。
通俗解釋,最後,對於NIO和傳統IO
有一個網友講的生動的例子:
以前的流總是堵塞的,一個線程只要對它進行操作,其它操作就會被堵塞,也就相當於水管沒有閥門,你伸手接水的時候,不管水到了沒有,你就都只能耗在接水(流)上。
nio的Channel的加入,相當於增加了水龍頭(有閥門),雖然一個時刻也只能接一個水管的水,但依賴輪換策略,在水量不大的時候,各個水管裏流出來的水,都可以得到妥
善接納,這個關鍵之處就是增加了一個接水工,也就是Selector,他負責協調,也就是看哪根水管有水了的話,在當前水管的水接到一定程度的時候,就切換一下:臨時關上當
前水龍頭,試着打開另一個水龍頭(看看有沒有水)。
當其他人需要用水的時候,不是直接去接水,而是事前提了一個水桶給接水工,這個水桶就是Buffer。也就是,其他人雖然也可能要等,但不會在現場等,而是回家等,可以做
其它事去,水接滿了,接水工會通知他們。
這其實也是非常接近當前社會分工細化的現實,也是統分利用現有資源達到併發效果的一種很經濟的手段,而不是動不動就來個並行處理,雖然那樣是最簡單的,但也是最浪費資源的方式。
java反射的作用於原理
什麼是Java的反射呢?
Java 反射是可以讓我們在運行時,通過一個類的Class對象來獲取它獲取類的方法、屬性、父類、接口等類的內部信息的機制。
這種動態獲取信息以及動態調用對象的方法的功能稱爲JAVA的反射。
反射的作用?
反射就是:在任意一個方法裏:
1.如果我知道一個類的名稱/或者它的一個實例對象, 我就能把這個類的所有方法和變量的信息找出來(方法名,變量名,方法,修飾符,類型,方法參數等等所有信息)
2.如果我還明確知道這個類裏某個變量的名稱,我還能得到這個變量當前的值。
3.當然,如果我明確知道這個類裏的某個方法名+參數個數類型,我還能通過傳遞參數來運行那個類裏的那個方法。
反射機制主要提供了以下功能:
- 在運行時判斷任意一個對象所屬的類。
- 在運行時構造任意一個類的對象。
- 在運行時判斷任意一個類所具有的成員變量和方法。
- 在運行時調用任意一個對象的方法。
- 生成動態代理。
反射的原理?
JAVA語言編譯之後會生成一個.class文件,反射就是通過字節碼文件找到某一個類、類中的方法以及屬性等。
反射的實現API有哪些?
反射的實現主要藉助以下四個類:
Class:類的對象
Constructor:類的構造方法
Field:類中的屬性對象
Method:類中的方法對象
反射的實例
泛型常用特點
List<String>能否轉爲List<Object>
不可以強轉類型的
這個問題涉及到了,範型向上轉型 和 範型向下轉型問題。 List向上轉換至List(等價於List)會丟失String類的身份(String類型的特有接口)。 當需要由List向下轉型時,你的程序必須明確的知道將對象轉換成何種具體類型,不然這將是不安全的操作。
如果要強轉類型,Json 序列化轉型
List<String> str = new ArrayList<String>();
List<Object> obj= JSONObject.parseArray(JSONObject.toJSONString(str));
或者遍歷,或者克隆,但是取出來就是(Object)了,需要強轉,String 因爲類型丟了。
解析XML的幾種方式的原理與特點:DOM、SAX
Android中三種常用解析XML的方式(DOM、SAX、PULL)簡介及區別。
http://blog.csdn.net/cangchen/article/details/44034799
xml解析的兩種基本方式:DOM和SAX的區別是?
DOM: document object model。
SAX: simple api for xml 。
dom一次性把xml文件全部加載到內存中簡歷一個結構一摸一樣的樹, 效率低。 SAX解析器的優點是解析速度快,佔用內存少,效率高。
DOM在內存中以樹形結構存放,因此檢索和更新效率會更高。但是對於特別大的文檔,解析和加載整個文檔將會很耗資源。
DOM,它是生成一個樹,有了樹以後你搜索、查找都可以做。 SAX,它是基於流的,就是解析器從頭到尾解析一遍xml文件,解析完了以後你不過想再查找重新解析。 sax解析器核心是事件處理機制。例如解析器發現一個標記的開始標記時,將所發現的數據會封裝爲一個標記開始事件,並把這個報告給事件處理器。
平時工作中,xml解析你是使用什麼?
JDOM
DOM4J
Java1.7與1.8,1.9,10 新特性
1.5
- 自動裝箱與拆箱
- 枚舉(常用來設計單例模式)
- 靜態導入
- 可變參數
- 內省
1.6
- Web服務元數據
- 腳本語言支持
- JTable的排序和過濾
- 更簡單,更強大的JAX-WS
- 輕量級Http Server
- 嵌入式數據庫 Derby
1.7
- switch中可以使用字串了
- 運用List tempList = new ArrayList<>(); 即泛型實例化類型自動推斷
- 語法上支持集合,而不一定是數組
- 新增一些取環境信息的工具方法
- Boolean類型反轉,空指針安全,參與位運算
- 兩個char間的equals
- 安全的加減乘除
- map集合支持併發請求,且可以寫成 Map map = {name:”xxx”,age:18};
1.8
- 允許在接口中有默認方法實現
- Lambda表達式
- 函數式接口
- 方法和構造函數引用
- Lambda的範圍
- 內置函數式接口
- Streams
- Parallel Streams
- Map
- 時間日期API
- Annotations
1.9
- Jigsaw 項目;模塊化源碼
- 簡化進程API
- 輕量級 JSON API
- 錢和貨幣的API
- 改善鎖爭用機制
- 代碼分段緩存
- 智能Java編譯, 第二階段
- HTTP 2.0客戶端
- Kulla計劃: Java的REPL實現
10
- 本地變量類型推斷
- 統一JDK倉庫
- 垃圾回收器接口
- G1的並行Full GC
- 應用程序類數據共享
- ThreadLocal握手機制
設計模式:單例、工廠、適配器、責任鏈、觀察者等等
【示例】設計模式——單例模式、工廠模式、代理模式、觀察者模式、裝飾器模式
菜鳥教程-設計模式
什麼是設計模式
設計模式是一種解決方案,用於解決在軟件設計中普遍存在的問題,是前輩們對之前軟件設計中反覆出現的問題的一個總結。
我們學設計模式,是爲了學習如何合理的組織我們的代碼,如何解耦,如何真正的達到對修改封閉對擴展開放的效果,而不是去背誦那些類的繼承模式,然後自己記不住,回過頭來就罵設計模式把你的代碼搞複雜了,要反設計模式。
設計模式的六大原則
- 開閉原則:實現熱插拔,提高擴展性。
- 里氏代換原則:實現抽象的規範,實現子父類互相替換;
- 依賴倒轉原則:針對接口編程,實現開閉原則的基礎;
- 接口隔離原則:降低耦合度,接口單獨設計,互相隔離;
- 迪米特法則,又稱不知道原則:功能模塊儘量獨立;
- 合成複用原則:儘量使用聚合,組合,而不是繼承;
1、開閉原則(Open Close Principle)
開閉原則的意思是:對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。簡言之,是爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,後面的具體設計中我們會提到這點。
2、里氏代換原則(Liskov Substitution Principle)
里氏代換原則是面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。LSP 是繼承複用的基石,只有當派生類可以替換掉基類,且軟件單位的功能不受到影響時,基類才能真正被複用,而派生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對開閉原則的補充。實現開閉原則的關鍵步驟就是抽象化,而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。
3、依賴倒轉原則(Dependence Inversion Principle)
這個原則是開閉原則的基礎,具體內容:針對接口編程,依賴於抽象而不依賴於具體。
4、接口隔離原則(Interface Segregation Principle)
這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。它還有另外一個意思是:降低類之間的耦合度。由此可見,其實設計模式就是從大型軟件架構出發、便於升級和維護的軟件設計思想,它強調降低依賴,降低耦合。
5、迪米特法則,又稱最少知道原則(Demeter Principle)
最少知道原則是指:一個實體應當儘量少地與其他實體之間發生相互作用,使得系統功能模塊相對獨立。
6、合成複用原則(Composite Reuse Principle)
合成複用原則是指:儘量使用合成/聚合的方式,而不是使用繼承。
JNI的使用
JNI是 Java Native Interface 的縮寫,它提供了若干的API實現了Java和其他語言的通信(主要是C&C++)。從Java1.1開始,JNI標準成爲java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互。JNI一開始是爲了本地已編譯語言,尤其是C和C++而設計的,但是它並不妨礙你使用其他編程語言,只要調用約定受支持就可以了。使用java與本地已編譯的代碼交互,通常會喪失平臺可移植性。
JNI步驟
- java類中編寫帶有native 聲明的方法。
- 使用 javac 命令編譯所編寫的java類。
- 使用 javah 命令生成頭文件。
- 使用C/C++實現本地方法。
- 生成動態連接庫。
- 執行(java)。
JNI實例
public class HelloWorld {
public native void displayHelloWorld();//所有native關鍵詞修飾的都是對本地的聲明
static {
System.loadLibrary("hello");//載入本地庫
}
public static void main(String[] args) {
new HelloWorld().displayHelloWorld();
}
}
AOP是什麼
AOP(Aspect Oriented Programming) 面向切面編程,是目前軟件開發中的一個熱點,是Spring框架內容,利用AOP可以對業務邏輯的各個部分隔離,從而使的業務邏輯各部分的耦合性降低,提高程序的可重用性,踢開開發效率,主要功能:日誌記錄,性能統計,安全控制,事務處理,異常處理等。
AOP實現原理是java動態代理,但是jdk的動態代理必須實現接口,所以spring的aop是用cglib這個庫實現的,cglis使用裏asm這個直接操縱字節碼的框架,所以可以做到不使用接口的情況下實現動態代理。
OOP是什麼
OOP面向對象編程,針對業務處理過程的實體及其屬性和行爲進行抽象封裝,以獲得更加清晰高效的邏輯單元劃分。
AOP與OOP的區別
OOP面向對象編程,針對業務處理過程的實體及其屬性和行爲進行抽象封裝,以獲得更加清晰高效的邏輯單元劃分。而AOP則是針對業務處理過程中的切面進行提取,它所面對的是處理過程的某個步驟或階段,以獲得邏輯過程的中各部分之間低耦合的隔離效果。這兩種設計思想在目標上有着本質的差異。
舉例:
對於“僱員”這樣一個業務實體進行封裝,自然是OOP的任務,我們可以建立一個“Employee”類,並將“僱員”相關的屬性和行爲封裝其中。而用AOP 設計思想對“僱員”進行封裝則無從談起。
同樣,對於“權限檢查”這一動作片段進行劃分,則是AOP的目標領域。
OOP面向名次領域,AOP面向動詞領域。
總之AOP可以通過預編譯方式和運行期動態代理實現在不修改源碼的情況下,給程序動態同意添加功能的一項技術。
推薦閱讀
Spring Cloud
- Spring Cloud(一)服務的註冊與發現 Eureka
- Spring Cloud(二)Consul 服務治理實現
- Spring Cloud(三)服務提供者 Eureka + 服務消費者(rest + Ribbon)
- Spring Cloud(四)服務提供者 Eureka + 服務消費者 Feign
- Spring Cloud(五)斷路器監控(Hystrix Dashboard)
- Spring Cloud(六)服務網關 zuul 快速入門
- Spring Cloud(七)服務網關 Zuul Filter 使用
- Spring Cloud(八)高可用的分佈式配置中心 Spring Cloud Config
- Spring Cloud(九)高可用的分佈式配置中心 Spring Cloud Config 集成 Eureka 服務
- Spring Cloud(十)高可用的分佈式配置中心 Spring Cloud Config 中使用 Refresh
Spring Boot
- Spring Boot 中使用 LogBack
- Spring Boot 中使用 Dubbo
- Spring Boot 中使用 RabbitMQ
- Spring Boot 中使用 SolrCloud
- Spring Boot 中使用 RocketMQ
Docker 容器
- Docker Compose 1.18.0 之服務編排詳解
- Docker CE 安裝 初窺 Dockerfile 部署 Nginx
- Docker Container 容器操作
- Docker Hub 倉庫使用,及搭建 Docker Registry
- Docker Registry Server 搭建,配置免費 HTTPS 證書,及擁有權限認證、TLS 的私有倉庫
- Docker Registry 企業級私有鏡像倉庫Harbor管理WEB UI, 可能是最詳細的部署
- Docker 部署 SpringBoot 項目整合 Redis 鏡像做訪問計數 Demo
- Docker Maven Plugin 生成 Docker 鏡像 push 到 DockerHub上
環境搭建
我有一個微信公衆號,經常會分享一些Java技術相關的乾貨;如果你喜歡我的分享,可以用微信搜索“Java團長”或者“javatuanzhang”關注。
</div>
<p class="postfoot">
posted on <span id="post-date">2018-04-24 10:58</span> <a href="https://www.cnblogs.com/java1024/">Java團長</a> 閱讀(<span id="post_view_count">403</span>) 評論(<span id="post_comment_count">0</span>) <a href="https://i.cnblogs.com/EditPosts.aspx?postid=8926864" rel="nofollow">編輯</a> <a href="#" onclick="AddToWz(8926864);return false;">收藏</a>
</p>
</div>