最近看到網上流傳着,各種面試經驗及面試題,往往都是一大堆技術題目貼上去,而沒有答案。
為此我業餘時間整理了,Java基礎常見的40道常見面試題,及詳細答案,望各路大牛,發現不對的地方,不吝賜教,留言即可。
八種基本資料類型的大小,以及他們的封裝類
引用資料類型
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的差別
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後
整形,枚舉類型,boolean,字元串都可以。
原理
switch (expression) // 括号裡是一個表達式,結果是個整數{
case constant1: // case 後面的标号,也是個整數
group of statements 1;
break;
case constant2:
group of statements 2;
break;
...
default:
default group of statements
}
jdk1.7後,整形,枚舉類型,boolean,字元串都可以。
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()指的是值是否相同。
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對象,則需要如下定義:
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_calv...
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在這個桶裡找到我們要的類。
請看下面這個例子
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);
}
}
輸出結果為:
true
False
[HashTest@1, HashTest@1]
以上這個示例,我們隻是重寫了HashCode方法,從上面的結果可以看出,雖然兩個對象的HashCode相等,但是實際上兩個對象并不是相等,因為我們沒有重寫equals方法,那麼就會調用Object預設的equals方法,顯示這是兩個不同的對象。
這裡我們将生成的對象放到了HashSet中,而HashSet中隻能夠存放唯一的對象,也就是相同的(适用于equals方法)的對象隻會存放一個,但是這裡實際上是兩個對象ab都被放到了HashSet中,這樣HashSet就失去了他本身的意義了。
下面我們繼續重寫equals方法:
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()隻是簡單示例寫的,真正的生産換将不是這樣的
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()方法。
例如
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
}
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/11...
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方法。
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
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap重新計算hash值,而且用與代替求模:
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;
}
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。
transient Entry<K,V>[] table;
我們向 HashMap 中所放置的對象實際上是存儲在該數組當中。
而Map中的key,value則以Entry的形式存放在數組中。
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在多線程并發的情況下會産生死循環呢,為什麼會産生,何種情況下才會産生死循環呢???
既然會産生死循環,為什麼并發情況下,還是用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、一個非靜态内部類可以通路外部類的資料和方法。
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...
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的使用
https://www.cnblogs.com/larryzeal/p/5687392.html
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可以通過預編譯方式和運作期動态代理實作在不修改源碼的情況下,給程式動态同意添加功能的一項技術。