前言
網(wǎng)上的 Java 基礎(chǔ)面試題文章有非常多,但是大部分都比較老了飘痛。
很多題目早已不是當(dāng)前的熱門(mén)題目珊膜,沒(méi)有必要在這些題目上花太多時(shí)間。
很多答案放現(xiàn)在已經(jīng)不準(zhǔn)確宣脉,可能會(huì)誤導(dǎo)新人车柠。
因此,我花了幾天時(shí)間整理了一些時(shí)下高頻的 Java 基礎(chǔ)題目塑猖,并反復(fù)斟酌竹祷,給出符合當(dāng)前版本的解析。
面試系列
我自己前前后后加起來(lái)總共應(yīng)該參加了不下四五十次的面試萌庆,拿到過(guò)幾乎所有一線(xiàn)大廠(chǎng)的 offer:阿里溶褪、字節(jié)、美團(tuán)践险、快手猿妈、拼多多等等。
每次面試后我都會(huì)將面試的題目進(jìn)行記錄巍虫,并整理成自己的題庫(kù)彭则,最近我將這些題目整理出來(lái),并按大廠(chǎng)的標(biāo)準(zhǔn)給出自己的解析占遥,希望在這金三銀四的季節(jié)里俯抖,能助你一臂之力。
面試文章持續(xù)更新中... ...
正文
1瓦胎、面向?qū)ο蟮娜齻€(gè)基本特征芬萍?
面向?qū)ο蟮娜齻€(gè)基本特征是:封裝、繼承和多態(tài)搔啊。
繼承:讓某個(gè)類(lèi)型的對(duì)象獲得另一個(gè)類(lèi)型的對(duì)象的屬性的方法柬祠。繼承就是子類(lèi)繼承父類(lèi)的特征和行為,使得子類(lèi)對(duì)象(實(shí)例)具有父類(lèi)的實(shí)例域和方法负芋,或子類(lèi)從父類(lèi)繼承方法漫蛔,使得子類(lèi)具有父類(lèi)相同的行為。
封裝:隱藏部分對(duì)象的屬性和實(shí)現(xiàn)細(xì)節(jié)旧蛾,對(duì)數(shù)據(jù)的訪(fǎng)問(wèn)只能通過(guò)外公開(kāi)的接口莽龟。通過(guò)這種方式,對(duì)象對(duì)內(nèi)部數(shù)據(jù)提供了不同級(jí)別的保護(hù)锨天,以防止程序中無(wú)關(guān)的部分意外的改變或錯(cuò)誤的使用了對(duì)象的私有部分毯盈。
多態(tài):對(duì)于同一個(gè)行為,不同的子類(lèi)對(duì)象具有不同的表現(xiàn)形式病袄。多態(tài)存在的3個(gè)條件:1)繼承奶镶;2)重寫(xiě)迟赃;3)父類(lèi)引用指向子類(lèi)對(duì)象。
舉個(gè)簡(jiǎn)單的例子:英雄聯(lián)盟里面我們按下 Q 鍵這個(gè)動(dòng)作:
對(duì)于亞索厂镇,就是斬鋼閃
對(duì)于提莫纤壁,就是致盲吹箭
對(duì)于劍圣,就是阿爾法突襲
同一個(gè)事件發(fā)生在不同的對(duì)象上會(huì)產(chǎn)生不同的結(jié)果捺信。
我再舉一個(gè)簡(jiǎn)單的例子幫助大家理解酌媒,這個(gè)例子可能不是完全準(zhǔn)確,但是我認(rèn)為是有利于理解的迄靠。
public class Animal { // 動(dòng)物
public void sleep() {
System.out.println("躺著睡");
}
}
class Horse extends Animal { // 馬 是一種動(dòng)物
public void sleep() {
System.out.println("站著睡");
}
}
class Cat extends Animal { // 貓 是一種動(dòng)物
private int age;
public int getAge() {
return age + 1;
}
@Override
public void sleep() {
System.out.println("四腳朝天的睡");
}
}
在這個(gè)例子中:
House 和 Cat 都是 Animal秒咨,所以他們都繼承了 Animal,同時(shí)也從 Animal 繼承了 sleep 這個(gè)行為掌挚。
但是針對(duì) sleep 這個(gè)行為雨席,House 和 Cat 進(jìn)行了重寫(xiě),有了不同的表現(xiàn)形式(實(shí)現(xiàn))吠式,這個(gè)我們稱(chēng)為多態(tài)陡厘。
在 Cat 里,將 age 屬性定義為 private特占,外界無(wú)法直接訪(fǎng)問(wèn)糙置,要獲取 Cat 的 age 信息只能通過(guò) getAge 方法,從而對(duì)外隱藏了 age 屬性是目,這個(gè)就叫做封裝谤饭。當(dāng)然,這邊 age 只是個(gè)例子懊纳,實(shí)際使用中可能是一個(gè)復(fù)雜很多的對(duì)象揉抵。
2、訪(fǎng)問(wèn)修飾符public嗤疯,private冤今,protected,以及不寫(xiě)時(shí)的區(qū)別身弊?
3、下面兩個(gè)代碼塊能正常編譯和執(zhí)行嗎列敲?
// 代碼塊1
short s1 = 1; s1 = s1 + 1;
// 代碼塊2
short s1 = 1; s1 += 1;
代碼塊1編譯報(bào)錯(cuò)阱佛,錯(cuò)誤原因是:不兼容的類(lèi)型: 從int轉(zhuǎn)換到short可能會(huì)有損失”。
代碼塊2正常編譯和執(zhí)行戴而。
我們將代碼塊2進(jìn)行編譯凑术,字節(jié)碼如下:
public class com.joonwhee.open.demo.Convert {
public com.joonwhee.open.demo.Convert();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1 // 將int類(lèi)型值1入(操作數(shù))棧
1: istore_1 // 將棧頂int類(lèi)型值保存到局部變量1中
2: iload_1 // 從局部變量1中裝載int類(lèi)型值入棧
3: iconst_1 // 將int類(lèi)型值1入棧
4: iadd // 將棧頂兩int類(lèi)型數(shù)相加,結(jié)果入棧
5: i2s // 將棧頂int類(lèi)型值截?cái)喑蓅hort類(lèi)型值所意,后帶符號(hào)擴(kuò)展成int類(lèi)型值入棧淮逊。
6: istore_1 // 將棧頂int類(lèi)型值保存到局部變量1中
7: return
}
可以看到字節(jié)碼中包含了 i2s 指令催首,該指令用于將 int 轉(zhuǎn)成 short。i2s 是 int to short 的縮寫(xiě)泄鹏。
其實(shí)郎任,s1 += 1 相當(dāng)于 s1 = (short)(s1 + 1),有興趣的可以自己編譯下這兩行代碼的字節(jié)碼备籽,你會(huì)發(fā)現(xiàn)是一摸一樣的舶治。
說(shuō)好的 Java 基礎(chǔ)題,怎么又開(kāi)始變態(tài)起來(lái)了车猬?霉猛??
4珠闰、基礎(chǔ)考察惜浅,指出下題的輸出結(jié)果
public static void main(String[] args) {
Integer a = 128, b = 128, c = 127, d = 127;
System.out.println(a == b);
System.out.println(c == d);
}
答案是:false,true伏嗜。
執(zhí)行 Integer a = 128坛悉,相當(dāng)于執(zhí)行:Integer a = Integer.valueOf(128),基本類(lèi)型自動(dòng)轉(zhuǎn)換為包裝類(lèi)的過(guò)程稱(chēng)為自動(dòng)裝箱(autoboxing)阅仔。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在 Integer 中引入了 IntegerCache 來(lái)緩存一定范圍的值吹散,IntegerCache 默認(rèn)情況下范圍為:-128~127。
本題中的 127 命中了 IntegerCache八酒,所以 c 和 d 是相同對(duì)象空民,而 128 則沒(méi)有命中,所以 a 和 b 是不同對(duì)象羞迷。
但是這個(gè)緩存范圍時(shí)可以修改的界轩,可能有些人不知道∠挝停可以通過(guò)JVM啟動(dòng)參數(shù):-XX:AutoBoxCacheMax=<size> 來(lái)修改上限值浊猾,如下圖所示:
5、用最有效率的方法計(jì)算2乘以8热鞍?
2 << 3葫慎。
進(jìn)階:通常情況下,可以認(rèn)為位運(yùn)算是性能最高的薇宠。但是偷办,其實(shí)編譯器現(xiàn)在已經(jīng)“非常聰明了”绰姻,很多指令編譯器都能自己做優(yōu)化顿涣。所以在實(shí)際實(shí)用中遍尺,我們無(wú)需特意去追求實(shí)用位運(yùn)算捌木,這樣不僅會(huì)導(dǎo)致代碼可讀性很差珍剑,而且某些自作聰明的優(yōu)化反而會(huì)誤導(dǎo)編譯器解幽,使得編譯器無(wú)法進(jìn)行更好的優(yōu)化浑娜。
這可能就是所謂的“豬隊(duì)友”吧瓮钥。
6、&和&&的區(qū)別湖苞?
&&:邏輯與運(yùn)算符拯欧。當(dāng)運(yùn)算符左右兩邊的表達(dá)式都為 true,才返回 true袒啼。同時(shí)具有短路性哈扮,如果第一個(gè)表達(dá)式為 false,則直接返回 false蚓再。
&:邏輯與運(yùn)算符滑肉、按位與運(yùn)算符。
按位與運(yùn)算符:用于二進(jìn)制的計(jì)算摘仅,只有對(duì)應(yīng)的兩個(gè)二進(jìn)位均為1時(shí)靶庙,結(jié)果位才為1 ,否則為0娃属。
邏輯與運(yùn)算符:& 在用于邏輯與時(shí)六荒,和 && 的區(qū)別是不具有短路性。所在通常使用邏輯與運(yùn)算符都會(huì)使用 &&矾端,而 & 更多的適用于位運(yùn)算掏击。
7、String 是 Java 基本數(shù)據(jù)類(lèi)型嗎秩铆?
答:不是砚亭。Java 中的基本數(shù)據(jù)類(lèi)型只有8個(gè):byte、short殴玛、int捅膘、long、float滚粟、double寻仗、char、boolean凡壤;除了基本類(lèi)型(primitive type)署尤,剩下的都是引用類(lèi)型(reference type)。
基本數(shù)據(jù)類(lèi)型:數(shù)據(jù)直接存儲(chǔ)在棧上
引用數(shù)據(jù)類(lèi)型區(qū)別:數(shù)據(jù)存儲(chǔ)在堆上亚侠,棧上只存儲(chǔ)引用地址
8曹体、String 類(lèi)可以繼承嗎?
不行盖奈。String 類(lèi)使用 final 修飾混坞,無(wú)法被繼承狐援。
9钢坦、String和StringBuilder究孕、StringBuffer的區(qū)別?
String:String 的值被創(chuàng)建后不能修改爹凹,任何對(duì) String 的修改都會(huì)引發(fā)新的 String 對(duì)象的生成厨诸。
StringBuffer:跟 String 類(lèi)似,但是值可以被修改禾酱,使用 synchronized 來(lái)保證線(xiàn)程安全微酬。
StringBuilder:StringBuffer 的非線(xiàn)程安全版本,沒(méi)有使用 synchronized颤陶,具有更高的性能颗管,推薦優(yōu)先使用。
10滓走、String s = new String("xyz") 創(chuàng)建了幾個(gè)字符串對(duì)象垦江?
一個(gè)或兩個(gè)。如果字符串常量池已經(jīng)有“xyz”搅方,則是一個(gè)比吭;否則,兩個(gè)姨涡。
當(dāng)字符創(chuàng)常量池沒(méi)有 “xyz”衩藤,此時(shí)會(huì)創(chuàng)建如下兩個(gè)對(duì)象:
一個(gè)是字符串字面量 "xyz" 所對(duì)應(yīng)的、駐留(intern)在一個(gè)全局共享的字符串常量池中的實(shí)例涛漂,此時(shí)該實(shí)例也是在堆中赏表,字符串常量池只放引用。
另一個(gè)是通過(guò) new String() 創(chuàng)建并初始化的怖喻,內(nèi)容與"xyz"相同的實(shí)例底哗,也是在堆中。
11锚沸、String s = "xyz" 和 String s = new String("xyz") 區(qū)別跋选?
兩個(gè)語(yǔ)句都會(huì)先去字符串常量池中檢查是否已經(jīng)存在 “xyz”,如果有則直接使用哗蜈,如果沒(méi)有則會(huì)在常量池中創(chuàng)建 “xyz” 對(duì)象前标。
另外,String s = new String("xyz") 還會(huì)通過(guò) new String() 在堆里創(chuàng)建一個(gè)內(nèi)容與 "xyz" 相同的對(duì)象實(shí)例距潘。
所以前者其實(shí)理解為被后者的所包含炼列。
12、== 和 equals 的區(qū)別是什么音比?
==:運(yùn)算符俭尖,用于比較基礎(chǔ)類(lèi)型變量和引用類(lèi)型變量。
對(duì)于基礎(chǔ)類(lèi)型變量,比較的變量保存的值是否相同稽犁,類(lèi)型不一定要相同焰望。
short s1 = 1; long l1 = 1;
// 結(jié)果:true。類(lèi)型不同已亥,但是值相同
System.out.println(s1 == l1);
對(duì)于引用類(lèi)型變量熊赖,比較的是兩個(gè)對(duì)象的地址是否相同。
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
// 結(jié)果:false虑椎。通過(guò)new創(chuàng)建震鹉,在內(nèi)存中指向兩個(gè)不同的對(duì)象
System.out.println(i1 == i2);
equals:Object 類(lèi)中定義的方法,通常用于比較兩個(gè)對(duì)象的值是否相等捆姜。
equals 在 Object 方法中其實(shí)等同于 ==传趾,但是在實(shí)際的使用中,equals 通常被重寫(xiě)用于比較兩個(gè)對(duì)象的值是否相同泥技。
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
// 結(jié)果:true墨缘。兩個(gè)不同的對(duì)象,但是具有相同的值
System.out.println(i1.equals(i2));
// Integer的equals重寫(xiě)方法
public boolean equals(Object obj) {
if (obj instanceof Integer) {
// 比較對(duì)象中保存的值是否相同
return value == ((Integer)obj).intValue();
}
return false;
}
13零抬、兩個(gè)對(duì)象的 hashCode() 相同镊讼,則 equals() 也一定為 true,對(duì)嗎平夜?
不對(duì)蝶棋。hashCode() 和 equals() 之間的關(guān)系如下:
當(dāng)有 a.equals(b) == true 時(shí),則 a.hashCode() == b.hashCode() 必然成立忽妒,
反過(guò)來(lái)玩裙,當(dāng) a.hashCode() == b.hashCode() 時(shí),a.equals(b) 不一定為 true段直。
14吃溅、什么是反射
反射是指在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類(lèi)都能夠知道這個(gè)類(lèi)所有的屬性和方法鸯檬;并且對(duì)于任意一個(gè)對(duì)象决侈,都能夠調(diào)用它的任意一個(gè)方法;這種動(dòng)態(tài)獲取信息以及動(dòng)態(tài)調(diào)用對(duì)象方法的功能稱(chēng)為反射機(jī)制喧务。
15赖歌、深拷貝和淺拷貝區(qū)別是什么?
數(shù)據(jù)分為基本數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型功茴÷耄基本數(shù)據(jù)類(lèi)型:數(shù)據(jù)直接存儲(chǔ)在棧中;引用數(shù)據(jù)類(lèi)型:存儲(chǔ)在棧中的是對(duì)象的引用地址坎穿,真實(shí)的對(duì)象數(shù)據(jù)存放在堆內(nèi)存里展父。
淺拷貝:對(duì)于基礎(chǔ)數(shù)據(jù)類(lèi)型:直接復(fù)制數(shù)據(jù)值返劲;對(duì)于引用數(shù)據(jù)類(lèi)型:只是復(fù)制了對(duì)象的引用地址,新舊對(duì)象指向同一個(gè)內(nèi)存地址栖茉,修改其中一個(gè)對(duì)象的值旭等,另一個(gè)對(duì)象的值隨之改變。
深拷貝:對(duì)于基礎(chǔ)數(shù)據(jù)類(lèi)型:直接復(fù)制數(shù)據(jù)值衡载;對(duì)于引用數(shù)據(jù)類(lèi)型:開(kāi)辟新的內(nèi)存空間,在新的內(nèi)存空間里復(fù)制一個(gè)一模一樣的對(duì)象隙袁,新老對(duì)象不共享內(nèi)存痰娱,修改其中一個(gè)對(duì)象的值,不會(huì)影響另一個(gè)對(duì)象菩收。
深拷貝相比于淺拷貝速度較慢并且花銷(xiāo)較大梨睁。
16、并發(fā)和并行有什么區(qū)別娜饵?
并發(fā):兩個(gè)或多個(gè)事件在同一時(shí)間間隔發(fā)生坡贺。
并行:兩個(gè)或者多個(gè)事件在同一時(shí)刻發(fā)生。
并行是真正意義上箱舞,同一時(shí)刻做多件事情遍坟,而并發(fā)在同一時(shí)刻只會(huì)做一件事件,只是可以將時(shí)間切碎晴股,交替做多件事情愿伴。
網(wǎng)上有個(gè)例子挺形象的:
你吃飯吃到一半,電話(huà)來(lái)了电湘,你一直到吃完了以后才去接隔节,這就說(shuō)明你不支持并發(fā)也不支持并行。
你吃飯吃到一半寂呛,電話(huà)來(lái)了怎诫,你停了下來(lái)接了電話(huà),接完后繼續(xù)吃飯贷痪,這說(shuō)明你支持并發(fā)幻妓。
你吃飯吃到一半,電話(huà)來(lái)了劫拢,你一邊打電話(huà)一邊吃飯涌哲,這說(shuō)明你支持并行。
17尚镰、構(gòu)造器是否可被 重寫(xiě)?
Constructor 不能被 override(重寫(xiě))阀圾,但是可以 overload(重載),所以你可以看到?個(gè)類(lèi)中有多個(gè)構(gòu)造函數(shù)的情況狗唉。
18初烘、當(dāng)一個(gè)對(duì)象被當(dāng)作參數(shù)傳遞到一個(gè)方法后,此方法可改變這個(gè)對(duì)象的屬性,并可返回變化后的結(jié)果肾筐,那么這里到底是值傳遞還是引用傳遞哆料?
值傳遞。Java 中只有值傳遞吗铐,對(duì)于對(duì)象參數(shù)东亦,值的內(nèi)容是對(duì)象的引用。
19唬渗、Java 靜態(tài)變量和成員變量的區(qū)別典阵。
public class Demo {
/**
* 靜態(tài)變量:又稱(chēng)類(lèi)變量,static修飾
*/
public static String STATIC_VARIABLE = "靜態(tài)變量";
/**
* 實(shí)例變量:又稱(chēng)成員變量镊逝,沒(méi)有static修飾
*/
public String INSTANCE_VARIABLE = "實(shí)例變量";
}
成員變量存在于堆內(nèi)存中壮啊。靜態(tài)變量存在于方法區(qū)中。
成員變量與對(duì)象共存亡撑蒜,隨著對(duì)象創(chuàng)建而存在歹啼,隨著對(duì)象被回收而釋放。靜態(tài)變量與類(lèi)共存亡座菠,隨著類(lèi)的加載而存在狸眼,隨著類(lèi)的消失而消失。
成員變量所屬于對(duì)象浴滴,所以也稱(chēng)為實(shí)例變量份企。靜態(tài)變量所屬于類(lèi),所以也稱(chēng)為類(lèi)變量巡莹。
成員變量只能被對(duì)象所調(diào)用 司志。靜態(tài)變量可以被對(duì)象調(diào)用,也可以被類(lèi)名調(diào)用降宅。
20骂远、是否可以從一個(gè)靜態(tài)(static)方法內(nèi)部發(fā)出對(duì)非靜態(tài)(non-static)方法的調(diào)用?
區(qū)分兩種情況腰根,發(fā)出調(diào)用時(shí)是否顯示創(chuàng)建了對(duì)象實(shí)例激才。
1)沒(méi)有顯示創(chuàng)建對(duì)象實(shí)例:不可以發(fā)起調(diào)用,非靜態(tài)方法只能被對(duì)象所調(diào)用额嘿,靜態(tài)方法可以通過(guò)對(duì)象調(diào)用瘸恼,也可以通過(guò)類(lèi)名調(diào)用,所以靜態(tài)方法被調(diào)用時(shí)册养,可能還沒(méi)有創(chuàng)建任何實(shí)例對(duì)象东帅。因此通過(guò)靜態(tài)方法內(nèi)部發(fā)出對(duì)非靜態(tài)方法的調(diào)用,此時(shí)可能無(wú)法知道非靜態(tài)方法屬于哪個(gè)對(duì)象球拦。
public class Demo {
public static void staticMethod() {
// 直接調(diào)用非靜態(tài)方法:編譯報(bào)錯(cuò)
instanceMethod();
}
public void instanceMethod() {
System.out.println("非靜態(tài)方法");
}
}
2)顯示創(chuàng)建對(duì)象實(shí)例:可以發(fā)起調(diào)用靠闭,在靜態(tài)方法中顯示的創(chuàng)建對(duì)象實(shí)例帐我,則可以正常的調(diào)用。
public class Demo {
public static void staticMethod() {
// 先創(chuàng)建實(shí)例對(duì)象愧膀,再調(diào)用非靜態(tài)方法:成功執(zhí)行
Demo demo = new Demo();
demo.instanceMethod();
}
public void instanceMethod() {
System.out.println("非靜態(tài)方法");
}
}
21拦键、初始化考察,請(qǐng)指出下面程序的運(yùn)行結(jié)果檩淋。
public class InitialTest {
public static void main(String[] args) {
A ab = new B();
ab = new B();
}
}
class A {
static { // 父類(lèi)靜態(tài)代碼塊
System.out.print("A");
}
public A() { // 父類(lèi)構(gòu)造器
System.out.print("a");
}
}
class B extends A {
static { // 子類(lèi)靜態(tài)代碼塊
System.out.print("B");
}
public B() { // 子類(lèi)構(gòu)造器
System.out.print("b");
}
}
執(zhí)行結(jié)果:ABabab芬为,兩個(gè)考察點(diǎn):
1)靜態(tài)變量只會(huì)初始化(執(zhí)行)一次。
2)當(dāng)有父類(lèi)時(shí)蟀悦,完整的初始化順序?yàn)椋焊割?lèi)靜態(tài)變量(靜態(tài)代碼塊)->子類(lèi)靜態(tài)變量(靜態(tài)代碼塊)->父類(lèi)非靜態(tài)變量(非靜態(tài)代碼塊)->父類(lèi)構(gòu)造器 ->子類(lèi)非靜態(tài)變量(非靜態(tài)代碼塊)->子類(lèi)構(gòu)造器 媚朦。
關(guān)于初始化,這題算入門(mén)題熬芜,我之前還寫(xiě)過(guò)一道有(fei)點(diǎn)(chang)意(bian)思(tai)的進(jìn)階題目,有興趣的可以看看:一道有意思的“初始化”面試題
22福稳、重載(Overload)和重寫(xiě)(Override)的區(qū)別涎拉?
方法的重載和重寫(xiě)都是實(shí)現(xiàn)多態(tài)的方式,區(qū)別在于前者實(shí)現(xiàn)的是編譯時(shí)的多態(tài)性的圆,而后者實(shí)現(xiàn)的是運(yùn)行時(shí)的多態(tài)性鼓拧。
重載:一個(gè)類(lèi)中有多個(gè)同名的方法,但是具有有不同的參數(shù)列表(參數(shù)類(lèi)型不同越妈、參數(shù)個(gè)數(shù)不同或者二者都不同)季俩。
重寫(xiě):發(fā)生在子類(lèi)與父類(lèi)之間,子類(lèi)對(duì)父類(lèi)的方法進(jìn)行重寫(xiě)梅掠,參數(shù)都不能改變酌住,返回值類(lèi)型可以不相同,但是必須是父類(lèi)返回值的派生類(lèi)阎抒。即外殼不變酪我,核心重寫(xiě)!重寫(xiě)的好處在于子類(lèi)可以根據(jù)需要且叁,定義特定于自己的行為都哭。
23、為什么不能根據(jù)返回類(lèi)型來(lái)區(qū)分重載逞带?
如果我們有兩個(gè)方法如下欺矫,當(dāng)我們調(diào)用:test(1) 時(shí),編譯器無(wú)法確認(rèn)要調(diào)用的是哪個(gè)展氓。
// 方法1
int test(int a);
// 方法2
long test(int a);
方法的返回值只是作為方法運(yùn)行之后的一個(gè)“狀態(tài)”穆趴,但是并不是所有調(diào)用都關(guān)注返回值,所以不能將返回值作為重載的唯一區(qū)分條件遇汞。
24毡代、抽象類(lèi)(abstract class)和接口(interface)有什么區(qū)別阅羹?
抽象類(lèi)只能單繼承,接口可以多實(shí)現(xiàn)教寂。
抽象類(lèi)可以有構(gòu)造方法捏鱼,接口中不能有構(gòu)造方法。
抽象類(lèi)中可以有成員變量酪耕,接口中沒(méi)有成員變量导梆,只能有常量(默認(rèn)就是 public static final)
抽象類(lèi)中可以包含非抽象的方法,在 Java 7 之前接口中的所有方法都是抽象的迂烁,在 Java 8 之后看尼,接口支持非抽象方法:default 方法、靜態(tài)方法等盟步。Java 9 支持私有方法藏斩、私有靜態(tài)方法。
抽象類(lèi)中的抽象方法類(lèi)型可以是任意修飾符却盘,Java 8 之前接口中的方法只能是 public 類(lèi)型狰域,Java 9 支持 private 類(lèi)型。
設(shè)計(jì)思想的區(qū)別:
接口是自上而下的抽象過(guò)程黄橘,接口規(guī)范了某些行為兆览,是對(duì)某一行為的抽象。我需要這個(gè)行為塞关,我就去實(shí)現(xiàn)某個(gè)接口抬探,但是具體這個(gè)行為怎么實(shí)現(xiàn),完全由自己決定帆赢。
抽象類(lèi)是自下而上的抽象過(guò)程小压,抽象類(lèi)提供了通用實(shí)現(xiàn),是對(duì)某一類(lèi)事物的抽象椰于。我們?cè)趯?xiě)實(shí)現(xiàn)類(lèi)的時(shí)候场航,發(fā)現(xiàn)某些實(shí)現(xiàn)類(lèi)具有幾乎相同的實(shí)現(xiàn),因此我們將這些相同的實(shí)現(xiàn)抽取出來(lái)成為抽象類(lèi)廉羔,然后如果有一些差異點(diǎn)溉痢,則可以提供抽象方法來(lái)支持自定義實(shí)現(xiàn)。
我在網(wǎng)上看到有個(gè)說(shuō)法憋他,挺形象的:
普通類(lèi)像親爹 孩饼,他有啥都是你的。
抽象類(lèi)像叔伯竹挡,有一部分會(huì)給你镀娶,還能指導(dǎo)你做事的方法。
接口像干爹揪罕,可以給你指引方法梯码,但是做成啥樣得你自己努力實(shí)現(xiàn)宝泵。
25、Error 和 Exception 有什么區(qū)別轩娶?
Error 和 Exception 都是 Throwable 的子類(lèi)儿奶,用于表示程序出現(xiàn)了不正常的情況。區(qū)別在于:
Error 表示系統(tǒng)級(jí)的錯(cuò)誤和程序不必處理的異常鳄抒,是恢復(fù)不是不可能但很困難的情況下的一種嚴(yán)重問(wèn)題闯捎,比如內(nèi)存溢出,不可能指望程序能處理這樣的情況许溅。
Exception 表示需要捕捉或者需要程序進(jìn)行處理的異常瓤鼻,是一種設(shè)計(jì)或?qū)崿F(xiàn)問(wèn)題,也就是說(shuō)贤重,它表示如果程序運(yùn)行正常茬祷,從不會(huì)發(fā)生的情況。
26并蝗、Java 中的 final 關(guān)鍵字有哪些用法祭犯?
修飾類(lèi):該類(lèi)不能再派生出新的子類(lèi),不能作為父類(lèi)被繼承借卧。因此盹憎,一個(gè)類(lèi)不能同時(shí)被聲明為abstract 和 final筛峭。
修飾方法:該方法不能被子類(lèi)重寫(xiě)铐刘。
修飾變量:該變量必須在聲明時(shí)給定初值,而在以后只能讀取影晓,不可修改镰吵。 如果變量是對(duì)象,則指的是引用不可修改挂签,但是對(duì)象的屬性還是可以修改的疤祭。
public class FinalDemo {
// 不可再修改該變量的值
public static final int FINAL_VARIABLE = 0;
// 不可再修改該變量的引用,但是可以直接修改屬性值
public static final User USER = new User();
public static void main(String[] args) {
// 輸出:User(id=0, name=null, age=0)
System.out.println(USER);
// 直接修改屬性值
USER.setName("test");
// 輸出:User(id=0, name=test, age=0)
System.out.println(USER);
}
}
27饵婆、闡述 final勺馆、finally、finalize 的區(qū)別侨核。
其實(shí)是三個(gè)完全不相關(guān)的東西草穆,只是長(zhǎng)的有點(diǎn)像。搓译。
final 如上所示悲柱。
finally:finally 是對(duì) Java 異常處理機(jī)制的最佳補(bǔ)充,通常配合 try些己、catch 使用豌鸡,用于存放那些無(wú)論是否出現(xiàn)異常都一定會(huì)執(zhí)行的代碼嘿般。在實(shí)際使用中,通常用于釋放鎖涯冠、數(shù)據(jù)庫(kù)連接等資源炉奴,把資源釋放方法放到 finally 中,可以大大降低程序出錯(cuò)的幾率功偿。
finalize:Object 中的方法盆佣,在垃圾收集器將對(duì)象從內(nèi)存中清除出去之前做必要的清理工作。finalize()方法僅作為了解即可械荷,在 Java 9 中該方法已經(jīng)被標(biāo)記為廢棄共耍,并添加新的 java.lang.ref.Cleaner,提供了更靈活和有效的方法來(lái)釋放資源吨瞎。這也側(cè)面說(shuō)明了痹兜,這個(gè)方法的設(shè)計(jì)是失敗的,因此更加不能去使用它颤诀。
28字旭、try、catch崖叫、finally 考察遗淳,請(qǐng)指出下面程序的運(yùn)行結(jié)果。
public class TryDemo {
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
try {
return 1;
} catch (Exception e) {
return 2;
} finally {
System.out.print("3");
}
}
}
執(zhí)行結(jié)果:31心傀。
相信很多同學(xué)應(yīng)該都做對(duì)了屈暗,try、catch脂男。finally 的基礎(chǔ)用法养叛,在 return 前會(huì)先執(zhí)行 finally 語(yǔ)句塊,所以是先輸出 finally 里的 3宰翅,再輸出 return 的 1弃甥。
29、try汁讼、catch淆攻、finally 考察2,請(qǐng)指出下面程序的運(yùn)行結(jié)果嘿架。
public class TryDemo {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
try {
return 2;
} finally {
return 3;
}
}
}
執(zhí)行結(jié)果:3瓶珊。
這題有點(diǎn)先將,但也不難眶明,try 返回前先執(zhí)行 finally艰毒,結(jié)果 finally 里不按套路出牌,直接 return 了搜囱,自然也就走不到 try 里面的 return 了丑瞧。
finally 里面使用 return 僅存在于面試題中柑土,實(shí)際開(kāi)發(fā)中千萬(wàn)不要這么用。
30绊汹、try稽屏、catch、finally 考察3西乖,請(qǐng)指出下面程序的運(yùn)行結(jié)果狐榔。
public class TryDemo {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
int i = 0;
try {
i = 2;
return i;
} finally {
i = 3;
}
}
}
執(zhí)行結(jié)果:2。
這邊估計(jì)有不少同學(xué)會(huì)以為結(jié)果應(yīng)該是 3获雕,因?yàn)槲覀冎涝?return 前會(huì)執(zhí)行 finally薄腻,而 i 在 finally 中被修改為 3 了,那最終返回 i 不是應(yīng)該為 3 嗎届案?確實(shí)很容易這么想庵楷,我最初也是這么想的,當(dāng)初的自己還是太年輕了啊楣颠。
這邊的根本原因是尽纽,在執(zhí)行 finally 之前,JVM 會(huì)先將 i 的結(jié)果暫存起來(lái)童漩,然后 finally 執(zhí)行完畢后弄贿,會(huì)返回之前暫存的結(jié)果,而不是返回 i矫膨,所以即使這邊 i 已經(jīng)被修改為 3差凹,最終返回的還是之前暫存起來(lái)的結(jié)果 2。
這邊其實(shí)根據(jù)字節(jié)碼可以很容易看出來(lái)豆拨,在進(jìn)入 finally 之前直奋,JVM 會(huì)使用 iload牺弹、istore 兩個(gè)指令匹厘,將結(jié)果暫存俗他,在最終返回時(shí)在通過(guò) iload、ireturn 指令返回暫存的結(jié)果弥搞。
為了避免氣氛再次變態(tài)起來(lái),我這邊就不貼具體的字節(jié)碼程序了渠旁,有興趣的同學(xué)可以自己編譯查看下攀例。
31、JDK1.8之后有哪些新特性顾腊?
接口默認(rèn)方法:Java 8允許我們給接口添加一個(gè)非抽象的方法實(shí)現(xiàn)粤铭,只需要使用 default關(guān)鍵字即可
Lambda 表達(dá)式和函數(shù)式接口:Lambda 表達(dá)式本質(zhì)上是一段匿名內(nèi)部類(lèi),也可以是一段可以傳遞的代碼杂靶。Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞到方法中)梆惯,使用 Lambda 表達(dá)式使代碼更加簡(jiǎn)潔酱鸭,但是也不要濫用,否則會(huì)有可讀性等問(wèn)題垛吗,《Effective Java》作者 Josh Bloch 建議使用 Lambda 表達(dá)式最好不要超過(guò)3行凹髓。
Stream API:用函數(shù)式編程方式在集合類(lèi)上進(jìn)行復(fù)雜操作的工具,配合Lambda表達(dá)式可以方便的對(duì)集合進(jìn)行處理怯屉。Java8 中處理集合的關(guān)鍵抽象概念蔚舀,它可以指定你希望對(duì)集合進(jìn)行的操作,可以執(zhí)行非常復(fù)雜的查找锨络、過(guò)濾和映射數(shù)據(jù)等操作赌躺。使用Stream API 對(duì)集合數(shù)據(jù)進(jìn)行操作,就類(lèi)似于使用 SQL 執(zhí)行的數(shù)據(jù)庫(kù)查詢(xún)羡儿。也可以使用 Stream API 來(lái)并行執(zhí)行操作寿谴。簡(jiǎn)而言之,Stream API 提供了一種高效且易于使用的處理數(shù)據(jù)的方式失受。
方法引用:方法引用提供了非常有用的語(yǔ)法讶泰,可以直接引用已有Java類(lèi)或?qū)ο螅▽?shí)例)的方法或構(gòu)造器。與lambda聯(lián)合使用拂到,方法引用可以使語(yǔ)言的構(gòu)造更緊湊簡(jiǎn)潔痪署,減少冗余代碼。
日期時(shí)間API:Java 8 引入了新的日期時(shí)間API改進(jìn)了日期時(shí)間的管理兄旬。
Optional 類(lèi):著名的 NullPointerException 是引起系統(tǒng)失敗最常見(jiàn)的原因狼犯。很久以前 Google Guava 項(xiàng)目引入了 Optional 作為解決空指針異常的一種方式,不贊成代碼被 null 檢查的代碼污染领铐,期望程序員寫(xiě)整潔的代碼悯森。受Google Guava的鼓勵(lì),Optional 現(xiàn)在是Java 8庫(kù)的一部分绪撵。
新工具:新的編譯工具瓢姻,如:Nashorn引擎 jjs、 類(lèi)依賴(lài)分析器 jdeps音诈。
50幻碱、wait() 和 sleep() 方法的區(qū)別
來(lái)源不同:sleep() 來(lái)自 Thread 類(lèi),wait() 來(lái)自 Object 類(lèi)细溅。
對(duì)于同步鎖的影響不同:sleep() 不會(huì)該表同步鎖的行為褥傍,如果當(dāng)前線(xiàn)程持有同步鎖,那么 sleep 是不會(huì)讓線(xiàn)程釋放同步鎖的喇聊。wait() 會(huì)釋放同步鎖恍风,讓其他線(xiàn)程進(jìn)入 synchronized 代碼塊執(zhí)行。
使用范圍不同:sleep() 可以在任何地方使用。wait() 只能在同步控制方法或者同步控制塊里面使用朋贬,否則會(huì)拋 IllegalMonitorStateException鸥咖。
恢復(fù)方式不同:兩者會(huì)暫停當(dāng)前線(xiàn)程,但是在恢復(fù)上不太一樣兄世。sleep() 在時(shí)間到了之后會(huì)重新恢復(fù)啼辣;wait() 則需要其他線(xiàn)程調(diào)用同一對(duì)象的 notify()/nofityAll() 才能重新恢復(fù)。
51御滩、線(xiàn)程的 sleep() 方法和 yield() 方法有什么區(qū)別鸥拧?
線(xiàn)程執(zhí)行 sleep() 方法后進(jìn)入超時(shí)等待(TIMED_WAITING)狀態(tài),而執(zhí)行 yield() 方法后進(jìn)入就緒(READY)狀態(tài)削解。
sleep() 方法給其他線(xiàn)程運(yùn)行機(jī)會(huì)時(shí)不考慮線(xiàn)程的優(yōu)先級(jí)富弦,因此會(huì)給低優(yōu)先級(jí)的線(xiàn)程運(yùn)行的機(jī)會(huì);yield() 方法只會(huì)給相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線(xiàn)程以運(yùn)行的機(jī)會(huì)氛驮。
52腕柜、線(xiàn)程的 join() 方法是干啥用的?
用于等待當(dāng)前線(xiàn)程終止矫废。如果一個(gè)線(xiàn)程A執(zhí)行了 threadB.join() 語(yǔ)句盏缤,其含義是:當(dāng)前線(xiàn)程A等待 threadB 線(xiàn)程終止之后才從 threadB.join() 返回繼續(xù)往下執(zhí)行自己的代碼。
53蓖扑、編寫(xiě)多線(xiàn)程程序有幾種實(shí)現(xiàn)方式唉铜?
通常來(lái)說(shuō),可以認(rèn)為有三種方式:1)繼承 Thread 類(lèi)律杠;2)實(shí)現(xiàn) Runnable 接口潭流;3)實(shí)現(xiàn) Callable 接口。
其中柜去,Thread 其實(shí)也是實(shí)現(xiàn)了 Runable 接口灰嫉。Runnable 和 Callable 的主要區(qū)別在于是否有返回值。
54嗓奢、Thread 調(diào)用 start() 方法和調(diào)用 run() 方法的區(qū)別
run():普通的方法調(diào)用讼撒,在主線(xiàn)程中執(zhí)行,不會(huì)新建一個(gè)線(xiàn)程來(lái)執(zhí)行蔓罚。
start():新啟動(dòng)一個(gè)線(xiàn)程椿肩,這時(shí)此線(xiàn)程處于就緒(可運(yùn)行)狀態(tài)瞻颂,并沒(méi)有運(yùn)行豺谈,一旦得到 CPU 時(shí)間片,就開(kāi)始執(zhí)行 run() 方法贡这。
55茬末、線(xiàn)程的狀態(tài)流轉(zhuǎn)
一個(gè)線(xiàn)程可以處于以下?tīng)顟B(tài)之一:
NEW:新建但是尚未啟動(dòng)的線(xiàn)程處于此狀態(tài),沒(méi)有調(diào)用 start() 方法。
RUNNABLE:包含就緒(READY)和運(yùn)行中(RUNNING)兩種狀態(tài)丽惭。線(xiàn)程調(diào)用 start() 方法會(huì)會(huì)進(jìn)入就緒(READY)狀態(tài)击奶,等待獲取 CPU 時(shí)間片。如果成功獲取到 CPU 時(shí)間片责掏,則會(huì)進(jìn)入運(yùn)行中(RUNNING)狀態(tài)柜砾。
BLOCKED:線(xiàn)程在進(jìn)入同步方法/同步塊(synchronized)時(shí)被阻塞,等待同步鎖的線(xiàn)程處于此狀態(tài)换衬。
WAITING:無(wú)限期等待另一個(gè)線(xiàn)程執(zhí)行特定操作的線(xiàn)程處于此狀態(tài)痰驱,需要被顯示的喚醒,否則會(huì)一直等待下去瞳浦。例如對(duì)于 Object.wait()担映,需要等待另一個(gè)線(xiàn)程執(zhí)行 Object.notify() 或 Object.notifyAll();對(duì)于 Thread.join()叫潦,則需要等待指定的線(xiàn)程終止蝇完。
TIMED_WAITING:在指定的時(shí)間內(nèi)等待另一個(gè)線(xiàn)程執(zhí)行某項(xiàng)操作的線(xiàn)程處于此狀態(tài)。跟 WAITING 類(lèi)似矗蕊,區(qū)別在于該狀態(tài)有超時(shí)時(shí)間參數(shù)短蜕,在超時(shí)時(shí)間到了后會(huì)自動(dòng)喚醒,避免了無(wú)期限的等待傻咖。
TERMINATED:執(zhí)行完畢已經(jīng)退出的線(xiàn)程處于此狀態(tài)忿危。
線(xiàn)程在給定的時(shí)間點(diǎn)只能處于一種狀態(tài)。這些狀態(tài)是虛擬機(jī)狀態(tài)没龙,不反映任何操作系統(tǒng)線(xiàn)程狀態(tài)铺厨。
56、synchronized 和 Lock 的區(qū)別
1)Lock 是一個(gè)接口硬纤;synchronized 是 Java 中的關(guān)鍵字解滓,synchronized 是內(nèi)置的語(yǔ)言實(shí)現(xiàn);
2)Lock 在發(fā)生異常時(shí)筝家,如果沒(méi)有主動(dòng)通過(guò) unLock() 去釋放鎖洼裤,很可能會(huì)造成死鎖現(xiàn)象,因此使用 Lock 時(shí)需要在 finally 塊中釋放鎖溪王;synchronized 不需要手動(dòng)獲取鎖和釋放鎖腮鞍,在發(fā)生異常時(shí),會(huì)自動(dòng)釋放鎖莹菱,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生移国;
3)Lock 的使用更加靈活,可以有響應(yīng)中斷道伟、有超時(shí)時(shí)間等迹缀;而 synchronized 卻不行使碾,使用 synchronized 時(shí),等待的線(xiàn)程會(huì)一直等待下去祝懂,直到獲取到鎖票摇;
4)在性能上,隨著近些年 synchronized 的不斷優(yōu)化砚蓬,Lock 和 synchronized 在性能上已經(jīng)沒(méi)有很明顯的差距了矢门,所以性能不應(yīng)該成為我們選擇兩者的主要原因。官方推薦盡量使用 synchronized灰蛙,除非 synchronized 無(wú)法滿(mǎn)足需求時(shí)颅和,則可以使用 Lock。
57缕允、synchronized 各種加鎖場(chǎng)景的作用范圍
1.作用于非靜態(tài)方法峡扩,鎖住的是對(duì)象實(shí)例(this),每一個(gè)對(duì)象實(shí)例有一個(gè)鎖障本。
public synchronized void method() {}
2.作用于靜態(tài)方法教届,鎖住的是類(lèi)的Class對(duì)象,因?yàn)镃lass的相關(guān)數(shù)據(jù)存儲(chǔ)在永久代元空間驾霜,元空間是全局共享的案训,因此靜態(tài)方法鎖相當(dāng)于類(lèi)的一個(gè)全局鎖,會(huì)鎖所有調(diào)用該方法的線(xiàn)程粪糙。
public static synchronized void method() {}
3.作用于 Lock.class强霎,鎖住的是 Lock 的Class對(duì)象,也是全局只有一個(gè)蓉冈。
synchronized (Lock.class) {}
4.作用于 this城舞,鎖住的是對(duì)象實(shí)例,每一個(gè)對(duì)象實(shí)例有一個(gè)鎖寞酿。
synchronized (this) {}
5.作用于靜態(tài)成員變量家夺,鎖住的是該靜態(tài)成員變量對(duì)象,由于是靜態(tài)變量伐弹,因此全局只有一個(gè)拉馋。
public static Object monitor = new Object(); synchronized (monitor) {}
58、如何檢測(cè)死鎖惨好?
死鎖的四個(gè)必要條件:
1)互斥條件:進(jìn)程對(duì)所分配到的資源進(jìn)行排他性控制煌茴,即在一段時(shí)間內(nèi)某資源僅為一個(gè)進(jìn)程所占有。此時(shí)若有其他進(jìn)程請(qǐng)求該資源日川,則請(qǐng)求進(jìn)程只能等待蔓腐。
2)請(qǐng)求和保持條件:進(jìn)程已經(jīng)獲得了至少一個(gè)資源,但又對(duì)其他資源發(fā)出請(qǐng)求逗鸣,而該資源已被其他進(jìn)程占有合住,此時(shí)該進(jìn)程的請(qǐng)求被阻塞绰精,但又對(duì)自己獲得的資源保持不放撒璧。
3)不可剝奪條件:進(jìn)程已獲得的資源在未使用完畢之前透葛,不可被其他進(jìn)程強(qiáng)行剝奪,只能由自己釋放卿樱。
4)環(huán)路等待條件:存在一種進(jìn)程資源的循環(huán)等待鏈僚害,鏈中每一個(gè)進(jìn)程已獲得的資源同時(shí)被 鏈中下一個(gè)進(jìn)程所請(qǐng)求。即存在一個(gè)處于等待狀態(tài)的進(jìn)程集合{Pl, P2, …, pn}繁调,其中 Pi 等待的資源被 P(i+1) 占有(i=0, 1, …, n-1)萨蚕,Pn 等待的資源被 P0占 有,如下圖所示蹄胰。
59岳遥、怎么預(yù)防死鎖?
預(yù)防死鎖的方式就是打破四個(gè)必要條件中的任意一個(gè)即可裕寨。
1)打破互斥條件:在系統(tǒng)里取消互斥浩蓉。若資源不被一個(gè)進(jìn)程獨(dú)占使用,那么死鎖是肯定不會(huì)發(fā)生的宾袜。但一般來(lái)說(shuō)在所列的四個(gè)條件中捻艳,“互斥”條件是無(wú)法破壞的。因此庆猫,在死鎖預(yù)防里主要是破壞其他幾個(gè)必要條件认轨,而不去涉及破壞“互斥”條件。月培。
2)打破請(qǐng)求和保持條件:1)采用資源預(yù)先分配策略嘁字,即進(jìn)程運(yùn)行前申請(qǐng)全部資源,滿(mǎn)足則運(yùn)行杉畜,不然就等待拳锚。 2)每個(gè)進(jìn)程提出新的資源申請(qǐng)前,必須先釋放它先前所占有的資源寻行。
3)打破不可剝奪條件:當(dāng)進(jìn)程占有某些資源后又進(jìn)一步申請(qǐng)其他資源而無(wú)法滿(mǎn)足霍掺,則該進(jìn)程必須釋放它原來(lái)占有的資源。
4)打破環(huán)路等待條件:實(shí)現(xiàn)資源有序分配策略拌蜘,將系統(tǒng)的所有資源統(tǒng)一編號(hào)杆烁,所有進(jìn)程只能采用按序號(hào)遞增的形式申請(qǐng)資源。
60简卧、為什么要使用線(xiàn)程池兔魂?直接new個(gè)線(xiàn)程不是很舒服?
如果我們?cè)诜椒ㄖ兄苯觧ew一個(gè)線(xiàn)程來(lái)處理举娩,當(dāng)這個(gè)方法被調(diào)用頻繁時(shí)就會(huì)創(chuàng)建很多線(xiàn)程析校,不僅會(huì)消耗系統(tǒng)資源构罗,還會(huì)降低系統(tǒng)的穩(wěn)定性,一不小心把系統(tǒng)搞崩了智玻,就可以直接去財(cái)務(wù)那結(jié)帳了遂唧。
如果我們合理的使用線(xiàn)程池,則可以避免把系統(tǒng)搞崩的窘境吊奢「桥恚總得來(lái)說(shuō),使用線(xiàn)程池可以帶來(lái)以下幾個(gè)好處:
降低資源消耗页滚。通過(guò)重復(fù)利用已創(chuàng)建的線(xiàn)程召边,降低線(xiàn)程創(chuàng)建和銷(xiāo)毀造成的消耗。
提高響應(yīng)速度裹驰。當(dāng)任務(wù)到達(dá)時(shí)隧熙,任務(wù)可以不需要等到線(xiàn)程創(chuàng)建就能立即執(zhí)行。
增加線(xiàn)程的可管理型幻林。線(xiàn)程是稀缺資源贞盯,使用線(xiàn)程池可以進(jìn)行統(tǒng)一分配,調(diào)優(yōu)和監(jiān)控滋将。
61邻悬、線(xiàn)程池的核心屬性有哪些?
threadFactory(線(xiàn)程工廠(chǎng)):用于創(chuàng)建工作線(xiàn)程的工廠(chǎng)随闽。
corePoolSize(核心線(xiàn)程數(shù)):當(dāng)線(xiàn)程池運(yùn)行的線(xiàn)程少于 corePoolSize 時(shí)父丰,將創(chuàng)建一個(gè)新線(xiàn)程來(lái)處理請(qǐng)求,即使其他工作線(xiàn)程處于空閑狀態(tài)掘宪。
workQueue(隊(duì)列):用于保留任務(wù)并移交給工作線(xiàn)程的阻塞隊(duì)列蛾扇。
maximumPoolSize(最大線(xiàn)程數(shù)):線(xiàn)程池允許開(kāi)啟的最大線(xiàn)程數(shù)。
handler(拒絕策略):往線(xiàn)程池添加任務(wù)時(shí)魏滚,將在下面兩種情況觸發(fā)拒絕策略:1)線(xiàn)程池運(yùn)行狀態(tài)不是 RUNNING镀首;2)線(xiàn)程池已經(jīng)達(dá)到最大線(xiàn)程數(shù),并且阻塞隊(duì)列已滿(mǎn)時(shí)鼠次。
keepAliveTime(保持存活時(shí)間):如果線(xiàn)程池當(dāng)前線(xiàn)程數(shù)超過(guò) corePoolSize更哄,則多余的線(xiàn)程空閑時(shí)間超過(guò) keepAliveTime 時(shí)會(huì)被終止。
62腥寇、說(shuō)下線(xiàn)程池的運(yùn)作流程成翩。
63、線(xiàn)程池有哪些拒絕策略赦役?
AbortPolicy:中止策略麻敌。默認(rèn)的拒絕策略,直接拋出 RejectedExecutionException掂摔。調(diào)用者可以捕獲這個(gè)異常术羔,然后根據(jù)需求編寫(xiě)自己的處理代碼赢赊。
DiscardPolicy:拋棄策略。什么都不做级历,直接拋棄被拒絕的任務(wù)释移。
DiscardOldestPolicy:拋棄最老策略。拋棄阻塞隊(duì)列中最老的任務(wù)鱼喉,相當(dāng)于就是隊(duì)列中下一個(gè)將要被執(zhí)行的任務(wù)秀鞭,然后重新提交被拒絕的任務(wù)趋观。如果阻塞隊(duì)列是一個(gè)優(yōu)先隊(duì)列扛禽,那么“拋棄最舊的”策略將導(dǎo)致拋棄優(yōu)先級(jí)最高的任務(wù),因此最好不要將該策略和優(yōu)先級(jí)隊(duì)列放在一起使用皱坛。
CallerRunsPolicy:調(diào)用者運(yùn)行策略编曼。在調(diào)用者線(xiàn)程中執(zhí)行該任務(wù)。該策略實(shí)現(xiàn)了一種調(diào)節(jié)機(jī)制剩辟,該策略既不會(huì)拋棄任務(wù)掐场,也不會(huì)拋出異常,而是將任務(wù)回退到調(diào)用者(調(diào)用線(xiàn)程池執(zhí)行任務(wù)的主線(xiàn)程)贩猎,由于執(zhí)行任務(wù)需要一定時(shí)間熊户,因此主線(xiàn)程至少在一段時(shí)間內(nèi)不能提交任務(wù),從而使得線(xiàn)程池有時(shí)間來(lái)處理完正在執(zhí)行的任務(wù)吭服。
70嚷堡、List、Set艇棕、Map三者的區(qū)別?
List(對(duì)付順序的好幫手): List 接口存儲(chǔ)一組不唯一(可以有多個(gè)元素引用相同的對(duì)象)蝌戒、有序的對(duì)象。
Set(注重獨(dú)一無(wú)二的性質(zhì)):不允許重復(fù)的集合沼琉,不會(huì)有多個(gè)元素引用相同的對(duì)象北苟。
Map(用Key來(lái)搜索的專(zhuān)業(yè)戶(hù)): 使用鍵值對(duì)存儲(chǔ)。Map 會(huì)維護(hù)與 Key 有關(guān)聯(lián)的值打瘪。兩個(gè) Key可以引用相同的對(duì)象友鼻,但 Key 不能重復(fù),典型的 Key 是String類(lèi)型闺骚,但也可以是任何對(duì)象彩扔。
71、ArrayList 和 LinkedList 的區(qū)別葛碧。
ArrayList 底層基于動(dòng)態(tài)數(shù)組實(shí)現(xiàn)借杰,LinkedList 底層基于鏈表實(shí)現(xiàn)。
對(duì)于按 index 索引數(shù)據(jù)(get/set方法):ArrayList 通過(guò) index 直接定位到數(shù)組對(duì)應(yīng)位置的節(jié)點(diǎn)进泼,而 LinkedList需要從頭結(jié)點(diǎn)或尾節(jié)點(diǎn)開(kāi)始遍歷蔗衡,直到尋找到目標(biāo)節(jié)點(diǎn)纤虽,因此在效率上 ArrayList 優(yōu)于 LinkedList。
對(duì)于隨機(jī)插入和刪除:ArrayList 需要移動(dòng)目標(biāo)節(jié)點(diǎn)后面的節(jié)點(diǎn)(使用System.arraycopy 方法移動(dòng)節(jié)點(diǎn))绞惦,而 LinkedList 只需修改目標(biāo)節(jié)點(diǎn)前后節(jié)點(diǎn)的 next 或 prev 屬性即可逼纸,因此在效率上 LinkedList 優(yōu)于 ArrayList。
對(duì)于順序插入和刪除:由于 ArrayList 不需要移動(dòng)節(jié)點(diǎn)济蝉,因此在效率上比 LinkedList 更好杰刽。這也是為什么在實(shí)際使用中 ArrayList 更多,因?yàn)榇蟛糠智闆r下我們的使用都是順序插入王滤。
72贺嫂、ArrayList 和 Vector 的區(qū)別。
Vector 和 ArrayList 幾乎一致雁乡,唯一的區(qū)別是 Vector 在方法上使用了 synchronized 來(lái)保證線(xiàn)程安全第喳,因此在性能上 ArrayList 具有更好的表現(xiàn)。
有類(lèi)似關(guān)系的還有:StringBuilder 和 StringBuffer踱稍、HashMap 和 Hashtable曲饱。
73、介紹下 HashMap 的底層數(shù)據(jù)結(jié)構(gòu)
我們現(xiàn)在用的都是 JDK 1.8珠月,底層是由“數(shù)組+鏈表+紅黑樹(shù)”組成扩淀,如下圖,而在 JDK 1.8 之前是由“數(shù)組+鏈表”組成啤挎。
74驻谆、為什么要改成“數(shù)組+鏈表+紅黑樹(shù)”?
主要是為了提升在 hash 沖突嚴(yán)重時(shí)(鏈表過(guò)長(zhǎng))的查找性能侵浸,使用鏈表的查找性能是 O(n)旺韭,而使用紅黑樹(shù)是 O(logn)。
75掏觉、那在什么時(shí)候用鏈表区端?什么時(shí)候用紅黑樹(shù)?
對(duì)于插入澳腹,默認(rèn)情況下是使用鏈表節(jié)點(diǎn)织盼。當(dāng)同一個(gè)索引位置的節(jié)點(diǎn)在新增后超過(guò)8個(gè)(閾值8):如果此時(shí)數(shù)組長(zhǎng)度大于等于 64,則會(huì)觸發(fā)鏈表節(jié)點(diǎn)轉(zhuǎn)紅黑樹(shù)節(jié)點(diǎn)(treeifyBin)酱塔;而如果數(shù)組長(zhǎng)度小于64沥邻,則不會(huì)觸發(fā)鏈表轉(zhuǎn)紅黑樹(shù),而是會(huì)進(jìn)行擴(kuò)容羊娃,因?yàn)榇藭r(shí)的數(shù)據(jù)量還比較小唐全。
對(duì)于移除,當(dāng)同一個(gè)索引位置的節(jié)點(diǎn)在移除后達(dá)到 6 個(gè),并且該索引位置的節(jié)點(diǎn)為紅黑樹(shù)節(jié)點(diǎn)邮利,會(huì)觸發(fā)紅黑樹(shù)節(jié)點(diǎn)轉(zhuǎn)鏈表節(jié)點(diǎn)(untreeify)弥雹。
76、HashMap 的默認(rèn)初始容量是多少延届?HashMap 的容量有什么限制嗎剪勿?
默認(rèn)初始容量是16。HashMap 的容量必須是2的N次方方庭,HashMap 會(huì)根據(jù)我們傳入的容量計(jì)算一個(gè)大于等于該容量的最小的2的N次方厕吉,例如傳 9,容量為16械念。
77头朱、HashMap 的插入流程是怎么樣的?
78订讼、HashMap 的擴(kuò)容(resize)流程是怎么樣的髓窜?
79扇苞、除了 HashMap欺殿,還用過(guò)哪些 Map,在使用時(shí)怎么選擇鳖敷?
80脖苏、HashMap 和Hashtable 的區(qū)別?
HashMap 允許 key 和 value 為 null,Hashtable 不允許定踱。
HashMap 的默認(rèn)初始容量為 16棍潘,Hashtable 為 11。
HashMap 的擴(kuò)容為原來(lái)的 2 倍崖媚,Hashtable 的擴(kuò)容為原來(lái)的 2 倍加 1亦歉。
HashMap 是非線(xiàn)程安全的,Hashtable是線(xiàn)程安全的畅哑。
HashMap 的 hash 值重新計(jì)算過(guò)肴楷,Hashtable 直接使用 hashCode。
HashMap 去掉了 Hashtable 中的 contains 方法荠呐。
HashMap 繼承自 AbstractMap 類(lèi)赛蔫,Hashtable 繼承自 Dictionary 類(lèi)。
90泥张、Java 內(nèi)存結(jié)構(gòu)(運(yùn)行時(shí)數(shù)據(jù)區(qū))
程序計(jì)數(shù)器:線(xiàn)程私有呵恢。一塊較小的內(nèi)存空間,可以看作當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼的行號(hào)指示器媚创。如果線(xiàn)程正在執(zhí)行的是一個(gè)Java方法渗钉,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是Native方法钞钙,這個(gè)計(jì)數(shù)器值則為空鳄橘。
Java虛擬機(jī)棧:線(xiàn)程私有粤剧。它的生命周期與線(xiàn)程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表挥唠、操作數(shù)棧抵恋、動(dòng)態(tài)鏈接、方法出口等信息宝磨。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程弧关,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程。
本地方法棧:線(xiàn)程私有唤锉。本地方法棧與虛擬機(jī)棧所發(fā)揮的作用是非常相似的世囊,它們之間的區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)窿祥。
Java堆:線(xiàn)程共享株憾。對(duì)大多數(shù)應(yīng)用來(lái)說(shuō),Java堆是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊晒衩。Java堆是被所有線(xiàn)程共享的一塊內(nèi)存區(qū)域嗤瞎,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例听系,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存贝奇。
方法區(qū):與Java堆一樣,是各個(gè)線(xiàn)程共享的內(nèi)存區(qū)域靠胜,它用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息(構(gòu)造方法掉瞳、接口定義)、常量浪漠、靜態(tài)變量陕习、即時(shí)編譯器編譯后的代碼(字節(jié)碼)等數(shù)據(jù)。方法區(qū)是JVM規(guī)范中定義的一個(gè)概念址愿,具體放在哪里该镣,不同的實(shí)現(xiàn)可以放在不同的地方。
運(yùn)行時(shí)常量池:運(yùn)行時(shí)常量池是方法區(qū)的一部分必盖。Class文件中除了有類(lèi)的版本拌牲、字段、方法歌粥、接口等描述信息外塌忽,還有一項(xiàng)信息是常量池,用于存放編譯期生成的各種字面量和符號(hào)引用失驶,這部分內(nèi)容將在類(lèi)加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放土居。
String str = new String("hello");
上面的語(yǔ)句中變量 str 放在棧上,用 new 創(chuàng)建出來(lái)的字符串對(duì)象放在堆上,而"hello"這個(gè)字面量是放在堆中擦耀。
91棉圈、什么是雙親委派模型?
如果一個(gè)類(lèi)加載器收到了類(lèi)加載的請(qǐng)求眷蜓,它首先不會(huì)自己去嘗試加載這個(gè)類(lèi)分瘾,而是把這個(gè)請(qǐng)求委派給父類(lèi)加載器去完成,每一個(gè)層次的類(lèi)加載器都是如此吁系,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類(lèi)加載器中德召,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒(méi)有找到所需的類(lèi))時(shí),子加載器才會(huì)嘗試自己去加載汽纤。
92上岗、Java虛擬機(jī)中有哪些類(lèi)加載器?
啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader):
這個(gè)類(lèi)加載器負(fù)責(zé)將存放在<JAVA_HOME>\lib目錄中的蕴坪,或者被-Xbootclasspath參數(shù)所指定的路徑中的肴掷,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別,如rt.jar背传,名字不符合的類(lèi)庫(kù)即使放在lib目錄中也不會(huì)被加載)類(lèi)庫(kù)加載到虛擬機(jī)內(nèi)存中呆瞻。
擴(kuò)展類(lèi)加載器(Extension ClassLoader):
這個(gè)加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的续室,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類(lèi)庫(kù)栋烤,開(kāi)發(fā)者可以直接使用擴(kuò)展類(lèi)加載器。
應(yīng)用程序類(lèi)加載器(Application ClassLoader):
這個(gè)類(lèi)加載器由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)挺狰。由于這個(gè)類(lèi)加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱(chēng)它為系統(tǒng)類(lèi)加載器买窟。它負(fù)責(zé)加載用戶(hù)類(lèi)路徑(ClassPath)上所指定的類(lèi)庫(kù)丰泊,開(kāi)發(fā)者可以直接使用這個(gè)類(lèi)加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類(lèi)加載器始绍,一般情況下這個(gè)就是程序中默認(rèn)的類(lèi)加載器瞳购。
自定義類(lèi)加載器:
用戶(hù)自定義的類(lèi)加載器。
93亏推、類(lèi)加載的過(guò)程
類(lèi)加載的過(guò)程包括:加載学赛、驗(yàn)證、準(zhǔn)備吞杭、解析盏浇、初始化,其中驗(yàn)證芽狗、準(zhǔn)備绢掰、解析統(tǒng)稱(chēng)為連接。
加載:通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取定義此類(lèi)的二進(jìn)制字節(jié)流,在內(nèi)存中生成一個(gè)代表這個(gè)類(lèi)的java.lang.Class對(duì)象滴劲。
驗(yàn)證:確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。
準(zhǔn)備:為靜態(tài)變量分配內(nèi)存并設(shè)置靜態(tài)變量初始值侄刽,這里所說(shuō)的初始值“通常情況”下是數(shù)據(jù)類(lèi)型的零值泉孩。
解析:將常量池內(nèi)的符號(hào)引用替換為直接引用。
初始化:到了初始化階段萧芙,才真正開(kāi)始執(zhí)行類(lèi)中定義的 Java 初始化程序代碼碴萧。主要是靜態(tài)變量賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{})中的語(yǔ)句。
94末购、介紹下垃圾收集機(jī)制(在什么時(shí)候破喻,對(duì)什么,做了什么)盟榴?
在什么時(shí)候曹质?
在觸發(fā)GC的時(shí)候,具體如下擎场,這里只說(shuō)常見(jiàn)的 Young GC 和 Full GC羽德。
觸發(fā)Young GC:當(dāng)新生代中的 Eden 區(qū)沒(méi)有足夠空間進(jìn)行分配時(shí)會(huì)觸發(fā)Young GC。
觸發(fā)Full GC:
當(dāng)準(zhǔn)備要觸發(fā)一次Young GC時(shí)迅办,如果發(fā)現(xiàn)統(tǒng)計(jì)數(shù)據(jù)說(shuō)之前Young GC的平均晉升大小比目前老年代剩余的空間大宅静,則不會(huì)觸發(fā)Young GC而是轉(zhuǎn)為觸發(fā)Full GC。(通常情況)
如果有永久代的話(huà)站欺,在永久代需要分配空間但已經(jīng)沒(méi)有足夠空間時(shí)姨夹,也要觸發(fā)一次Full GC。
System.gc()默認(rèn)也是觸發(fā)Full GC矾策。
heap dump帶GC默認(rèn)也是觸發(fā)Full GC磷账。
CMS GC時(shí)出現(xiàn)Concurrent Mode Failure會(huì)導(dǎo)致一次Full GC的產(chǎn)生。
對(duì)什么贾虽?
對(duì)那些JVM認(rèn)為已經(jīng)“死掉”的對(duì)象逃糟。即從GC Root開(kāi)始搜索,搜索不到的蓬豁,并且經(jīng)過(guò)一次篩選標(biāo)記沒(méi)有復(fù)活的對(duì)象绰咽。
做了什么?
對(duì)這些JVM認(rèn)為已經(jīng)“死掉”的對(duì)象進(jìn)行垃圾收集地粪,新生代使用復(fù)制算法取募,老年代使用標(biāo)記-清除和標(biāo)記-整理算法。
95驶忌、GC Root有哪些?
在Java語(yǔ)言中矛辕,可作為GC Roots的對(duì)象包括下面幾種:
虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象笑跛。
方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象。
方法區(qū)中常量引用的對(duì)象聊品。
本地方法棧中JNI(即一般說(shuō)的Native方法)引用的對(duì)象飞蹂。
96、垃圾收集有哪些算法翻屈,各自的特點(diǎn)陈哑?
標(biāo)記 - 清除算法
首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象伸眶。它的主要不足有兩個(gè):一個(gè)是效率問(wèn)題惊窖,標(biāo)記和清除兩個(gè)過(guò)程的效率都不高;另一個(gè)是空間問(wèn)題厘贼,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片界酒,空間碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行過(guò)程中需要分配較大對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作嘴秸。
復(fù)制算法
為了解決效率問(wèn)題毁欣,一種稱(chēng)為“復(fù)制”(Copying)的收集算法出現(xiàn)了,它將可用內(nèi)存按容量劃分為大小相等的兩塊岳掐,每次只使用其中的一塊凭疮。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面串述,然后再把已使用過(guò)的內(nèi)存空間一次清理掉执解。這樣使得每次都是對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況纲酗,只要移動(dòng)堆頂指針衰腌,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單耕姊,運(yùn)行高效桶唐。只是這種算法的代價(jià)是將內(nèi)存縮小為了原來(lái)的一半,未免太高了一點(diǎn)茉兰。
標(biāo)記 - 整理算法
復(fù)制收集算法在對(duì)象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作,效率將會(huì)變低欣簇。更關(guān)鍵的是规脸,如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保熊咽,以應(yīng)對(duì)被使用的內(nèi)存中所有對(duì)象都100%存活的極端情況莫鸭,所以在老年代一般不能直接選用這種算法。
根據(jù)老年代的特點(diǎn)横殴,有人提出了另外一種“標(biāo)記-整理”(Mark-Compact)算法被因,標(biāo)記過(guò)程仍然與“標(biāo)記-清除”算法一樣卿拴,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng)梨与,然后直接清理掉端邊界以外的內(nèi)存堕花。
分代收集算法
當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”(Generational Collection)算法,這種算法并沒(méi)有什么新的思想粥鞋,只是根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊缘挽。
一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/p>
在新生代中呻粹,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去壕曼,只有少量存活,那就選用復(fù)制算法等浊,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集腮郊。
在老年代中因?yàn)閷?duì)象存活率高、沒(méi)有額外空間對(duì)它進(jìn)行分配擔(dān)保筹燕,就必須使用標(biāo)記—清理或者標(biāo)記—整理算法來(lái)進(jìn)行回收轧飞。
最后
金三銀四的季節(jié),相信有不少同學(xué)正準(zhǔn)備跳槽庄萎。
我將我最近的原創(chuàng)的文章進(jìn)行了匯總踪少,其中有不少面試高頻題目解析,很多都是我自己在面試大廠(chǎng)時(shí)遇到的糠涛,我在對(duì)每個(gè)題目解析時(shí)都會(huì)按較高的標(biāo)準(zhǔn)進(jìn)行深入剖析援奢,可能只看一遍并不能完全明白,但是相信反復(fù)閱讀忍捡,定能有所收獲集漾。