一、JVM
1. JDK、JRE、JVM三者間的關(guān)系
JDK(Java Development Kit)是Java開發(fā)工具包,是整個(gè)JAVA的核心,包括了Java運(yùn)行環(huán)境JRE(Java Runtime Envirnment)、一堆Java工具(javac/java/jdb等)和Java基礎(chǔ)的類庫(即Java API 包括rt.jar)。
JRE是運(yùn)行基于Java語言編寫的程序所不可缺少的運(yùn)行環(huán)境。JRE中包含了JVM,runtime class libraries和Java application launcher,這些是運(yùn)行Java程序的必要組件。JRE是Java運(yùn)行環(huán)境,并不是一個(gè)開發(fā)環(huán)境,所以沒有包含任何開發(fā)工具(如編譯器和調(diào)試器),只是針對于使用Java程序的用戶。
JVM(java virtual machine)就是我們常說的java虛擬機(jī),它是整個(gè)java實(shí)現(xiàn)跨平臺的最核心的部分,所有的java程序會首先被編譯為.class的類文件,這種類文件可以在虛擬機(jī)上執(zhí)行。也就是說class并不直接與機(jī)器的操作系統(tǒng)相對應(yīng),而是經(jīng)過虛擬機(jī)間接與操作系統(tǒng)交互,由虛擬機(jī)將程序解釋給本地系統(tǒng)執(zhí)行。只有JVM還不能成class的執(zhí)行,因?yàn)樵诮忉宑lass的時(shí)候JVM需要調(diào)用解釋所需要的類庫lib,而jre包含lib類庫。
2. JVM四部分組成
整個(gè)JVM框架由類加載器加載文件,執(zhí)行器在內(nèi)存中處理數(shù)據(jù),交互時(shí)通過本地接口。
[1] ExecutionEngine:執(zhí)行引擎,又叫解釋器
負(fù)責(zé)解釋命令,提交操作系統(tǒng)執(zhí)行。
[2] NaiveInterface本地接口
作用:融合不同的語言為java所用。
[3] Runtimedataarea運(yùn)行數(shù)據(jù)區(qū)
是整個(gè)JVM的重點(diǎn),所有程序都被加載到這里運(yùn)行。
[4] ClassLoader:類加載器
流程:Java文件>JDK編譯>.class文件>classloader>加載到內(nèi)存中
n ClassLoader主要對類的請求提供服務(wù),當(dāng)JVM需要某類時(shí),它根據(jù)名稱向ClassLoader要求這個(gè)類,ClassLoader將描述該類的Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn),解析和初始化,最終形成能被java虛擬機(jī)直接使用的java類型。
n classloader具備層次關(guān)系:
a) 引導(dǎo)類加載器(bootstrap class loader)
他用類加載java 的核心庫(String 、Integer、List。。。)在jre/lib/rt.jar路徑下的內(nèi)容,是用C代碼來實(shí)現(xiàn)的,并不繼承自java.lang.ClassLoader。
加載擴(kuò)展類和應(yīng)用程序類加載器。并指定他們的父類加載器。
b) 擴(kuò)展類加載器(extensions class loader)
用來加載java的擴(kuò)展庫(jre/ext/*.jar路徑下的內(nèi)容)java虛擬機(jī)的實(shí)現(xiàn)會自動(dòng)提供一個(gè)擴(kuò)展目錄。該類加載器在此目錄里面查找并加載java類。
c) 應(yīng)用程序類加載器(application class loader)
他根據(jù)java應(yīng)用的類路徑(classpath路徑),我們編寫的應(yīng)用類默認(rèn)情況下都是通過AppClassLoader進(jìn)行加載的。當(dāng)我們使用 new關(guān)鍵字或者Class.forName來加載類時(shí),所要加載的類都是由調(diào)用new或者Class.forName的類的類加載器(也是 AppClassLoader)進(jìn)行加載的。
ApplicationClassLoader由于是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱為SystemClassLoader.
d) 自定義類加載器
為了能夠繞過Java類的既定加載過程,(例如為了達(dá)到類庫的互相隔離,例如為了達(dá)到熱部署重加載功能。),開發(fā)人員可以通過繼承java.lang.ClassLoader類的方式實(shí)現(xiàn)自己的類加載器,并在其中對類的加載過程進(jìn)行完全的控制和管理
n Java類加載雙親委派機(jī)制
? 在Java中,任意一個(gè)類都需要由加載它的類加載器和這個(gè)類本身一同確定其在java虛擬機(jī)中的唯一性,即比較兩個(gè)類是否相等,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提之下才有意義,否則,即使這兩個(gè)類來源于同一個(gè)Class類文件,只要加載它的類加載器不相同,那么這兩個(gè)類必定不相等(這里的相等包括代表類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceof關(guān)鍵字的結(jié)果)。
? 如果一個(gè)類加載器收到了類加載的請求,它不會自己去嘗試加載這個(gè)類,而是把這個(gè)請求委派給父類加載器去完成,這樣層層遞進(jìn),最終所有的加載請求都被傳到最頂層的啟動(dòng)類加載器中,只有當(dāng)父類加載器無法完成這個(gè)加載請求(它的搜索范圍內(nèi)沒有找到所需的類)時(shí),才會交給子類加載器去嘗試加載.
? 這樣的好處是:java類隨著它的類加載器一起具備了帶有優(yōu)先級的層次關(guān)系.這是十分必要的,比如Object類,它是所有java類的父類,因此無論哪個(gè)類加載都要加載這個(gè)類,最終所有的加載請求都匯總到頂層的啟動(dòng)類加載器中,因此Object類會由啟動(dòng)類加載器來加載,所以加載的都是同一個(gè)類,如果不使用雙親委派模型,由各個(gè)類加載器自行去加載的話,系統(tǒng)中就會出現(xiàn)不止一個(gè)Object類,應(yīng)用程序就會全亂了.
3. java類加載過程
[1] 加載
1. 通過一個(gè)類的全限定名獲取該類的二進(jìn)制流。
2. 將該二進(jìn)制流中的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法去運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
3. 在內(nèi)存中生成該類的Class對象,作為該類的數(shù)據(jù)訪問入口。
[2] 驗(yàn)證
驗(yàn)證的目的是為了確保Class文件的字節(jié)流中的信息不回危害到虛擬機(jī).在該階段主要完成以下四鐘驗(yàn)證:
1. 文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件的規(guī)范,如主次版本號是否在當(dāng)前虛擬機(jī)范圍內(nèi),常量池中的常量是否有不被支持的類型.
2. 元數(shù)據(jù)驗(yàn)證:對字節(jié)碼描述的信息進(jìn)行語義分析,如這個(gè)類是否有父類,是否集成了不被繼承的類等。
3. 字節(jié)碼驗(yàn)證:是整個(gè)驗(yàn)證過程中最復(fù)雜的一個(gè)階段,通過驗(yàn)證數(shù)據(jù)流和控制流的分析,確定程序語義是否正確,主要針對方法體的驗(yàn)證。如:方法中的類型轉(zhuǎn)換是否正確,跳轉(zhuǎn)指令是否正確等。
4. 符號引用驗(yàn)證:這個(gè)動(dòng)作在后面的解析過程中發(fā)生,主要是為了確保解析動(dòng)作能正確執(zhí)行。
[3] 準(zhǔn)備
準(zhǔn)備階段是為類的靜態(tài)變量分配內(nèi)存并將其初始化為默認(rèn)值,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。準(zhǔn)備階段不分配類中的實(shí)例變量的內(nèi)存,實(shí)例變量將會在對象實(shí)例化時(shí)隨著對象一起分配在Java堆中。
public static int value=123;//在準(zhǔn)備階段value初始值為0 。在初始化階段才會變?yōu)?23 。
[4] 解析
該階段主要完成符號引用到直接引用的轉(zhuǎn)換動(dòng)作。解析動(dòng)作并不一定在初始化動(dòng)作完成之前,也有可能在初始化之后。
[5] 初始化
初始化時(shí)類加載的最后一步,前面的類加載過程,除了在加載階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java程序代碼。
4. JVM內(nèi)存結(jié)構(gòu)
[1] 方法區(qū)(又叫靜態(tài)區(qū)):
所有線程共享方法區(qū)。用于存放類的元數(shù)據(jù)(即:編譯后的代碼、類的信息、常量池、字段信息、方法信息、靜態(tài)變量),并不是類的Class對象!Class對象是加載的最終產(chǎn)品。方法區(qū)有時(shí)候也稱為永久代,在該區(qū)內(nèi)很少發(fā)生垃圾回收,但是并不代表不發(fā)生GC,在這里進(jìn)行的GC主要是對方法區(qū)里的常量池和對類型的卸載
[2] 堆:
所有線程共享堆區(qū)。用于存儲所有new出來的對象,此對象最終由垃圾收集器收集,垃圾收集器針對的就是堆區(qū)
[3] 虛擬機(jī)棧(Java棧、占內(nèi)存):
線程私有,生命周期和線程相同。每個(gè)方法被調(diào)用的時(shí)候都會創(chuàng)建一個(gè)棧幀,用于存儲局部變量表、操作棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法從被調(diào)用直至執(zhí)行完成的過程就對應(yīng)著一個(gè)棧幀在虛擬機(jī)中從入棧到出棧的過程。
[4] 本地方法棧:
和java棧的作用差不多,只不過是為JVM使用到的native方法服務(wù)的,存儲了每個(gè)native方法調(diào)用的狀態(tài)
[5] 程序計(jì)數(shù)器:
線程私有,用于保存當(dāng)前線程執(zhí)行的內(nèi)存地址。由于JVM程序是多線程執(zhí)行的(線程輪流切換),所以為了保證線程切換回來后,還能恢復(fù)到原先狀態(tài),就需要一個(gè)獨(dú)立的計(jì)數(shù)器,記錄之前中斷的地方。
5. Java中Native關(guān)鍵字
native是在java和其他語言(如c++)進(jìn)行協(xié)作時(shí)使用的,也就是native后的函數(shù)的實(shí)現(xiàn)不是用java寫的。
Java不是完美的,Java的不足除了體現(xiàn)在運(yùn)行速度上要比傳統(tǒng)的C++慢許多之外,Java無法直接訪問到操作系統(tǒng)底層(如系統(tǒng)硬件等),java要實(shí)現(xiàn)對底層的控制,就需要一些其他語言的幫助,為此Java使用native方法來擴(kuò)展Java程序的功能。
其實(shí)現(xiàn)步驟:
1、在Java中聲明native()方法,然后編譯;
2、用javah產(chǎn)生一個(gè).h文件;
3、寫一個(gè).cpp文件實(shí)現(xiàn)native導(dǎo)出方法,其中需要包含第二步產(chǎn)生的.h文件(注意其中又包含了JDK帶的jni.h文件);
4、將第三步的.cpp文件編譯成動(dòng)態(tài)鏈接庫文件;
5、在Java中用System.loadLibrary()方法加載第四步產(chǎn)生的動(dòng)態(tài)鏈接庫文件,這個(gè)native()方法就可以在Java中被訪問了。
6. JVM中的年輕代 老年代 持久代 gc
虛擬機(jī)中的共劃分為三個(gè)代:年輕代、老年代和持久代(永久代)。其中持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關(guān)系不大。年輕代和老年代的劃分是對垃圾收集影響比較大的。
你出生在 Eden 區(qū),在 Eden 區(qū)有許多和你差不多的小兄弟、小姐妹,可以把 Eden 區(qū)當(dāng)成幼兒園,在這個(gè)幼兒園里大家玩了很長時(shí)間。Eden 區(qū)不能無休止地放你們在里面,當(dāng)幼兒園滿了,你就要被送到學(xué)校去上學(xué),這里假設(shè)從小學(xué)到高中都稱為 Survivor 區(qū)。開始的時(shí)候你在 Survivor 區(qū)里面劃分出來的的“From”區(qū),讀到高年級了,就進(jìn)了 Survivor 區(qū)的“To”區(qū),中間由于學(xué)習(xí)成績不穩(wěn)定,還經(jīng)常來回折騰。直到你 18 歲的時(shí)候,高中畢業(yè)了,該去社會上闖闖了。于是你就去了年老代,年老代里面人也很多。在年老代里,你生活了 20 年 (每次 GC 加一歲),最后壽終正寢,被 GC 回收。有一點(diǎn)沒有提,你在年老代遇到了一個(gè)同學(xué),他的名字叫愛德華 (慕光之城里的帥哥吸血鬼),他以及他的家族永遠(yuǎn)不會死,那么他們就生活在永生代。
l 年輕代:所有新生成的對象首先都是放在年輕代的。年輕代的目標(biāo)就是盡可能快速的收集掉那些生命周期短的對象。年輕代分三個(gè)區(qū)。一個(gè)Eden區(qū),兩個(gè)Survivor區(qū)(一般而言)。大部分對象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí),還存活的對象將被復(fù)制到Survivor區(qū)(兩個(gè)中的一個(gè)),當(dāng)這個(gè)Survivor區(qū)滿時(shí),此區(qū)的存活對象將被復(fù)制到另外一個(gè)Survivor區(qū),當(dāng)這個(gè)Survivor區(qū)也滿了的時(shí)候,從第一個(gè)Survivor區(qū)復(fù)制過來的并且此時(shí)還存活的對象,將被復(fù)制“年老區(qū)(Tenured)”。需要注意,Survivor的兩個(gè)區(qū)是對稱的,沒先后關(guān)系,所以同一個(gè)區(qū)中可能同時(shí)存在從Eden復(fù)制過來對象,和從前一個(gè)Survivor復(fù)制過來的對象,而復(fù)制到年老區(qū)的只有從第一個(gè)Survivor去過來的對象。而且,Survivor區(qū)總有一個(gè)是空的。同時(shí),根據(jù)程序需要,Survivor區(qū)是可以配置為多個(gè)的(多于兩個(gè)),這樣可以增加對象在年輕代中的存在時(shí)間,減少被放到年老代的可能。
l 年老代:在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對象,就會被放到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長的對象。
l 持久代:用于存放靜態(tài)文件,如Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如Hibernate等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來存放這些運(yùn)行過程中新增的類。持久代大小通過-XX:MaxPermSize=<N>進(jìn)行設(shè)置。
2 新生代進(jìn)行一次垃圾清理,被稱為youngGC或者minorGC,頻率高,執(zhí)行快!
2 老年代進(jìn)行一次垃圾清理,被稱為FULLGC或者majorGC,比年輕代的慢10倍
2 JVM優(yōu)化的一個(gè)原則就是:降低youngGC的頻率、減少FULLGC的次數(shù)。
n JVM內(nèi)存參數(shù)
-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M
-vmargs 說明后面是VM的參數(shù),所以后面的其實(shí)都是JVM的參數(shù)了
-Xms128m JVM初始分配的堆內(nèi)存
-Xmx512m JVM最大允許分配的堆內(nèi)存,按需分配
-XX:PermSize=64M JVM初始分配的非堆內(nèi)存
-XX:MaxPermSize=128M JVM最大允許分配的非堆內(nèi)存,按需分配
u JVM內(nèi)存配置參數(shù)
-Xmx10240m-Xms10240m-Xmn5120m-XXSurvivorRatio=3
-Xmx10240m:代表最大堆
-Xms10240m:代表初始/最小堆
-Xmn5120m:代表新生代
-XXSurvivorRatio=3:代表Eden:Survivor=3根據(jù)Generation-Collection算法(目前大部分JVM采用的算法),一般根據(jù)對象的生存周期將堆內(nèi)存分為若干不同的區(qū)域,一般情況將新生代分為Eden,兩塊Survivor;計(jì)算Survivor大小,Eden:Survivor=3,總大小為5120,3x+x+x=5120x=1024,則Surviver總大小為2048m。
? 堆(Heap)和非堆(Non-heap)內(nèi)存
按照官方的說法:“Java虛擬機(jī)具有一個(gè)堆,堆是運(yùn)行時(shí)數(shù)據(jù)區(qū)域,所有類實(shí)例和數(shù)組的內(nèi)存均從此處分配。堆是在Java虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的。”“在JVM中堆之外的內(nèi)存稱為非堆內(nèi)存(Non-heap memory)”。
可以看出JVM主要管理兩種類型的內(nèi)存
7. 何時(shí)會拋出OutOfMemoryException
并不是內(nèi)存被耗空的時(shí)候才拋出
JVM98%的時(shí)間都花費(fèi)在內(nèi)存回收
每次回收的內(nèi)存小于2%
8. 內(nèi)存泄漏與內(nèi)存溢出
l 內(nèi)存泄露是指你的應(yīng)用使用資源之后沒有及時(shí)釋放,導(dǎo)致應(yīng)用內(nèi)存中持有了無用的資源,這是一種狀態(tài)描述;
l 存溢出是指你的應(yīng)用的內(nèi)存已經(jīng)不能滿足正常使用了,堆棧已經(jīng)達(dá)到系統(tǒng)設(shè)置的最大值,進(jìn)而導(dǎo)致崩潰,這事一種結(jié)果描述;
l 通常都是由于內(nèi)存泄露導(dǎo)致堆棧內(nèi)存不斷增大,從而引發(fā)內(nèi)存溢出。
9. 對象存活判定
[1] 引用計(jì)數(shù)法
給對象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用了該對象,計(jì)數(shù)器就加1;當(dāng)引用失效,計(jì)數(shù)器就減1;任何時(shí)刻的計(jì)數(shù)器為0的對象就是不可能在被使用的對象。雖然是一個(gè)實(shí)現(xiàn)簡單有效的算法,但是很難解決對象之間循環(huán)相互引用的問題。
[2] 根搜索算法(GC Roots Tracing)
現(xiàn)在主流的JVM算法來標(biāo)記“死去”的對象。
算法的基本思路是通過一些稱為“GC-Roots”的對象,從這些節(jié)點(diǎn)往下延伸,搜索所走過的路叫引用鏈,當(dāng)一個(gè)對象沒有被引用鏈搜索到,則證明該對象不可用。如下圖Object-5\6\7是不可用的:
可用作GC-Roots的對象有:
1.方法區(qū)的靜態(tài)類型引用的對象
2.方法區(qū)的常量引用的對象
3.方法棧中引用
10. 垃圾收集的方法有哪些?
[1] 標(biāo)記-清除:
先標(biāo)記那些要被回收的對象,然后統(tǒng)一回收。這種方法很簡單,但是會有兩個(gè)主要問題:1.效率不高,需要遍歷整個(gè)堆,標(biāo)記和清除的效率都很低;2.會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導(dǎo)致以后程序在分配較大的對象時(shí),由于沒有充足的連續(xù)內(nèi)存而提前觸發(fā)一次GC動(dòng)作。3.在進(jìn)行GC時(shí)還要停掉當(dāng)前運(yùn)行的應(yīng)用程序。
[2] 復(fù)制算法:
為了解決效率問題,復(fù)制算法將可用內(nèi)存按容量劃分為相等的兩部分,然后每次只使用其中的一塊,當(dāng)一塊內(nèi)存用完時(shí),就將還存活的對象復(fù)制到第二塊內(nèi)存上,然后一次性清除完第一塊內(nèi)存,再將第二塊上的對象復(fù)制到第一塊。但是這種方式,內(nèi)存的代價(jià)太高,每次基本上都要浪費(fèi)一半的內(nèi)存。
后來將該算法進(jìn)行了改進(jìn),內(nèi)存區(qū)域不再是按照1:1去劃分,而是將內(nèi)存劃分為8:1:1三部分,較大那份內(nèi)存交Eden區(qū),其余是兩塊較小的內(nèi)存區(qū)叫Survior區(qū)。每次都會優(yōu)先使用Eden區(qū),若Eden區(qū)滿,就將對象復(fù)制到第二塊內(nèi)存區(qū)上,然后清除Eden區(qū),如果此時(shí)存活的對象太多,以至于Survivor不夠時(shí),會將這些對象通過分配擔(dān)保機(jī)制復(fù)制到老年代中。
[3] 標(biāo)記-整理
該算法主要是為了解決標(biāo)記-清除,產(chǎn)生大量內(nèi)存碎片的問題;當(dāng)對象存活率較高時(shí),也解決了復(fù)制算法的效率問題。它的不同之處就是在清除對象的時(shí)候現(xiàn)將可回收對象移動(dòng)到一端,然后清除掉端邊界以外的對象,這樣就不會產(chǎn)生內(nèi)存碎片了。
11. GC垃圾回收機(jī)制
如果一個(gè)對象不在被直接或間接地引用,那么這個(gè)對象就成為了「垃圾」(Garbage),它占用的內(nèi)存需要及時(shí)地釋放,否則就會引起「內(nèi)存泄露」。有些語言需要程序員來手動(dòng)釋放內(nèi)存(回收垃圾),有些語言有垃圾回收機(jī)制(GC)。
l 垃圾回收是在后臺運(yùn)行的,是作為一個(gè)單獨(dú)的低優(yōu)先級的線程運(yùn)行,不可預(yù)知的情況下對內(nèi)存堆中已經(jīng)死亡的或者長時(shí)間沒有使用的對象進(jìn)行清除和回收,程序員不能實(shí)時(shí)的調(diào)用垃圾回收器對某個(gè)對象或所有對象進(jìn)行垃圾回收,但是我們可以告訴他,盡快回收資源(System.gc和Runtime.getRuntime().gc())
l 垃圾回收器在回收某個(gè)對象的時(shí)候,首先會調(diào)用該對象的finalize方法
12. 垃圾收集器
上面有7中收集器,分為兩塊,上面為新生代收集器,下面是老年代收集器。如果兩個(gè)收集器之間存在連線,就說明它們可以搭配使用。
[1] Serial(串行GC)收集器
Serial收集器是一個(gè)新生代收集器,單線程執(zhí)行,使用復(fù)制算法。它在進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程(用戶線程)。是Jvm client模式下默認(rèn)的新生代收集器。對于限定單個(gè)CPU的環(huán)境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。
[2] ParNew(并行GC)收集器
ParNew收集器其實(shí)就是serial收集器的多線程版本,除了使用多條線程進(jìn)行垃圾收集之外,其余行為與Serial收集器一樣。
[3] Parallel Scavenge(并行回收GC)收集器
Parallel Scavenge收集器也是一個(gè)新生代收集器,它也是使用復(fù)制算法的收集器,又是并行多線程收集器。parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量。吞吐量= 程序運(yùn)行時(shí)間/(程序運(yùn)行時(shí)間 + 垃圾收集時(shí)間),虛擬機(jī)總共運(yùn)行了100分鐘。其中垃圾收集花掉1分鐘,那吞吐量就是99%。
[4] Serial Old(串行GC)收集器
Serial Old是Serial收集器的老年代版本,它同樣使用一個(gè)單線程執(zhí)行收集,使用“標(biāo)記-整理”算法。主要使用在Client模式下的虛擬機(jī)。
[5] Parallel Old(并行GC)收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法。
[6] CMS(并發(fā)GC)收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。CMS收集器是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的
[7] G1收集器
G1(Garbage First)收集器是JDK1.7提供的一個(gè)新收集器,G1收集器基于“標(biāo)記-整理”算法實(shí)現(xiàn),也就是說不會產(chǎn)生內(nèi)存碎片。還有一個(gè)特點(diǎn)之前的收集器進(jìn)行收集的范圍都是整個(gè)新生代或老年代,而G1將整個(gè)Java堆(包括新生代,老年代)。
13. JVM優(yōu)化
? 如何將新對象預(yù)留在年輕代
Full GC的成本遠(yuǎn)遠(yuǎn)高于Minor GC,因此某些情況下需要盡可能將對象分配在年輕代,可以為應(yīng)用程序分配一個(gè)合理的年輕代空間,以最大限度避免新對象直接進(jìn)入年老代的情況發(fā)生。
? 如何讓大對象進(jìn)入年老代
因?yàn)榇髮ο蟪霈F(xiàn)在年輕代很可能擾亂年輕代GC,可以使用參數(shù)-XX:PetenureSizeThreshold設(shè)置大對象直接進(jìn)入年老代的閾值。當(dāng)對象的大小超過這個(gè)值時(shí),將直接在年老代分配。
? 如何設(shè)置對象進(jìn)入年老代的年齡
堆中的每一個(gè)對象都有自己的年齡。一般情況下,年輕對象存放在年輕代,年老對象存放在年老代。為了做到這點(diǎn),虛擬機(jī)為每個(gè)對象都維護(hù)一個(gè)年齡。如果對象在Eden區(qū),經(jīng)過一次GC后依然存活,則被移動(dòng)到Survivor區(qū)中,對象年齡加1。以后,如果對象每經(jīng)過一次GC依然存活,則年齡再加1。當(dāng)對象年齡達(dá)到閾值時(shí),就移入年老代,成為老年對象。這個(gè)閾值的最大值可以通過參數(shù)-XX:MaxTenuringThreshold來設(shè)置,默認(rèn)值是15。雖然-XX:MaxTenuringThreshold的值可能是15或者更大,但這不意味著新對象非要達(dá)到這個(gè)年齡才能進(jìn)入年老代。事實(shí)上,對象實(shí)際進(jìn)入年老代的年齡是虛擬機(jī)在運(yùn)行時(shí)根據(jù)內(nèi)存使用情況動(dòng)態(tài)計(jì)算的,這個(gè)參數(shù)指定的是閾值年齡的最大值。
? 穩(wěn)定的Java堆VS動(dòng)蕩的Java堆
一般來說,穩(wěn)定的堆大小對垃圾回收是有利的。獲得一個(gè)穩(wěn)定的堆大小的方法是使-Xms和-Xmx的大小一致,即最大堆和最小堆(初始堆)一樣。如果這樣設(shè)置,系統(tǒng)在運(yùn)行時(shí)堆大小理論上是恒定的,穩(wěn)定的堆空間可以減少GC的次數(shù)。因此,很多服務(wù)端應(yīng)用都會將最大堆和最小堆設(shè)置為相同的數(shù)值。但是,一個(gè)不穩(wěn)定的堆并非毫無用處。穩(wěn)定的堆大小雖然可以減少GC次數(shù),但同時(shí)也增加了每次GC的時(shí)間。讓堆大小在一個(gè)區(qū)間中震蕩,在系統(tǒng)不需要使用大內(nèi)存時(shí),壓縮堆空間,使GC應(yīng)對一個(gè)較小的堆,可以加快單次GC的速度?;谶@樣的考慮,JVM還提供了兩個(gè)參數(shù)用于壓縮和擴(kuò)展堆空間。
? 增大吞吐量提升系統(tǒng)性能
吞吐量優(yōu)先的方案將會盡可能減少系統(tǒng)執(zhí)行垃圾回收的總時(shí)間,故可以考慮關(guān)注系統(tǒng)吞吐量的并行回收收集器。在擁有高性能的計(jì)算機(jī)上,進(jìn)行吞吐量優(yōu)先優(yōu)化
二、Java基礎(chǔ)
14. public class和class的區(qū)別
class的定義有兩種方式: public class類名------------class類名 采用public class來聲明class,那么文件名必須和類名完全一致(包括大小寫),如果文件名和類名不一致,將會出現(xiàn)錯(cuò)誤。在一個(gè)java源文件中只能有一個(gè)class被public修飾。
15. Java標(biāo)識符的命名規(guī)則
a)標(biāo)識符是由,數(shù)字,字母,下劃線和美元符號構(gòu)成,其他符號不可以
b)必須以字母、下劃線或美元符號開頭,不能以數(shù)字開頭
16. 訪問控制權(quán)限
17. 數(shù)據(jù)類型
基本數(shù)據(jù)類型叫做原生類!
類中聲明的變量有默認(rèn)初始值;
方法中聲明的變量沒有默認(rèn)初始值,必須在定義時(shí)初始化,否則在訪問該變量時(shí)會出錯(cuò)。
自動(dòng)轉(zhuǎn)換:(byte,short,char)àintàlong(L)àfloat(F)àdouble
強(qiáng)制轉(zhuǎn)換會導(dǎo)致溢出,精度降低
18. Integer與int的區(qū)別
1. 包裝類、基本數(shù)據(jù)類型
2. Null,0
3. Integer中包含更多的與整數(shù)操作相關(guān)的方法。
19. Math.round();
Math.ceil():向上取整
Math.floor();向下取整
Math.round();四舍五入:+0.5后向下取整
20. Unicode與utf-8的區(qū)別
計(jì)算機(jī)內(nèi)只能保存101010等二進(jìn)制數(shù)據(jù),那么頁面上顯示的字符是如何顯示出來的呢?
[1] 字符集(Charset) charset=char+set,char是字符,set是集合,charset就是字符的集合。字符集就是這個(gè)編碼方式涵蓋了哪些字符,每個(gè)字符都有一個(gè)數(shù)字序號。
[2] 編碼方式(Encoding) 編碼方式就是一個(gè)字符要怎樣編碼成二進(jìn)制字節(jié)序,或者反過來怎么解析。 也即給你一個(gè)數(shù)字序號,要編碼成幾個(gè)字節(jié),字節(jié)順序如何,或者其他特殊規(guī)則。
[3] 字形字體(Font) 根據(jù)數(shù)字序號調(diào)用字體存儲的字形,就可以在頁面上顯示出來了。 所以一個(gè)字符要顯示出來,要顯示成什么樣子要看字體文件。
綜上所述,Unicode只是字符集(一個(gè)Unicode是2個(gè)字節(jié)16位),而沒有編碼方式。UTF-8是一種Unicode字符集的編碼方式,其他還有UTF-16,UTF-32等。而有了字符集以及編碼方式,如果系統(tǒng)字體是沒有這個(gè)字符,也是顯示不出來的。
21. 運(yùn)算符instanceof
//為了避免異常ClassCastException的發(fā)生,java引入了instanceof
用法:
1.instanceof運(yùn)算符的運(yùn)算結(jié)果是boolean類型
2.(引用instanceof類型)-->true/false
例如:(a instanceof Cat)//如果結(jié)果是true表示:a引用指向堆中的java對象是Cat類型.
22. ++
[1] 靜態(tài)語句塊中x為局部變量,不影響靜態(tài)變量x的值
[2] x和y為靜態(tài)變量,默認(rèn)初始值為0,屬于當(dāng)前類,其值得改變會影響整個(gè)類運(yùn)行。
[3] java中自增操作非原子性的
main方法中:
執(zhí)行x--后x=-1
調(diào)用myMethod方法,x執(zhí)行x++結(jié)果為-1(后++),但x=0,++x結(jié)果1,x=1,則y=0
x+y + ++x,先執(zhí)行x+y,結(jié)果為1,執(zhí)行++x結(jié)果為2,得到最終結(jié)果為3
*比如 a=0;(a非long和double類型)這個(gè)操作是不可分割的,那么我們說這個(gè)操作時(shí)原子操作。再比如:a++;這個(gè)操作實(shí)際是a = a + 1;是可分割的,所以他不是一個(gè)原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(shù)(sychronized)來讓它變成一個(gè)原子操作。一個(gè)操作是原子操作,那么我們稱它具有原子性。
23. ++i與i++
看一些視頻教程里面寫for循環(huán)的時(shí)候都是寫 ++i 而不是 i++,上網(wǎng)搜索了一下,原來有效率問題
++i相當(dāng)于下列代碼
i += 1;
return i;
i++相當(dāng)于下列代碼
j = i;
i += 1;
return j;
當(dāng)然如果編譯器會將這些差別都優(yōu)化掉,那么效率就都差不多了
24. 三目運(yùn)算符
數(shù)據(jù)類型轉(zhuǎn)換:
[1] 存在常量:(false?10:x)
當(dāng)后兩個(gè)表達(dá)式中有一個(gè)是常量,且另外一個(gè)類型是T,常量表達(dá)式可以用T類型表示,則輸出結(jié)果是T類型。
[2] 兩個(gè)變量:(false?i:x)
輸出精度高的類型
25. 移位運(yùn)算符
java中有三種移位運(yùn)算符
<<左移運(yùn)算符,num<<1,相當(dāng)于num乘以2
>>右移運(yùn)算符,num>>1,相當(dāng)于num除以2
>>>無符號右移,忽略符號位,空位都以0補(bǔ)齊
如果移動(dòng)的位數(shù)超過了該類型的最大位數(shù),那么編譯器會對移動(dòng)的位數(shù)取模。如對int型移動(dòng)33位,實(shí)際上只移動(dòng)了33/2=1位。
26. 運(yùn)算符優(yōu)先級
27. equals()和==
[1] = =
對于基本數(shù)據(jù)類型,等號比較的是值,對于引用類型,比較的是引用的內(nèi)存地址
[2] equals( ) //比較兩個(gè)引用的內(nèi)存地址(底層源碼就是= =)
public boolean equals(Objectob j){
return(this==obj);//比較內(nèi)存地址
}
如果一個(gè)類沒有自己定義equals方法,它默認(rèn)的equals方法(從Object類繼承的)就是使用==操作符
在現(xiàn)實(shí)的業(yè)務(wù)邏輯中,需要重寫equals方法
String類中已經(jīng)重寫了Object中的equals方法,所以比較的是具體的內(nèi)容。
下面的代碼有什么不妥之處?
1. if(username.equals(“zxx”){}
username可能為NULL,會報(bào)空指針錯(cuò)誤;改為"zxx".equals(username)
2. int x = 1;
return x==1?true:false; 這個(gè)改成return x==1;就可以!
28. 在JAVA中如何跳出當(dāng)前的多重嵌套循環(huán)
在Java中,要想跳出多重循環(huán),可以在外面的循環(huán)語句前定義一個(gè)標(biāo)號,然后在里層循環(huán)體的代碼中使用帶有標(biāo)號的break語句,即可跳出外層循環(huán)。例如,
ok:
for(int i=0;i<10;i++) {
for(int j=0;j<10;j++) {
System.out.println(“i=” + i + “,j=” + j);
if(j == 5) break ok;
}
}
另外,我個(gè)人通常并不使用標(biāo)號這種方式,而是讓外層的循環(huán)條件表達(dá)式的結(jié)果可以受到里層循環(huán)體代碼的控制,例如,要在二維數(shù)組中查找到某個(gè)數(shù)字。
int arr[][] ={{1,2,3},{4,5,6,7},{9}};
boolean found = false;
for(int i=0;i<arr.length&& !found;i++) {
for(int j=0;j<arr[i].length;j++){
System.out.println(“i=” + i + “,j=” + j);
if(arr[i][j] ==5) {
found = true;
break;
}
}
29. 面向?qū)ο蟮娜筇匦?/h3>
l 封裝:屬性私有化,對外提供公開的SET、GET方法,使程序更加健壯
l 繼承:子類可以繼承父類的東西,這樣有利于代碼的重用。
l 多態(tài):指一個(gè)類實(shí)例的相同方法在不同情形有不同表現(xiàn)形式。多態(tài)機(jī)制使具有不同內(nèi)部結(jié)構(gòu)的對象可以共享相同的外部接口。這意味著,雖然針對不同對象的具體操作不同,但通過一個(gè)公共的類,它們(那些操作)可以通過相同的方式予以調(diào)用。使用多態(tài)不但能減少編碼的工作量,還能使代碼之間的耦合度降低,大大提高程序的可維護(hù)性及可擴(kuò)展性。
關(guān)于java語言中向上轉(zhuǎn)型和向下轉(zhuǎn)型
1.向上轉(zhuǎn)型(upcasting)自動(dòng)類型轉(zhuǎn)換:子--->父Animal a1=new Cat();父類型的引用指向子類型對象
2.向下轉(zhuǎn)型(downcasting)強(qiáng)制類型轉(zhuǎn)換:父--->子Catc1=(Cat)a2;
l 多態(tài)的條件是:
a) 有繼承或?qū)崿F(xiàn)
b) 有方法的覆蓋或?qū)崿F(xiàn)
c) 父類對象(接口)指向子類對象
30. 五大原則
[1] 單一職責(zé)原則SRP(Single Responsibility Principle)
是指一個(gè)類的功能要單一,不能包羅萬象。如同一個(gè)人一樣,分配的工作不能太多,否則一天到晚雖然忙忙碌碌的,但效率卻高不起來。
[2] 開放封閉原則OCP(Open-Close Principle)
一個(gè)模塊在擴(kuò)展性方面應(yīng)該是開放的而在更改性方面應(yīng)該是封閉的。比如:一個(gè)網(wǎng)絡(luò)模塊,原來只服務(wù)端功能,而現(xiàn)在要加入客戶端功能,那么應(yīng)當(dāng)在不用修改服務(wù)端功能代碼的前提下,就能夠增加客戶端功能的實(shí)現(xiàn)代碼,這要求在設(shè)計(jì)之初,就應(yīng)當(dāng)將服務(wù)端和客戶端分開,公共部分抽象出來。
[3] 里氏替換原則LSP(the Liskov Substitution Principle LSP)
子類應(yīng)當(dāng)可以替換父類并出現(xiàn)在父類能夠出現(xiàn)的任何地方。比如:公司搞年度晚會,所有員工可以參加抽獎(jiǎng),那么不管是老員工還是新員工,也不管是總部員工還是外派員工,都應(yīng)當(dāng)可以參加抽獎(jiǎng),否則這公司就不和諧了。
[4] 依賴倒置原則DIP(the Dependency Inversion Principle DIP)
傳統(tǒng)的結(jié)構(gòu)化編程中,最上層的模塊通常都要依賴下面的子模塊來實(shí)現(xiàn),也稱為高層依賴低層!所以DIP原則就是要逆轉(zhuǎn)這種依賴關(guān)系,讓高層模塊不要依賴低層模塊,所以稱之為依賴倒置原則!
[5] 接口分離原則ISP(the Interface Segregation Principle ISP)
模塊間要通過抽象接口隔離開,而不是通過具體的類強(qiáng)耦合起來
1. 覆蓋(Override)
繼承最基本的作用:代碼重用。繼承最重要的作用:方法可以重寫。
l 方法名,返回值類型,參數(shù)列表必須相同
l 修飾符:重寫的方法不能比被重寫的方法擁有更低的訪問權(quán)限。
l 重寫的方法不能比被重寫的方法拋出更寬泛的異常。
l 私有的方法不能被覆蓋。(多態(tài)之后講)
l 構(gòu)造方法無法被覆蓋。因?yàn)闃?gòu)造方法無法被繼承。
l 靜態(tài)的方法不存在覆蓋。(多態(tài)之后講)
l 覆蓋指的是成員方法,和成員變量無關(guān)。
2. 重載(Overload)
l 方法名必須相同,
l 參數(shù)列表必須不同(類型、個(gè)數(shù)、順序)
l 返回類型、修飾符也可以不同
3. 構(gòu)造方法
[1] 不能有返回值
[2] 構(gòu)造方法的作用
a) 創(chuàng)建對象:new構(gòu)造方法名(實(shí)參);在堆中開辟空間存儲對象
b) 初始化成員變量
[3] 如果一個(gè)類沒有提供任何構(gòu)造方法,則系統(tǒng)默認(rèn)提供無參構(gòu)造方法。
如果手動(dòng)提供提供構(gòu)造方法,系統(tǒng)將不再提供任何構(gòu)造方法。
[4] 構(gòu)造方法無法被繼承,可以被重載,但不可以被覆蓋
4. 傳值與引用
[1] 不管參數(shù)類型是什么,全部是傳遞參數(shù)的副本。
如果參數(shù)是基本數(shù)據(jù)類型,則傳遞的是值的副本,如果參數(shù)是對象,則傳遞的是該對象引用的副本,因?yàn)閷ο蟠娣旁诙牙铮^基本數(shù)據(jù)類型,占內(nèi)存比較大。
[2] 成員變量在什么時(shí)候賦值?只有在調(diào)用構(gòu)造方法時(shí)才會賦值。
[3] 若把引用的副本看作是值,則有以下說法:不管是基本類型還是對象類型都值傳遞,
[4] 按值傳遞意味著當(dāng)將一個(gè)參數(shù)傳遞給一個(gè)函數(shù)時(shí),函數(shù)接收的是原始值的一個(gè)副本。因此,如果函數(shù)修改了該參數(shù),僅改變副本,而原始值保持不變。按引用傳遞意味著當(dāng)將一個(gè)參數(shù)傳遞給一個(gè)函數(shù)時(shí),函數(shù)接收的是原始值的內(nèi)存地址,而不是值的副本。因此,如果函數(shù)修改了該參數(shù),調(diào)用代碼中的原始值也隨之改變。
5. static
static表示“全局”或者“靜態(tài)”的意思,用來修飾成員變量和成員方法,也可以形成靜態(tài)static代碼塊。
類名.靜態(tài)方法名(參數(shù)列表...)
類名.靜態(tài)變量名
[1] static變量
被static修飾的變量,叫靜態(tài)變量或類變量,沒有修飾的變量,叫實(shí)例變量。
n 靜態(tài)變量在類加載時(shí)初始化,靜態(tài)變量在內(nèi)存中只有一個(gè)拷貝(節(jié)省內(nèi)存),JVM只為靜態(tài)分配一次內(nèi)存,在加載類的過程中完成靜態(tài)變量的內(nèi)存分配,可用類名直接訪問(方便),當(dāng)然也可以通過對象來訪問(但是這是不推薦的)。
n 實(shí)例變量,每創(chuàng)建一個(gè)實(shí)例,就會為實(shí)例變量分配一次內(nèi)存,實(shí)例變量可以在內(nèi)存中有多個(gè)拷貝,互不影響(靈活)。
所以一般在需要實(shí)現(xiàn)以下兩個(gè)功能時(shí)使用靜態(tài)變量:
1) 在對象之間共享值時(shí)
2) 方便訪問變量時(shí)
[2] 靜態(tài)方法
a) 靜態(tài)方法可以直接通過類名調(diào)用,任何的實(shí)例也都可以調(diào)用。
b) 一般情況下,工具類中的方法都是靜態(tài)方法。
c) 因?yàn)閟tatic方法獨(dú)立于任何實(shí)例,因此static方法必須被實(shí)現(xiàn),而不能是抽象的abstract。
d) 它們僅能調(diào)用其他的static方法、static數(shù)據(jù),在一個(gè)static方法中引用任何實(shí)例變量都是非法的
e) 它們不能以任何方式引用this或super
f) static方法是類方法,在編譯時(shí)靜態(tài)綁定的(private、static、final修飾的方法是靜態(tài)綁定的),所以他們修飾的方法不能實(shí)現(xiàn)多態(tài),當(dāng)然不需要被子類覆蓋了(子類覆蓋父類方法是為了實(shí)現(xiàn)多態(tài))。能被繼承。而其他的方法在運(yùn)行時(shí)動(dòng)態(tài)綁定。
[3] static代碼塊
static代碼塊也叫靜態(tài)代碼塊,可以有多個(gè),位置可以隨便放,它不在任何的方法體內(nèi),JVM加載類時(shí)會執(zhí)行這些靜態(tài)的代碼塊,如果static代碼塊有多個(gè),JVM將按照它們在類中出現(xiàn)的先后順序依次執(zhí)行它們,每個(gè)代碼塊只會被執(zhí)行一次。
實(shí)例語句塊,{system.out.print(“A“)}在構(gòu)造方法前執(zhí)行。
[4] static和final一塊用表示什么?
static final用來修飾成員變量和成員方法,可簡單理解為“全局常量”!
對于變量,表示一旦給值就不可修改,并且通過類名可以訪問。
對于方法,表示不可覆蓋,并且可以通過類名直接訪問。
6. this,super
[1] this
l this是引用類型,在堆中的每一個(gè)對象上都有this,保存內(nèi)存地址指向自身
this不能用在靜態(tài)環(huán)境中(static變量,static方法,static語句塊),只能用在成員方法和構(gòu)造方法中,代表當(dāng)前對象,靜態(tài)方法的執(zhí)行根本就不需要對象的存在
l this( )用在構(gòu)造方法中,用來調(diào)用另外一個(gè)構(gòu)造方法,必須在第一行
l this可以用來區(qū)分成員變量與局部變量 this.age=age;
[2] super
l super不能應(yīng)用在靜態(tài)方法中,只能應(yīng)用在成員方法和構(gòu)造方法中,(和this是一樣的)
l super(參數(shù)列表 )可以在子類構(gòu)造方法中調(diào)用父類的構(gòu)造方法,用“super(參數(shù)列表)”的方式調(diào)用,參數(shù)不是必須的。同時(shí)還要注意的一點(diǎn)是:“super(參數(shù)列表)”這條語句只能用在子類構(gòu)造方法體中的第一行。
l super.方法名(參數(shù)列表)可以在子類中調(diào)用父類的成員方法,子類的成員方法覆蓋了父類的成員方法時(shí),此時(shí),用“super.方法名(參數(shù)列表)”的方式訪問父類的方法。
l 區(qū)分變量:當(dāng)子類方法中的局部變量或成員變量與父類成員變量同名時(shí),也就是子類局部變量覆蓋父類成員變量時(shí),用“super.成員變量名”來引用父類成員變量。當(dāng)然,如果父類的成員變量沒有被覆蓋,也可以用“super.成員變量名”來引用父類成員變量,不過這是不必要的。
l 子類中沒有顯示調(diào)用構(gòu)造方法,會默認(rèn)調(diào)用直接父類的無參構(gòu)造方法,此種情況下如果父類中沒有無參構(gòu)造方法,那么編譯時(shí)將會失敗。
l 一個(gè)構(gòu)造方法第一行如果沒有this(...);也沒有顯示的去調(diào)用super(...);系統(tǒng)會默認(rèn)調(diào)用super();
l super(....)和this(....)不能共存。
l super(...);調(diào)用了父類中的構(gòu)造方法,但是并不會創(chuàng)建父類對象。
[3] this和super的異同
l super( )和this( )類似,區(qū)別是,super( )從子類中調(diào)用父類的構(gòu)造方法,this( )調(diào)用同一類的構(gòu)造方法。
l this( )和super( )不能同時(shí)出現(xiàn)在一個(gè)構(gòu)造函數(shù)里面,因?yàn)閠his( )必然會調(diào)用其它的構(gòu)造函數(shù),其它的構(gòu)造函數(shù)必然也會有super( )語句的存在,所以在同一個(gè)構(gòu)造函數(shù)里面有相同的語句,就失去了語句的意義,編譯器也不會通過。
7. 使用final關(guān)鍵字修飾一個(gè)變量時(shí),是引用不能變,還是引用的對象不能變?
使用final關(guān)鍵字修飾一個(gè)變量時(shí),是指引用變量不能變,引用變量所指向的對象中的內(nèi)容還是可以改變的。例如,對于如下語句:
final StringBuffer a=new StringBuffer("immutable"); 執(zhí)行如下語句將報(bào)告編譯期錯(cuò)誤:
a=new StringBuffer(""); 但是,執(zhí)行如下語句則可以通過編譯:
a.append(" broken!");
有人在定義方法的參數(shù)時(shí),可能想采用如下形式來阻止方法內(nèi)部修改傳進(jìn)來的參數(shù)對象:
public void method(final StringBuffer param){
}
實(shí)際上,這是辦不到的,在該方法內(nèi)部仍然可以增加如下代碼來修改參數(shù)對象:
param.append("a");
8. final、finalize、finally
通常考試時(shí)給一張紙,寫答案。三個(gè)詞的概念完全不一樣
[1] final
n final用來修飾類,無法被繼承(用final修飾的類叫做最終類?。。。?/p>
n final用來修飾方法,無法被覆蓋
n final用來修局部變量,一旦賦值,無法被改變
n final用來修成員變量,需要手動(dòng)賦值
n final修成員變量與static連用,稱為常量,常量全部大寫
[2] finalize( )
是Object一個(gè)方法的名字,垃圾回收器在回收java對象之前會先自動(dòng)調(diào)用java對象的finalize方法。
[3] finally
異常處理機(jī)制中的語句塊,finally語句塊總是會執(zhí)行。
9. 抽象類
1. 抽象類不能被final修飾
2. 抽象類無法被實(shí)例化,但是有構(gòu)造方法,構(gòu)造方法,給子類創(chuàng)建對象用
4. 抽象類中可以定義抽象方法abstract void m1();到了一個(gè)強(qiáng)制的約束作用,要求子類必須實(shí)現(xiàn)
5. 非抽象類中不可以有抽象方法
6. 一個(gè)非抽象類繼承抽象類,必須將其抽象方法全部實(shí)現(xiàn)
10. 接口
1. 接口是特殊類型的抽象類,特殊在接口是完全抽象的
2. 接口中沒有構(gòu)造方法,無法被實(shí)例化
3. 接口與接口之間可以實(shí)現(xiàn)多繼承interface emplements A,B,C{},但接口之間不可以實(shí)現(xiàn),解決了Java單繼承的問題
4. 一個(gè)類可以實(shí)現(xiàn)多個(gè)接口
5. 一個(gè)非抽象類實(shí)現(xiàn)接口,必須把接口中全部方法實(shí)現(xiàn)&覆蓋&重寫
6. 使項(xiàng)目分層,所有層均面向接口編程,開發(fā)效率高,降低耦合
11. 接口和抽象類的區(qū)別
l 接口只能做方法申明,抽象類中可以做方法申明,也可以做方法實(shí)現(xiàn)
l 接口里定義的變量只能是公共的靜態(tài)的常量,抽象類中的變量是普通變量。
l 抽象類里的抽象方法必須全部被子類所實(shí)現(xiàn),如果子類不能全部實(shí)現(xiàn)父類抽象方法,那么該子類只能是抽象類。
同樣,一個(gè)實(shí)現(xiàn)接口的時(shí)候,如不能全部實(shí)現(xiàn)接口方法,那么該類也只能為抽象類。
l 接口可繼承接口,并可多繼承接口,但類只能單根繼承。
12. abstract的method是否可同時(shí)是static, native,synchronized?
Abctract:是用來繼承的
另外兩個(gè)必須有方法體
13. Object基類的方法
方法名
返回類型
作用
clone( )
Object
創(chuàng)建并返回此對象的一個(gè)副本
equals( )
boolean
指示其他某個(gè)對象是否與此對象“相等”
finalize( )
void
當(dāng)垃圾回收器確定不存在對該對象的更多引用時(shí),由對象的垃圾回收器調(diào)用此方法
getClass( )
Object
返回此Object的運(yùn)行時(shí)類
hashCode( )
Int
返回該對象的哈希碼值
notify( )
Void
喚醒在此對象監(jiān)視器上等待的單個(gè)線程
notify All( )
Void
喚醒在此對象監(jiān)視器上等待的所有線程
toString( )
String
返回該對象的字符串表示
Wait( )
void
在其他線程調(diào)用此對象的notify()方法或notify
寫clone()方法時(shí),通常都有一行代碼,是什么?
clone 有缺省行為,
super.clone();
因?yàn)槭紫纫迅割愔械某蓡T復(fù)制到位,然后才是復(fù)制自己的成員。
14. HashCode和Equals方法
1、如果兩個(gè)對象相同(即用equals比較返回true),那么它們的hashCode值一定要相同;
2、如果兩個(gè)對象的hashCode相同,它們并不一定相同(即用equals比較返回false)。
為了提高程序的效率才實(shí)現(xiàn)了hashcode方法,先進(jìn)行hashcode的比較,如果不同,那沒就不必在進(jìn)行equals的比較了,這樣就大大減少了equals比較的次數(shù),這對比需要比較的數(shù)量很大的效率提高是很明顯的,一個(gè)很好的例子就是在集合中的使用;set集合是無序的,因此是不能重復(fù)的,那么怎么能保證不能被放入重復(fù)的元素呢,但靠equals方法一樣比較的
話,如果原來集合中以后又10000個(gè)元素了,那么放入10001個(gè)元素,難道要將前面的所有元素都進(jìn)行比較,看看是否有重復(fù),歐碼噶的,這個(gè)效率可想而知,因此hashcode就應(yīng)遇而生了,java就采用了hash表,利用哈希算法(也叫散列算法),就是將對象數(shù)據(jù)根據(jù)該對象的特征使用特定的算法將其定義到一個(gè)地址上,那么在后面定義進(jìn)來的數(shù)據(jù)只要看對應(yīng)的hashcode地址上是否有值,那么就用equals比較,如果沒有則直接插入,只要就大大減少了equals的使用次數(shù),執(zhí)行效率就大大提高了。
繼續(xù)上面的話題,為什么必須要重寫hashcode方法,其實(shí)簡單的說就是為了保證同一個(gè)對象,保證在equals相同的情況下hashcode值必定相同,如果重寫了equals而未重寫hashcode方法,可能就會出現(xiàn)兩個(gè)沒有關(guān)系的對象equals相同的(因?yàn)閑qual都是根據(jù)對象的特征進(jìn)行重寫的),但hashcode確實(shí)不相同的
15. 內(nèi)部類
1. 靜態(tài)內(nèi)部類
1) 靜態(tài)內(nèi)部類可以等同看做靜態(tài)變量,可用訪問控制權(quán)限的修飾符修飾。
2) 內(nèi)部類重要的作用:可以訪問外部類中私有的數(shù)據(jù)。
3) 靜態(tài)內(nèi)部類可以直接訪問外部類的靜態(tài)數(shù)據(jù),無法直接訪問成員。
2. 成員內(nèi)部類
1) 成員內(nèi)部類可以等同看做成員變量,可用訪問控制權(quán)限的修飾符修飾
2) 成員內(nèi)部類中不能有靜態(tài)聲明.
3) 成員內(nèi)部類可以訪問外部類所有的數(shù)據(jù).
3. 局部內(nèi)部類
1) 局部內(nèi)部類等同于局部變量,不能用訪問控制權(quán)限修飾符修飾。
2) 重點(diǎn):局部內(nèi)部類在訪問局部變量的時(shí)候,局部變量必須使用final修飾。
3) 局部內(nèi)部類不能有靜態(tài)聲明
4. 匿名內(nèi)部類
指的是類沒有名字的類
.
16. 異常定義
常見的運(yùn)行時(shí)異常(RuntimeException)
java.lang.ArithmeticException
算術(shù)異常
5/0
ClassCastException
類型轉(zhuǎn)化異常
Cat cat=new Dog( );
IllegalArgumentException
非法參數(shù)異常
IndexOutOfBoundsException
下表越界異常)
NullPointerException
空指針異常
SecurityException
由安全管理器拋出的異常,指示存在安全侵犯。
1.異常是什么?
第一,異常模擬的是現(xiàn)實(shí)世界中“不正常的”事件。
第二,java中采用“類”去模擬異常。
第三,類是可以創(chuàng)建對象的。
NullPointerExceptione=0x1234;
e是引用類型,e中保存的內(nèi)存地址指向堆中的“對象”,這個(gè)對象一定是NullPointerException類型。
這個(gè)對象就表示真實(shí)存在的異常事件(實(shí)例)。
NullPointerException是一類異常。
“搶劫”就是一類異常。----->類
“張三被搶劫”就是一個(gè)異常事件---->對象
2.異常機(jī)制的作用?
java語言為我們提供一種完善的異常處理機(jī)制,
作用是:程序發(fā)生異常事件之后,為我們輸出詳細(xì)的信息,
程序員通過這個(gè)信息,可以對程序進(jìn)行一些處理,使程序更加健壯。
本質(zhì):程序執(zhí)行過程中發(fā)生了算數(shù)異常這個(gè)事件,JVM為我們創(chuàng)建了一個(gè)ArithmeticException類型的對象。
并且這個(gè)對象中包含了詳細(xì)的異常信息,并且JVM將這個(gè)對象中的信息輸出到控制臺。
17. 異常的分類
18. 處理異常的兩種方式
[1] 聲明拋出throws
以下程序演示第一種方式:聲明拋出,在方法聲明的位置上使用throws關(guān)鍵字向上拋出異常。
1. 深入throws
2. 底層實(shí)現(xiàn)原理
[2] try...catch...
語法:
try{
可能出現(xiàn)異常的代碼;
}catch(異常類型1變量){
處理異常的代碼;
}catch(異常類型2變量){
處理異常的代碼;
}....
1) catch語句塊可以寫多個(gè).
2) try中的代碼出現(xiàn)異常時(shí),出現(xiàn)異常下面的代碼不會執(zhí)行
3) 但是從上到下catch,必須從小類型異常到大類型異常進(jìn)行捕捉。
4) try...catch...中最多執(zhí)行1個(gè)catch語句塊。執(zhí)行結(jié)束之后try...catch...就結(jié)束了。
19. getMessage和printStackTrace
何取得異常對象的具體信息,常用的方法主要有兩種:
取得異常描述信息:getMessage()
取得異常的堆棧信息(比較適合于程序調(diào)試階段):printStackTrace()
20. 關(guān)于finally語句塊
1.finally語句塊可以直接和try語句塊聯(lián)用。try....finally...
2.try...catch....finally也可以.
3.在finally語句塊中的代碼是一定會執(zhí)行的。
4.System.exit(0);//正常退出JVM只要在執(zhí)行finally語句塊之前退出了
JVM,則finally語句塊不會執(zhí)行
5. finally例題
6. finally語句塊是一定會執(zhí)行的,所以通常在程序中為了保證某資源一定會釋放,所以一般在finally語句塊中釋放資源。
7.try和finally中都有return語句,執(zhí)行哪一個(gè)return?
執(zhí)行try塊,執(zhí)行到return語句時(shí),先執(zhí)行return的語句,但是不返回到main 方法,接下來執(zhí)行finally塊,遇到finally塊中的return語句,執(zhí)行,并將值返回到main方法,這里就不會再回去返回try塊中計(jì)算得到的值
8.重寫的方法不能比被重寫的方法拋出更寬泛的異常.
21. 受控異常和非受控異常
受控異常:CheckedException,這類異常必須寫try{}catch{},或者throw拋出,否則編譯通不過。
非受控異常:UncheckedException,這類異常也叫做運(yùn)行時(shí)異常(與非受控異常字?jǐn)?shù)相等),這類異常不需要try{}catch{},也不需要throw拋出,編譯能通過。
為什么要使用非受控異常?為了簡化代碼。試想一下,如果所有可能出現(xiàn)異常的地方(比如訪問數(shù)組元素可能會越界、調(diào)用對象方法,對象可能為null),我們都寫try{}catch{},或者throw拋出,那么代碼肯定冗余的不成樣子了。也就是說,采用非受控異常(運(yùn)行時(shí)異常)可以減少代碼的污染。
對于非受控異常(運(yùn)行時(shí)異常),因?yàn)椴恍枰~外處理,也能編譯通過,我們可以進(jìn)行預(yù)先檢查,比如訪問數(shù)組元素時(shí),我們預(yù)先檢查是否越界,調(diào)用對象方法時(shí),預(yù)先檢查對象是否為null
22. throw和throws
[1] throws出現(xiàn)在方法聲明的后面,異常由方法的調(diào)用者處理;而throw出現(xiàn)在方法體,由方法體內(nèi)的語句來處理。
[2] throws表示出現(xiàn)異常的一種可能性,并不一定會發(fā)生這些異常;throw則是拋出了異常,執(zhí)行throw則一定拋出了某種異常,是一個(gè)實(shí)例。
[3] 兩者都是消極處理異常的方式,只是拋出或者可能拋出異常,但是不會由函數(shù)去處理異常,真正的處理異常由函數(shù)的上層調(diào)用處理。
23. String和StringBuffer和StringBuilde
[1] String
String類是不可變類,也就是說String對象聲明后,將不可修改。
1.如果是采用雙引號引起來的字符串常量,首先會到常量池(方法區(qū))中去查找,如果存在就不再分配,如果不存在就分配,常量池中的數(shù)據(jù)是在編譯期賦值的,也就是生成class文件時(shí)就把它放到常量池里了,所以s1和s2都指向常量池中的同一個(gè)字符串“abc”
2.關(guān)于s3,s3采用的是new的方式,在new的時(shí)候存在雙引號,所以他會到常量區(qū)中查找“abc”,而常量區(qū)中存在“abc”,所以常量區(qū)中將不再放置字符串,而new關(guān)鍵子會在堆中分配內(nèi)存,所以在堆中會創(chuàng)建一個(gè)對象abc,s3會指向abc
3.如果比較s2和s3的值必須采用equals,String已經(jīng)對eqauls方法進(jìn)行了覆蓋 。
31. String s = new String("abc");
32. String s1 = "abc";
33. String s2 = new String("abc");
34.
35. System.out.println(s == s1);
36. System.out.println(s == s2);
37. System.out.println(s1 == s2);
請問以上程序執(zhí)行結(jié)果是什么?
第一句執(zhí)行后內(nèi)存中有兩個(gè) 對象,而不是一個(gè)。一個(gè)由new String("abc")中的"abc"在String Pool里生成一個(gè)值為"abc"的對象;第二個(gè)由new在堆里產(chǎn)生一個(gè)值為"abc"的對象,該對象完全是String Pool里的"abc"的一個(gè)拷貝。變量s最后指向堆中產(chǎn)生的"abc"對象;
第二句執(zhí)行時(shí),s1先去String Pool找是否有值為"abc"的對象,很顯然在上一步中java已經(jīng)在String Pool里生成一個(gè)"abc"對象了,所以s1直接指向String Pool中的這個(gè)"abc";
第三句中又有一個(gè)new,在java中凡遇到new時(shí),都會在堆里產(chǎn)生一個(gè)新的對象。因此,該句執(zhí)行后堆里又多了一個(gè)"abc"對象,這與執(zhí)行第一句后生成的"abc"是不同的兩個(gè)對象,s2最后指向這個(gè)新生成的對象。
因此,執(zhí)行后面的打印語句的結(jié)果是三個(gè)false
String常用方法
endWith 判斷字符串是否以指定的后綴結(jié)束 startsWith 判斷字符串是否以指定的前綴開始 equals 字符串內(nèi)容比較 equalsIgnoreCase 字符串內(nèi)容比較,忽略大小寫 indexOf 取得指定字符串在字符串的位置 lastIndexOf 返回最后一次出現(xiàn)的位置 length 取得字符串長度 replaceAll 替換字符串中指定的內(nèi)容 split 根據(jù)指定的表達(dá)式拆分字符串 substring 截子串 tirm 去前尾空格 valueOf 將其他類型轉(zhuǎn)換成字符串
[2] StringBuffer
先申請一塊內(nèi)存,存放字符序列,如果字符序列滿了,會重新改變緩存區(qū)的大小,以容納更多的字符序列。StringBuffer是可變對象,這個(gè)是String最大的不同。
[3] StringBuilder
用法同StringBuffer,StringBuilder和StringBuffer的區(qū)別是StringBuffer中所有的方法都是同步的,是線程安全的,但速度慢,StringBuilder的速度快,但不是線程安全的。
24. java中replace()和replaceAll()區(qū)別
[1] replace的參數(shù)是char和CharSequence(串),即可以支持字符的替換,也支持字符串的替換
[2] replaceAll的參數(shù)是regex,即基于規(guī)則表達(dá)式的替換,比如,可以通過replaceAll("\\d","*")把一個(gè)字符串所有的數(shù)字字符都換成星號;
[3] 相同點(diǎn):都是全部替換,即把源字符串中的某一字符或字符串全部換成指定的字符或字符串
[4] replaceFirst():如果只想替換第一次出現(xiàn)的,可以使用replaceFirst(),這個(gè)方法也是基于規(guī)則表達(dá)式的替換,但與replaceAll()不同的是,只替換第一次出現(xiàn)的字符串;
[5] 如果replaceAll()和replaceFirst()所用的參數(shù)據(jù)不是基于規(guī)則表達(dá)式的,則與replace()替換字符串的效果是一樣的,即這兩者也支持字符串的操作;
[6] 還有一點(diǎn)注意:執(zhí)行了替換操作后,源字符串的內(nèi)容是沒有發(fā)生改變的.
舉例如下:
關(guān)于字符串中的"\"替換:
'\'在java中是一個(gè)轉(zhuǎn)義字符,所以需要用兩個(gè)代表一個(gè)。例如System.out.println("\\");只打印出一個(gè)"\"。但是'\'也是正則表達(dá)式中的轉(zhuǎn)義字符(replaceAll的參數(shù)就是正則表達(dá)式),需要用兩個(gè)代表一個(gè)。所以:\\\\被java轉(zhuǎn)換成\\,\\又被正則表達(dá)式轉(zhuǎn)換成\。
將字符串中的'/'替換成'\':
25. 枚舉類型
需求:定義一個(gè)方法,該方法的作用是計(jì)算兩個(gè)int類型數(shù)據(jù)的商。
如果計(jì)算成功則該方法返回1,如果執(zhí)行失敗則該方法返回0
程序執(zhí)行成功,但是該程序存在風(fēng)險(xiǎn),分析:存在什么風(fēng)險(xiǎn)?返回類型int范圍太廣
程序中的問題能在編譯階段解決的,絕對不會放在運(yùn)行期解決。所以以下程序可以引入“枚舉類型”。
三、Java容器框架
Java容器類庫一共有兩種主要類型Collection(單列集合)和Map(雙列集合)。
所有的java容器類都可以自動(dòng)調(diào)整自己的尺寸。
26. Collection
Collection接口是Set、List和Queue接口的父接口。
27. List
List:代表有序、可重復(fù)的集合。所以與Set相比,增加了與索引位置相關(guān)的操作;
1. ArrayList集合底層是數(shù)組。數(shù)組是有下標(biāo)的。擅長隨機(jī)訪問,但在List中間插入、刪除、移動(dòng)元素較慢。
ArrayList集合底層默認(rèn)初始化容量是10.擴(kuò)大之后的容量是原容量的1.5倍.
2. LinkedList:插入、刪除、移動(dòng)元素方便,隨機(jī)訪問比較差。
3. Vector集合底層是數(shù)組,默認(rèn)初始化容量也是10.擴(kuò)大之后的容量是原容量的2倍,線程安全,效率較低
4. ArrayList與Vector區(qū)別:
線程不安全VS線程安全:對于單線程的程序選用ArrayList,效率高
擴(kuò)容1.5VS擴(kuò)容2
5. 如果優(yōu)化ArrayList和Vector?
盡量減少擴(kuò)容操作,因?yàn)閿U(kuò)容需要數(shù)組拷貝。數(shù)組拷貝很耗內(nèi)存。
一般推薦在創(chuàng)建集合的時(shí)候指定初始化容量。
28. List list=new和ArrayList list=new這兩種方式有什么區(qū)別,為什么大多數(shù)情況下會用第一種。
List是一個(gè)接口,而ArrayList 是一個(gè)類。
List list = new ArrayList();這句創(chuàng)建了一個(gè)ArrayList的對象后把上溯到了List。此時(shí)它是一個(gè)List對象了,有些ArrayList有但是List沒有的屬性和方法,它就不能再用了。而ArrayList list=new ArrayList();創(chuàng)建一對象則保留了ArrayList的所有屬性。
為什么一般都使用 List list = new ArrayList() ,而不用 ArrayList alist = new ArrayList()呢?
問題就在于List有多個(gè)實(shí)現(xiàn)類,如 LinkedList或者Vector等等,現(xiàn)在你用的是ArrayList,也許哪一天你需要換成其它的實(shí)現(xiàn)類呢?,這時(shí)你只要改變這一行就行了:List list = new LinkedList(); 其它使用了list地方的代碼根本不需要改動(dòng)。假設(shè)你開始用 ArrayList alist = new ArrayList(), 這下你有的改了,特別是如果你使用了 ArrayList特有的方法和屬性。 ,如果沒有特別需求的話,最好使用List list = new LinkedList(); ,便于程序代碼的重構(gòu). 這就是面向接口編程的好處。
29.
30. Set
Set代表無序、不可重復(fù)的集合,判斷兩個(gè)對象是否相同則是根據(jù)equals方法。
1. HashSet:底層是HashMap,HashMap底層采用了哈希表數(shù)據(jù)結(jié)構(gòu),查找插入優(yōu)于TreeSet。
HashSet其實(shí)是HashMap中的key部分。HashSet有什么特點(diǎn),HashMap中的key應(yīng)該具有相同的特點(diǎn)。
HashMap和HashSet初始化容量都是16,默認(rèn)加載因子是0.75。
存儲在HashSet集合或者HashMap集合key部分的元素,需要同時(shí)重寫hashCode+equals
2. TreeSet:實(shí)現(xiàn)SortedSet集合接口,其中的元素自動(dòng)排序。因?yàn)楸淮鎯Φ脑貙?shí)現(xiàn)了Comparable接口,SUN編寫TreeSet集合在添加元素的時(shí)候,會調(diào)用compareTo方法完成比較。
3. LinkedHashSet:使用鏈表結(jié)合散列函數(shù)。
4. Queue新增的體系集合,代表一種隊(duì)列集合實(shí)現(xiàn)。
31. Iterator
[1] list刪除元素操作
錯(cuò)誤的方法:
如果在循環(huán)的過程中調(diào)用集合的remove()方法,就會導(dǎo)致循環(huán)出錯(cuò),例如:
for(inti=0;i<list.size();i++){
list.remove(...);
}
循環(huán)過程中l(wèi)ist.size()的大小變化了,就導(dǎo)致了錯(cuò)誤。
正確的方法:
如果你想在循環(huán)語句中正確并安全的刪除集合中的某個(gè)元素,就要用迭代器iterator的remove()方法,因?yàn)樗膔emove()方法不僅會刪除元素,還會維護(hù)一個(gè)標(biāo)志,用來記錄目前是不是可刪除狀態(tài),例如,你不能連續(xù)兩次調(diào)用它的remove()方法,調(diào)用之前至少有一次next()方法的調(diào)用。
32. 看需求選集合
是否是鍵值對象形式:
是:Map
鍵是否需要排序:
是:TreeMap
否:HashMap
不知道,就使用HashMap。
否:Collection
元素是否唯一:
是:Set
元素是否需要排序:
是:TreeSet
否:HashSet
不知道,就使用HashSet
否:List
要安全嗎:
是:Vector(其實(shí)我們也不用它,后面我們講解了多線程以后,我在給你回顧用誰)
否:ArrayList或者LinkedList
增刪多:LinkedList
查詢多:ArrayList
不知道,就使用ArrayList
不知道,就使用ArrayList
33. 集合的常見方法及遍歷方式
Collection:
add()
remove()
contains()
iterator()
size()
遍歷:
增強(qiáng)for
迭代器
|--List
get()
遍歷:
普通for
|--Set
Map:
put()
remove()
containskey(),containsValue()
keySet()
get()
values()
entrySet()
size()
遍歷:
根據(jù)鍵找值
根據(jù)鍵值對對象分別找鍵和值
34. Map
Map代表具有映射關(guān)系的集合,實(shí)現(xiàn)將唯一鍵映射到特定的值上。
關(guān)于Map集合中常用的方法:
clear() 清空Map isEmpty() 判斷該集合是否為空 size() 獲取Map中鍵值對的個(gè)數(shù) put(Object key,Object value) 向集合中添加鍵值對 get(Object key) 通過key獲取value containsKey(Object key) 判斷Map中是否包含這樣的key containValue(Object value) 判斷Map中是否包含這樣的value remove(Object key) 通過key將鍵值對刪除 values() 獲取Map集合中所有的value keySet() 獲取Map中所有的key entrySet() 返回此映射中包含的映射關(guān)系的Set視圖
注意:存儲在Map集合key部分的元素需要同時(shí)重寫hashCode+equals方法.
Hash家族嘛,高逼格,必須一次性兩個(gè)值存儲,就是所謂的鍵值對.
但是呢,Hash家族內(nèi)部分為了幾個(gè)小家族,分別是HashMap,Hashtable,TreeMap. LinkedHashMap
這幾個(gè)家族呢,對鍵值對能不能存儲null這種不是很安全的"買賣"有不一樣的行動(dòng).
其中的HashMap家族與Hashtable、TreeMap不同,認(rèn)為沒有風(fēng)險(xiǎn)就沒有利潤!于是乎,準(zhǔn)許自己的鍵值對都可以為null!
Hashtable與TreeMap一看SUN國王居然默許了HashMap的冒險(xiǎn)行為,使得HashMap家族的利潤大大增加,這兩個(gè)家族也不甘寂寞,于是乎也就允許了自己的鍵值對可以為"",但是不能觸碰null的界限.
1. HashMap:HashMap中的key就是HashSet,底層是哈希表/散列表,用于快速查找
HashMap默認(rèn)初始化容量是16,默認(rèn)加載因子0.75,鍵值均可以為null,線程不安全
2. Hashtable默認(rèn)初始化容量是11,默認(rèn)加載因子是0.75,鍵值均不可以為null,線程安全
3. Properties;也是由key和value組成,但是key和value都是字符串類型
dbinfo這樣的文件我們稱作配置文件,
配置的文件的作用就是:使程序更加靈活。
注意:一般在程序中可變的東西不要寫死。推薦寫到配置文件中。
運(yùn)行同樣的程序得到不同的結(jié)果。
像dbinfo這樣一個(gè)具有特殊內(nèi)容的配置文件我們又叫做:屬性文件。
java規(guī)范中要求屬性文件以“.properties”
屬性文件中數(shù)據(jù)要求:
key和value之間可以使用“空格”,“冒號”,“等號”。
如果“空格”,“等號”,“冒號”都有,按最前的作為分隔符。
4. LinkedHashMap:是HashMap的一個(gè)子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時(shí),先得到的記錄肯定是先插入的.也可以在構(gòu)造時(shí)用帶參數(shù),按照應(yīng)用次數(shù)排序。在遍歷的時(shí)候會比HashMap慢,不過有種情況例外,當(dāng)HashMap容量很大,實(shí)際數(shù)據(jù)較少時(shí),遍歷起來可能會比 LinkedHashMap慢,因?yàn)長inkedHashMap的遍歷速度只和實(shí)際數(shù)據(jù)有關(guān),和容量無關(guān),而HashMap的遍歷速度和他的容量有關(guān)。
5. TreeMap:SortedMap中的key特點(diǎn):無序不可重復(fù),但是存進(jìn)去的元素可以按照大小自動(dòng)排列。如果想自動(dòng)排序:key部分的元素需要,1,實(shí)現(xiàn)Comparable接口.2.單獨(dú)寫一個(gè)比較器.
35. 解決哈希(HASH)沖突的主要方法(????)
不同的key用同樣的Hash算法,可能會得到相同的hash值
[1] 開放定址法
(1)線性探查法(Linear Probing)
插入元素時(shí),如果發(fā)生沖突,算法會簡單的從該槽位置向后循環(huán)遍歷hash表,直到找到表中的下一個(gè)空槽,并將該元素放入該槽中(會導(dǎo)致相同hash值的元素挨在一起和其他hash值對應(yīng)的槽被占用)。查找元素時(shí),首先找到散列值所指向的槽,如果沒有找到匹配,則繼續(xù)從該槽遍歷hash表,直到:(1)找到相應(yīng)的元素;(2)找到一個(gè)空槽,指示查找的元素不存在,(所以不能隨便刪除元素);(3)整個(gè)hash表遍歷完畢(指示該元素不存在并且hash表是滿的)用線性探測法處理沖突,思路清晰,算法簡單,但存在下列缺點(diǎn):
? 處理溢出需另編程序。一般可另外設(shè)立一個(gè)溢出表,專門用來存放上述哈希表中放不下的記錄。此溢出表最簡單的結(jié)構(gòu)是順序表,查找方法可用順序查找。
? 按上述算法建立起來的哈希表,刪除工作非常困難。假如要從哈希表HT中刪除一個(gè)記錄,按理應(yīng)將這個(gè)記錄所在位置置為空,但我們不能這樣做,而只能標(biāo)上已被刪除的標(biāo)記,否則,將會影響以后的查找。
? 線性探測法很容易產(chǎn)生堆聚現(xiàn)象。所謂堆聚現(xiàn)象,就是存入哈希表的記錄在表中連成一片。按照線性探測法處理沖突,如果生成哈希地址的連續(xù)序列愈長(即不同關(guān)鍵字值的哈希地址相鄰在一起愈長),則當(dāng)新的記錄加入該表時(shí),與這個(gè)序列發(fā)生沖突的可能性愈大。因此,哈希地址的較長連續(xù)序列比較短連續(xù)序列生長得快,這就意味著,一旦出現(xiàn)堆聚(伴隨著沖突),就將引起進(jìn)一步的堆聚。
(2)線性補(bǔ)償探測法
將線性探測的步長從1改為Q,即將上述算法中的hash = (hash + 1) % m 改為:hash = (hash + Q) % m = hash % m + Q % m,而且要求 Q 與 m 是互質(zhì)的,以便能探測到哈希表中的所有單元。
【例】PDP-11小型計(jì)算機(jī)中的匯編程序所用的符合表,就采用此方法來解決沖突,所用表長m=1321,選用Q=25。
(3)隨機(jī)探測
將線性探測的步長從常數(shù)改為隨機(jī)數(shù),即令: hash = (hash + RN) % m ,其中 RN 是一個(gè)隨機(jī)數(shù)。在實(shí)際程序中應(yīng)預(yù)先用隨機(jī)數(shù)發(fā)生器產(chǎn)生一個(gè)隨機(jī)序列,將此序列作為依次探測的步長。這樣就能使不同的關(guān)鍵字具有不同的探測次序,從而可以避 免或減少堆聚?;谂c線性探測法相同的理由,在線性補(bǔ)償探測法和隨機(jī)探測法中,刪除一個(gè)記錄后也要打上刪除標(biāo)記。
[2] 拉鏈法
將所有關(guān)鍵字為同義詞的結(jié)點(diǎn)鏈接在同一個(gè)單鏈表中。
若選定的散列表長度為m,則可將散列表定義為一個(gè)由m個(gè)頭指針組成的指針數(shù)組T[0..m-1]。凡是散列地址為i的結(jié)點(diǎn),均插入到以T[i]為頭指針的單鏈表中。T中各分量的初值均應(yīng)為空指針。在拉鏈法中,裝填因子α可以大于1,但一般均取α≤1。
與開放定址法相比,拉鏈法有如下幾個(gè)優(yōu)點(diǎn):
①拉鏈法處理沖突簡單,且無堆積現(xiàn)象,即非同義詞決不會發(fā)生沖突,因此平均查找長度較短;
②由于拉鏈法中各鏈表上的結(jié)點(diǎn)空間是動(dòng)態(tài)申請的,故它更適合于造表前無法確定表長的情況;
③開放定址法為減少沖突,要求裝填因子α較小,故當(dāng)結(jié)點(diǎn)規(guī)模較大時(shí)會浪費(fèi)很多空間。而拉鏈法中可取α≥1,且結(jié)點(diǎn)較大時(shí),拉鏈法中增加的指針域可忽略不計(jì),因此節(jié)省空間;
④在用拉鏈法構(gòu)造的散列表中,刪除結(jié)點(diǎn)的操作易于實(shí)現(xiàn)。只要簡單地刪去鏈表上相應(yīng)的結(jié)點(diǎn)即可。而對開放地址法構(gòu)造的散列表,刪除結(jié)點(diǎn)不能簡單地將被刪結(jié)點(diǎn)的空間置為空,否則將截?cái)嘣谒筇钊松⒘斜淼耐x詞結(jié)點(diǎn)的查找路徑。這是因?yàn)楦鞣N開放地址法中,空地址單元(即開放地址)都是查找失敗的條件。因此在用開放地址法處理沖突的散列表上執(zhí)行刪除操作,只能在被刪結(jié)點(diǎn)上做刪除標(biāo)記,而不能真正刪除結(jié)點(diǎn)。
拉鏈法的缺點(diǎn)
指針需要額外的空間,故當(dāng)結(jié)點(diǎn)規(guī)模較小時(shí),開放定址法較為節(jié)省空間,而若將節(jié)省的指
36. HashMap怎么實(shí)現(xiàn)按鍵值對來存取數(shù)據(jù)呢?
37. 泛型
1.泛型類:classA<T>{}
2.在創(chuàng)建泛型類實(shí)例時(shí),需要為其類型變量賦值A(chǔ)<String>a=new A<String>();
*如果創(chuàng)建實(shí)例時(shí),不給類型變量賦值,那么會有一個(gè)警告!
3.泛型方法:具有一個(gè)或多個(gè)類型變量的方法,稱之為泛型方法!
Class A<T>{
public T fun(T t1){}
}
l fun()方法不是泛型方法!它是泛型類中的一個(gè)方法!
l Public <T>T fun(T t1){}-->它是泛型方法
l 泛型方法與泛型類沒什么關(guān)系,泛型方法不一定非要在泛型類中!
4.泛型在類中或方法中的使用
5.泛型的繼承和實(shí)現(xiàn)
Class A<T>{}
Class AA extends A<String>{}//不是泛型類,只是它爸爸是泛型類!
l 子類不是泛型類:需要給父類傳遞類型常量
l 當(dāng)給父類傳遞的類型常量為String時(shí),那么在父類中所有T都會被String替換!
l 子類是泛型類:可以給父類傳遞類型常量,也可以傳遞類型變量
Class AA1 extends A<Integer>{}
Class AA3<E> extends A<E>{}
泛型的通配符
1.通配符使用的場景
方法的形參!
2.通配符的優(yōu)點(diǎn)
使方法更加通用!
3.通配符分類
無界通配:?
子類限定:?extends Object
父類限定:?super Integer
4.通配符缺點(diǎn)
使變量使用上不再方便
無界:參數(shù)和返回值為泛型的方法,不能使用!
子類:參數(shù)為泛型的方法不能使用
父類:返回值為泛型的方法不能使用
5.比較通配符
[1] 為什么引入泛型?
可以統(tǒng)一集合中的數(shù)據(jù)類型
可以減少強(qiáng)制類型轉(zhuǎn)換.
[2] 泛型語法如何實(shí)現(xiàn)?
泛型是一個(gè)編譯階段的語法。
在編譯階段統(tǒng)一集合中的類型.
[3] 泛型的優(yōu)點(diǎn)和缺點(diǎn)?
優(yōu)點(diǎn):統(tǒng)一類型,減少強(qiáng)制轉(zhuǎn)換.
缺點(diǎn):只能存儲一種類型.
以下程序沒有使用泛型,缺點(diǎn)?
如果集合不使用泛型,則集合中的元素類型不統(tǒng)一。
在遍歷集合的時(shí)候,只能拿出來Object類型,需要做
大量的強(qiáng)制類型轉(zhuǎn)換。麻煩。
[4] 自定義泛型
38. 可變長參數(shù)
39. IO
需要重點(diǎn)掌握的16個(gè)流java.io.*
用來讀取文件的
1) FileInputStream
2) FileOutputStream
3) FileReader
4) FileWriter
帶有緩沖區(qū)的
1) BufferedReader
2) BufferedWriter
3) BufferedInputStream
專門讀取數(shù)據(jù)的
1) DataInputStream
2) DataOutputStream
專門讀取Java對象
1) ObjectInputStream
2) ObjectOutputStream
轉(zhuǎn)換流(字節(jié)流轉(zhuǎn)換成字符流)
1) InputStreamReader
2) OutputStreamWriter
1) PrintWriter
2) PrintStream//標(biāo)準(zhǔn)的輸出流(默認(rèn)輸出到控制臺)
java語言中的流分為:四大家族(InputStream,OutputStream,Reader,Writer)
40. FileInputStream
定義路徑:
String filePath="temp01";//相對路徑,相對當(dāng)前而言,在當(dāng)前路徑下找。
String filePath="D:\\course\\JavaProjects\\02-JavaSE\\chapter08\\temp01";//絕對路徑
String filePath="D:/course/JavaProjects/02-JavaSE/chapter08/temp01";
1.創(chuàng)建流
FileInputStream fis=new FileInputStream(filePath);
2.開始讀一個(gè)字節(jié)
Int i1=fis.read();//以字節(jié)的方式讀取.
如果已經(jīng)讀取到文件的末尾,就會返回-1
循環(huán)讀?。?/p>
Int temp=0;
while((temp=fis.read())!=-1){
System.out.println(temp);
}
//頻繁訪問磁盤,傷害磁盤,并且效率低。
3.讀取多個(gè)字節(jié)
讀取之前在內(nèi)存中準(zhǔn)備一個(gè)byte數(shù)組,數(shù)組相當(dāng)于緩存,每次讀取多個(gè)字節(jié)存儲到byte數(shù)組中。不是單字節(jié)讀取了,效率高。
//準(zhǔn)備一個(gè)byte數(shù)組
4.int fis.available();//返回流中剩余的估計(jì)字節(jié)數(shù)
5.fis.skip(2);//跳過2個(gè)字節(jié)
6.為了保證流一定會釋放,所以在finally語句塊中執(zhí)行fis.close();
41. FileOutputStream
1.創(chuàng)建文件字節(jié)輸出流
FileOutputStream fos=new FileOutputStream("temp02");//該文件不存在則自動(dòng)創(chuàng)建.
//謹(jǐn)慎使用,會將源文件內(nèi)容覆蓋.
//以追加的方式寫入,不會覆蓋前邊內(nèi)容
FileOutputStream fos=new FileOutputStream("temp02",true);
2.開始寫
String msg="HelloWorld!";
byte[] bytes=msg.getBytes();//將String轉(zhuǎn)換成byte數(shù)組.
fos.write(bytes);//將byte數(shù)組中所有的數(shù)據(jù)全部寫入
fos.write(bytes,0,3);//將byte數(shù)組的一部分寫入
3.最后的時(shí)候?yàn)榱吮WC數(shù)據(jù)完全寫入硬盤,所以要刷新.
fos.flush();//強(qiáng)制寫入.
4.關(guān)閉
fos.close();
關(guān)于文件復(fù)制粘貼
FileInputStream fis=new FileInputStream("InputStream_OutputStream.mdl");
FileOutputStream fos=new FileOutputStream("c:/InputStream_OutputStream.mdl");
//一邊讀,一邊寫
byte[] bytes=new byte[1024];//1KB
int temp=0;
while((temp=fis.read(bytes))!=-1){
fos.write(bytes,0,temp);//將byte數(shù)組中內(nèi)容直接寫入
}
fos.flush();//刷新
fis.close();//關(guān)閉
fos.close();//關(guān)閉
42. FileReader
FileReader fr=new FileReader(“Temp01”);
char[]c=newchar[512];//1kb
int temp=0;
while((temp=fr.read(c))!=1){
system.out.print(new String(c,0,temp));
}
43. FileWriter
1.創(chuàng)建文件字符輸出流
FileWriter fw=new FileWriter("temp03");//覆蓋
FileWriter fw=new FileWriter("temp03",true);//追加
2.開始寫
fw.write("李海波?。。?!");
3.將char數(shù)組的一部分寫入
char[] chars={'我','是','中','國','人','!','。','?'};
fw.write(chars,0,5);
4.刷新
fw.flush();
5.關(guān)閉
fw.close();
關(guān)于文件復(fù)制粘貼
FileReader fr=newFileReader("Copy02.java");
FileWriter fw=newFileWriter("c:/Copy02.java");
char[] chars=newchar[512];
int temp=0;
while((temp=fr.read(chars))!=-1){
fw.write(chars,0,temp);
}
fw.flush();
fr.close();
fw.close();
44. Buffered
BufferedInputStream;
BufferedOutputStream;
BufferedReader;帶有緩沖區(qū)的字符輸入流
BufferedWriter;帶有緩沖區(qū)的字符輸出流
他們增強(qiáng)了字節(jié)流的讀取和寫入效率。以BufferedInputStream為例,不帶緩沖的操作,每讀一個(gè)字節(jié)就要寫入一個(gè)字節(jié),如果數(shù)據(jù)量巨大,頻繁訪問磁盤,傷害磁盤,由于涉及磁盤的IO操作相比內(nèi)存的操作要慢很多,所以效率低。帶緩沖的流,可以一次讀很多字節(jié),但不向磁盤中寫入,只是先放到內(nèi)存里。等湊夠了緩沖區(qū)大小的時(shí)候一次性寫入磁盤,這種方式可以減少磁盤操作次數(shù),速度就會提高很多
1. 創(chuàng)建一個(gè)帶有緩沖區(qū)的字符輸入流
FileInputStream fis=new FileInputStream("BufferedReaderTest02.java");//文件字節(jié)輸入流
InputStreamReader isr=new InputStreamReader(fis);//isr是字符流
BufferedReader br=new BufferedReader(isr);//將文件字符輸入流包裝成帶有緩沖區(qū)的字符輸入流
根據(jù)流出現(xiàn)的位置,流又可以分為:包裝流或者處理流和節(jié)點(diǎn)流(相對而言)
FileReader fr是一個(gè)節(jié)點(diǎn)流
BufferedReader br是一個(gè)包裝流,或者處理流.
BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("BufferedReaderTest02.java")));
2.開始讀
String temp=null;
while((temp=br.readLine())!=null){//br.readLine()方法讀取一行,但是行尾不帶換行符.
System.out.println(temp);//輸出一行.
}
//關(guān)閉
//注意:關(guān)閉的時(shí)候只需要關(guān)閉最外層的包裝流。(這里有一個(gè)裝飾者模式)
br.close();
45. 鍵盤讀入
Scanner是SDK1.5新增的一個(gè)類,可是使用該類創(chuàng)建一個(gè)對象.
Scanner reader=new Scanner(System.in);
//以前的方式
Scanner s=new Scanner(System.in);//System.in是一個(gè)標(biāo)準(zhǔn)的輸入流,默認(rèn)接收鍵盤的輸入.
//程序執(zhí)行到此處停下來,等待用戶的輸入
Long a=reader.nextLong();
然后reader對象調(diào)用下列方法(函數(shù)),讀取用戶在命令行輸入的各種數(shù)據(jù)類型
next.Byte(),nextDouble(),nextFloat,nextInt(),nextLine(),nextLong(),nextShot()
上述方法執(zhí)行時(shí)都會造成堵塞,等待用戶在命令行輸入數(shù)據(jù)回車確認(rèn).例如,擁護(hù)在鍵盤輸入12.34
hasNextFloat()的值是true
hasNextInt()的值是false
NextLine()等待用戶輸入一個(gè)文本行并且回車,該方法得到一個(gè)String類型的數(shù)據(jù)。
String str=s.next();
System.out.println("您輸入了:"+str);
//使用BufferedReader用來接收用戶的輸入.
BufferedReader br=new BufferedReader(newInputStreamReader(System.in));
//接收輸入(每一次都接收一行)
Stringstr=br.readLine();
System.out.println("您輸入了:"+str);
br.close();
46. DataInputStream
數(shù)據(jù)字節(jié)輸出流.
可以將內(nèi)存中的"int i=10;"寫入到硬盤文件中,
寫進(jìn)去的不是字符串,寫進(jìn)去的是二進(jìn)制數(shù)據(jù),
帶類型。
//創(chuàng)建數(shù)據(jù)字節(jié)輸出流
DataOutputStream dos=new DataOutputStream(newFileOutputStream("temp05"));
//準(zhǔn)備數(shù)據(jù)
Long l=1000L;
Float f=3.2f;
//寫
dos.writeLong(l);
dos.writeFloat(f);
//刷新
dos.flush();
//關(guān)閉
dos.close();
//讀
//注意:要使用該流讀取數(shù)據(jù),必須提前知道該文件中數(shù)據(jù)的存儲格式,順序。
//讀得順序必須和寫入的順序相同。
47. PrintStream
java.io.PrintStream;標(biāo)準(zhǔn)的輸出流,默認(rèn)打印到控制臺.以字節(jié)方式
java.io.PrintWriter;以字符方式.
//默認(rèn)是輸出到控制臺的.
System.out.println("HelloWorld!");
PrintStream ps=System.out;
ps.println("JAVA….");
//可以改變輸出方向.
System.setOut(new PrintStream(new FileOutputStream("log")));//log日志文件
//再次輸出
//System.out.print("HAHA");
//通常使用上面的這種方式記錄日志.
//需求:記錄日志,m1方法開始執(zhí)行的時(shí)間和結(jié)束的時(shí)間.記錄到log文件中.
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-ddHH:mm:ssSSS");
System.out.println("m1方法開始執(zhí)行"+sdf.format(newDate()));
m1();
System.out.println("m1方法執(zhí)行結(jié)束"+sdf.format(newDate()));
}
48. Serializable序列化
待序列化的Java類只需要實(shí)現(xiàn)Serializable接口即可。實(shí)際的序列化和反序列化工作是通過ObjectOuputStream和ObjectInputStream來完成的。
49. ObjectInputStream
java.io.ObjectOutputStream;序列化JAVA對象到硬盤.(Serial)
java.io.ObjectInputStream;將硬盤中的數(shù)據(jù)“反序列化”到JVM內(nèi)存。(DeSerial)
Compile編譯(java-->class)
DeCompile反編譯.(class-->java)
50. 從文件讀數(shù),計(jì)算后再寫入
51. 進(jìn)程、線程定義
一個(gè)進(jìn)程對應(yīng)一個(gè)應(yīng)用程序,例如在windows下啟動(dòng)網(wǎng)易云音樂就表示啟動(dòng)了一個(gè)進(jìn)程。打開英雄聯(lián)盟,又啟動(dòng)了一個(gè)進(jìn)程?,F(xiàn)在的計(jì)算機(jī)都是支持多進(jìn)程的。一邊玩游戲,一邊聽歌。
對于單核計(jì)算機(jī)來講,在同一個(gè)時(shí)間點(diǎn)上,游戲進(jìn)程和音樂進(jìn)程不是同時(shí)執(zhí)行的,因?yàn)镃PU在某個(gè)時(shí)間點(diǎn)上只能做一件事兒。交替執(zhí)行,交替速度快,不易察覺。提高CPU使用率。
一個(gè)進(jìn)程中可以啟動(dòng)多個(gè)線程。多線程的作用是為了提高應(yīng)用程序的使用率(多個(gè)人可以同時(shí)訪問淘寶)。多個(gè)線程之間無縫切換。
l 區(qū)別:
進(jìn)程和線程是不同的操作系統(tǒng)資源管理方式。一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程,線程不能夠獨(dú)立執(zhí)行,必須依存在進(jìn)程中,進(jìn)程與進(jìn)程之間的內(nèi)存是獨(dú)立的,一個(gè)進(jìn)程崩潰后,不會對其它進(jìn)程產(chǎn)生影響。線程是一個(gè)進(jìn)程中的不同執(zhí)行路徑,線程與線程之間共享“堆內(nèi)存和方法區(qū)”,棧內(nèi)存是獨(dú)立的,即一個(gè)線程一個(gè)棧,多線程是指一個(gè)進(jìn)程中有線程同時(shí)執(zhí)行,提高了程序的運(yùn)行效率。一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉,所以多進(jìn)程的程序要比多線程的程序健壯。
52. 實(shí)現(xiàn)線程的三種方式
[1] 第一種方式:
第一步:繼承java.lang.Thread;
第二步:重寫run方法.
[2] java中實(shí)現(xiàn)線程的第二種方式:
第一步:寫一個(gè)類實(shí)現(xiàn)java.lang.Runnable;接口
第二步:實(shí)現(xiàn)run方法.
這種方式是推薦的。因?yàn)橐粋€(gè)類實(shí)現(xiàn)接口之外保留了類的繼承。
三個(gè)方法:
1.獲取當(dāng)前線程對象Thread.currentThread();
2.給線程起名t.setName("t1");
3.獲取線程的名字t.getName();
Thread t=Thread.currentThread();//t保存的內(nèi)存地址指向的線程是“t1線程對象”
System.out.println(t.getName());//Thread-0Thread-1
[3] 創(chuàng)建線程的第三種方式:實(shí)現(xiàn)Callable接口。
創(chuàng)建線程的三種方式的對比:
采用實(shí)現(xiàn)Runnable、Callable接口的方式創(chuàng)見多線程時(shí),優(yōu)勢是:
線程類只是實(shí)現(xiàn)了Runnable接口或Callable接口,還可以繼承其他類。
在這種方式下,多個(gè)線程可以共享同一個(gè)target對象,所以非常適合多個(gè)相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數(shù)據(jù)分開,形成清晰的模型,較好地體現(xiàn)了面向?qū)ο蟮乃枷搿?/p>
劣勢是:
編程稍微復(fù)雜,如果要訪問當(dāng)前線程,則必須使用Thread.currentThread()方法。
使用繼承Thread類的方式創(chuàng)建多線程時(shí)優(yōu)勢是:
編寫簡單,如果需要訪問當(dāng)前線程,則無需使用Thread.currentThread()方法,直接使用this即可獲得當(dāng)前線程。
劣勢是:
線程類已經(jīng)繼承了Thread類,所以不能再繼承其他父類。
53. 線程的生命周期
l 新建(new):新創(chuàng)建了一個(gè)線程對象。
l 可運(yùn)行(runnable):線程對象創(chuàng)建后,其他線程(比如main線程)調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中,等待被線程調(diào)度選中,獲取cpu的使用權(quán)。
l 運(yùn)行(running):可運(yùn)行狀態(tài)(runnable)的線程獲得了cpu時(shí)間片(timeslice),執(zhí)行程序代碼。
l 阻塞(block):阻塞狀態(tài)是指線程因?yàn)槟撤N原因放棄了cpu使用權(quán),也即讓出了cputimeslice,暫時(shí)停止運(yùn)行。直到線程進(jìn)入可運(yùn)行(runnable)狀態(tài),才有機(jī)會再次獲得cputimeslice轉(zhuǎn)到運(yùn)行(running)狀態(tài)。阻塞的情況分三種:
n 等待阻塞:運(yùn)行(running)的線程執(zhí)行o.wait()方法,會釋放鎖,JVM會把該線程放入等待隊(duì)列(waiting queue)中。
n 同步阻塞:運(yùn)行(running)的線程在獲取對象的同步鎖時(shí),若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池(lock pool)中。
n 其他阻塞:運(yùn)行(running)的線程執(zhí)行Thread.sleep(longms)或t.join()方法,或者發(fā)出了I/O請求時(shí),JVM會把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入可運(yùn)行(runnable)狀態(tài)。
l 死亡(dead):線程run()、main()方法執(zhí)行結(jié)束,或者因異常退出了run()方法,則該線程結(jié)束生命周期。死亡的線程不可再次復(fù)生。
54. 線程優(yōu)先級
高的獲取的CPU時(shí)間片相對多一些。
優(yōu)先級:1-10;最低1;最高10;默認(rèn)5
55. 線程sleep
Thread.sleep(毫秒);
sleep方法是一個(gè)靜態(tài)方法.
該方法的作用:阻塞當(dāng)前線程.騰出CPU,讓給其他線程
56. 線程面試題
57. 中斷睡眠
//需求:啟動(dòng)線程,5S之后打斷線程的
休眠.
58. 死鎖處理方法:
(1).查看那個(gè)spid處于wait狀態(tài),然后用kill spid來干掉(即破壞死鎖的第四個(gè)必要條件:循環(huán)等待);當(dāng)然這只是一種臨時(shí)解決方案,我們總不能在遇到死鎖就在用戶的生產(chǎn)環(huán)境上排查死鎖、Kill sp,我們應(yīng)該考慮如何去避免死鎖。
(2). 使用SET LOCK_TIMEOUT timeout_period(單位為毫秒)來設(shè)定鎖請求超時(shí)。默認(rèn)情況下,數(shù)據(jù)庫沒有超時(shí)期限(timeout_period值為-1,可以用SELECT @@LOCK_TIMEOUT來查看該值,即無限期等待)。當(dāng)請求鎖超過timeout_period時(shí),將返回錯(cuò)誤。timeout_period值為0時(shí)表示根本不等待,一遇到鎖就返回消息。設(shè)置鎖請求超時(shí),破環(huán)了死鎖的第二個(gè)必要條件(請求與保持條件)。
服務(wù)器: 消息 1222,級別 16,狀態(tài) 50,行 1
已超過了鎖請求超時(shí)時(shí)段。
(3). SQL Server內(nèi)部有一個(gè)鎖監(jiān)視器線程執(zhí)行死鎖檢查,鎖監(jiān)視器對特定線程啟動(dòng)死鎖搜索時(shí),會標(biāo)識線程正在等待的資源;然后查找特定資源的所有者,并遞歸地繼續(xù)執(zhí)行對那些線程的死鎖搜索,直到找到一個(gè)構(gòu)成死鎖條件的循環(huán)。檢測到死鎖后,數(shù)據(jù)庫引擎 選擇運(yùn)行回滾開銷最小的事務(wù)的會話作為死鎖犧牲品,返回1205 錯(cuò)誤,回滾死鎖犧牲品的事務(wù)并釋放該事務(wù)持有的所有鎖,使其他線程的事務(wù)可以請求資源并繼續(xù)運(yùn)行。
59. 線程之間是如何通信的
在JVM內(nèi)存中,方法區(qū)和堆區(qū)是線程共享的區(qū)域,可以直接調(diào)用,實(shí)現(xiàn)通信
定義全局變量
線程調(diào)用的時(shí)候傳遞參數(shù)
60. 終止線程
//5S之后終止.
Thread.sleep(5000);
//終止
p.run=false
61. 線程的yield
Thread.yield();
1.該方法是一個(gè)靜態(tài)方法.
2.作用:給同一個(gè)優(yōu)先級的線程讓位。但是讓位時(shí)間不固定。
3.和sleep方法相同,就是yield時(shí)間不固定。
62. join
Thread中,join()方法的作用是調(diào)用線程等待該線程完成后,才能繼續(xù)用下運(yùn)行。
下面例子中,主線程會等待t線程執(zhí)行完后再繼續(xù)向下執(zhí)行
63. 線程的同步意義(重點(diǎn))
異步編程模型:t1線程執(zhí)行t1的,t2線程執(zhí)行t2的,兩個(gè)線程之間誰也不等誰。
同步編程模型:t1線程和t2線程執(zhí)行,當(dāng)t1線程必須等t2線程執(zhí)行結(jié)束之后,t1線程才能執(zhí)行,這是同步編程模型。
1.什么時(shí)候要同步呢?為什么要引入線程同步呢?
為了數(shù)據(jù)的安全。java允許多線程并發(fā)控制盡管應(yīng)用程序的使用率降低,但是為了保證數(shù)據(jù)是安全的,必須加入線程同步機(jī)制(取款)。線程同步機(jī)制使程序變成了(等同)單線程。
2.什么條件下要使用線程同步?
第一:多線程環(huán)境共享同一個(gè)數(shù)據(jù).
第三:共享的數(shù)據(jù)涉及到修改操作。
以下程序演示取款例子。以下程序不使用線程同步機(jī)制,多線程同時(shí)對同一個(gè)賬戶進(jìn)行取款操作,會出現(xiàn)什么問題?
多線程的工作環(huán)境下怎么防止競爭資源,即防止對同一資源進(jìn)行并發(fā)操作,那就是使用加鎖機(jī)制。常用的線程鎖有:synchronized和Lock和volatile
l 加鎖必須要有鎖
l 執(zhí)行完后必須要釋放鎖
l 同一時(shí)間、同一個(gè)鎖,只能有一個(gè)線程執(zhí)行
64. Synchronized
對象鎖是對一個(gè)非靜態(tài)成員變量進(jìn)行synchronized修飾,或者對一個(gè)非靜態(tài)成員方法進(jìn)行synchronized進(jìn)行修飾,對于對象鎖,不同對象訪問同一個(gè)被synchronized修飾的方法的時(shí)候不會阻塞。對象鎖,則哪個(gè)線程最先擁有鎖對象,則哪個(gè)線程先執(zhí)行,其他線程阻塞。
[1] 對象鎖(方法鎖):
//對外提供一個(gè)取款的方法
public void withdraw(double money){ //對當(dāng)前賬戶進(jìn)行取款操作
//把需要同步的代碼,放到同步語句塊中.
原理:t1線程執(zhí)行到此處,遇到了synchronized關(guān)鍵字,就會去找this的對象鎖,如果找到this對象鎖,則進(jìn)入同步語句塊中執(zhí)行程序。當(dāng)同步語句塊中的代碼執(zhí)行結(jié)束之后,t1線程歸還this的對象鎖。
在t1線程執(zhí)行同步語句塊的過程中,如果t2線程也過來執(zhí)行以下代碼,也遇到synchronized關(guān)鍵字,所以也去找this的對象鎖,但是該對象鎖被t1線程持有,只能在這等待this對象的歸還。
面試題目:
[2] 類鎖
[1] synchronized關(guān)鍵字不能被繼承:
雖然可以用synchronized來定義方法,但是synchronized卻并不屬于方法定義的一部分,所以synchronized關(guān)鍵字并不能被繼承。
[2] 子類覆蓋父類中的synchronized方法:
如果父類中的某個(gè)方法使用了synchronized關(guān)鍵字,而子類中也覆蓋了這個(gè)方法,默認(rèn)情況下子類中的這個(gè)方法并不是同步的,必須顯示的在子類的這個(gè)方法中加上synchronized關(guān)鍵字才可。
[3] 子類調(diào)用父類中synchronized方法:
這樣雖然子類中的方法并不是同步的,但子類調(diào)用了父類中的同步方法,也就相當(dāng)子類方法也同步了。
65. Volatile
每個(gè)線程都有一個(gè)自己的本地內(nèi)存空間--線程??臻g。線程執(zhí)行時(shí),先把變量從主內(nèi)存讀取到線程自己的本地內(nèi)存空間,然后再對該變量進(jìn)行操作,對該變量操作完后,在某個(gè)時(shí)間再把變量刷新回主內(nèi)存
現(xiàn)在有兩個(gè)線程,一個(gè)是main線程,另一個(gè)是Run。它們都試圖修改isRunning變量。按照J(rèn)VM內(nèi)存模型,main線程將isRunning讀取到本地線程內(nèi)存空間,修改后,再刷新回主內(nèi)存。而Run線程會一直在自己的私有堆棧中讀取isRunning變量。因此,RunThread線程無法讀到main線程改變的isRunning變量,Run線程并不會終止!從而出現(xiàn)了死循環(huán)??!
解決方法:volatile private boolean isRunning = true;強(qiáng)制線程從主內(nèi)存中取volatile修飾的變量。每次都是從內(nèi)存中讀取最新值,保證了不同線程對這個(gè)變量進(jìn)行操作時(shí)的可見性,即一個(gè)線程修改了某個(gè)變量的值,這新值對其他線程來說是立即可見的。
[1] volatile關(guān)鍵字的非原子性
count變量使用volatile修飾,for循環(huán)中創(chuàng)建了100個(gè)線程,然后將這100個(gè)線程啟動(dòng)去執(zhí)行addCount(),每個(gè)線程執(zhí)行100次加1期望的正確的結(jié)果應(yīng)該是100*100=10000,但是,實(shí)際上count并沒有達(dá)到10000,總是小于10000。
原因是:volatile修飾的變量并不保證對它的操作(自增)具有原子性。比如,假設(shè) i 自增到 5,線程A從主內(nèi)存中讀取i,值為5,將它存儲到自己的線程空間中,執(zhí)行加1操作,值為6。此時(shí),CPU切換到線程B執(zhí)行,從主從內(nèi)存中讀取變量i的值。由于線程A還沒有來得及將加1后的結(jié)果寫回到主內(nèi)存,線程B就已經(jīng)從主內(nèi)存中讀取了i,因此,線程B讀到的變量 i 值還是5,相當(dāng)于線程B讀取的是已經(jīng)過時(shí)的數(shù)據(jù)了,從而導(dǎo)致線程不安全性。綜上,僅靠volatile不能保證線程的安全性。(原子性)
[2] 用于指令重排序優(yōu)化:保證有序性
volatile關(guān)鍵字修飾的變量不會被指令重排序優(yōu)化。
例如:線程B等待線程A把配置信息初始化成功后,使用配置信息去干活,線程A配置完成后會將flag=true,如果initialized變量不用volatile修飾,在線程A執(zhí)行的代碼中就有可能指令重排序。即:線程A會先執(zhí)行flag=true,再加載配置文件,這就意味著:配置信息還未成功初始化,但是flag=true了。那么就導(dǎo)致線程B的while循環(huán)“提前”跳出,拿著一個(gè)還未成功初始化的配置信息去干活,因此,flag變量就必須得用volatile修飾。這樣,就不會發(fā)生指令重排序,也即:只有當(dāng)配置信息被線程A成功初始化之后,flag變量才會初始化為true。
[3] volatile 與 synchronized 的比較
? volatile輕量級,只能修飾變量。主要作用是讓各個(gè)線程獲得最新的值。它強(qiáng)制線程每次從主內(nèi)存中講到變量,而不是從線程的私有內(nèi)存中讀取變量,從而保證了數(shù)據(jù)的可見性。不能用來同步,因?yàn)槎鄠€(gè)線程并發(fā)訪問volatile修飾的變量不會阻塞。
? Synchronized重量級,還可修飾方法。是通過加對象鎖或類鎖的方式,保證同一時(shí)刻只有一個(gè)線程對此對象進(jìn)行操作,操作完成后,歸還鎖,下一個(gè)線程才能繼續(xù)執(zhí)行。不僅保證可見性,而且還保證原子性。多個(gè)線程爭搶synchronized鎖對象時(shí),會出現(xiàn)阻塞。
66. Lock
Lock提供了比synchronized更多的功能。它控制線程的執(zhí)行的CPU,在總線之下只允許一個(gè)線程的CPU可以訪問某個(gè)內(nèi)存,當(dāng)某個(gè)線程執(zhí)行結(jié)束之后其他線程才能訪問變量資源。Lock的實(shí)現(xiàn)主要有ReentrantLock、ReadLock和WriteLock,ReentrantLock支持兩種鎖模式,公平鎖和非公平鎖。默認(rèn)的實(shí)現(xiàn)是非公平的。
synchronized和ReentrantLock的區(qū)別
l ReentrantLock 比Synchronized多了鎖投票,定時(shí)鎖等候和中斷鎖等候
l 如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷
l 如果使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時(shí)間以后,中斷等待,而干別的事情
l synchronized是托管給JVM執(zhí)行的,JVM會自動(dòng)釋放鎖定,而lock是Java寫的控制鎖的代碼。要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中;
l 在資源競爭不是很激烈的情況下, Synchronized的性能要優(yōu)于ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的性能會下降幾十倍,但是ReetrantLock的性能能維持常態(tài);
67. 線程池Executors
java.util.concurrent包的并發(fā)處理,這個(gè)包含有一系列能夠讓Java的并發(fā)編程變得更加簡單輕松的類。在這個(gè)包被添加以前,你需要自己去動(dòng)手實(shí)現(xiàn)自己的相關(guān)工具類。
Executor框架在java.util.cocurrent包下,通過該框架來控制線程的啟動(dòng)、執(zhí)行和關(guān)閉,可以簡化并發(fā)編程的操作。通過Executor來啟動(dòng)線程比使用Thread的start方法更好,除了更易管理,效率更好(用線程池實(shí)現(xiàn),節(jié)約開銷)外,還有關(guān)鍵的一點(diǎn):有助于避免this逃逸問題——如果我們在構(gòu)造器中啟動(dòng)一個(gè)線程,因?yàn)榱硪粋€(gè)任務(wù)可能會在構(gòu)造器結(jié)束之前開始執(zhí)行,此時(shí)可能會訪問到初始化了一半的對象用Executor在構(gòu)造器中。
多線程技術(shù)主要解決處理器單元內(nèi)多個(gè)線程執(zhí)行的問題,它可以顯著減少處理器單元的閑置時(shí)間,增加處理器單元的吞吐能力。假設(shè)一個(gè)服務(wù)器完成一項(xiàng)任務(wù)所需時(shí)間為:T1 創(chuàng)建線程時(shí)間,T2 在線程中執(zhí)行任務(wù)的時(shí)間,T3 銷毀線程時(shí)間。如果:T1 + T3 遠(yuǎn)大于 T2,則可以采用線程池,以提高服務(wù)器性能。
我們可以把并發(fā)執(zhí)行的任務(wù)傳遞給一個(gè)線程池,來替代為每個(gè)并發(fā)執(zhí)行的任務(wù)都啟動(dòng)一個(gè)新的線程。只要池里有空閑的線程,任務(wù)就會分配給一個(gè)線程執(zhí)行。在線程池的內(nèi)部,任務(wù)被插入一個(gè)阻塞隊(duì)列(Blocking Queue ),線程池里的線程會去取這個(gè)隊(duì)列里的任務(wù)。當(dāng)一個(gè)新任務(wù)插入隊(duì)列時(shí),一個(gè)空閑線程就會成功的從隊(duì)列中取出任務(wù)并且執(zhí)行它。
Java通過Executors提供四種線程池,分別為:
[1] newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。線程池為無限大,當(dāng)執(zhí)行第二個(gè)任務(wù)時(shí)第一個(gè)任務(wù)已經(jīng)完成,會復(fù)用執(zhí)行第一個(gè)任務(wù)的線程,而不用每次新建線程。
[2] newFixedThreadPool 創(chuàng)建一個(gè)定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊(duì)列中等待。定長線程池的大小最好根據(jù)系統(tǒng)資源進(jìn)行設(shè)置。如Runtime.getRuntime().availableProcessors()
[3] newScheduledThreadPool 創(chuàng)建一個(gè)定長線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。
[4] newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。
68. 線程池 Executor 生命周期
線程池Executor是異步的執(zhí)行任務(wù),因此任何時(shí)刻不能夠直接獲取提交的任務(wù)的狀態(tài)。這些任務(wù)有可能已經(jīng)完成,也有可能正在執(zhí)行或者還在排隊(duì)等待執(zhí)行。因此關(guān)閉線程池可能出現(xiàn)一下幾種情況:
平緩關(guān)閉shutdown():已經(jīng)啟動(dòng)的任務(wù)全部執(zhí)行完畢,同時(shí)不再接受新的任務(wù)
立即關(guān)閉shutdownNow():取消所有正在執(zhí)行和未執(zhí)行的任務(wù)
RUNNING:一旦構(gòu)造完成線程池就進(jìn)入了執(zhí)行狀態(tài)RUNNING,隨時(shí)準(zhǔn)備接受任務(wù)來執(zhí)行。
SHUTDOWN:線程池運(yùn)行中可以通過shutdown()和shutdownNow()改變到SHUTDOWN狀態(tài)
TERMINATED:一旦shutdown()或者shutdownNow()執(zhí)行完畢,線程池就進(jìn)入TERMINATED狀態(tài),此時(shí)線程池就結(jié)束了。
69. 守護(hù)線程
守護(hù)線程.
其他所有的用戶線程結(jié)束,則守護(hù)線程退出!
守護(hù)線程一般都是無限執(zhí)行的.
//將t1這個(gè)用戶線程修改成守護(hù)線程.
t1.setDaemon(true);
70. 定時(shí)器
71. Wait()與sleep()
對于sleep()方法,我們首先要知道該方法是屬于Thread類中的。而wait()方法,則是屬于Object類中的。
sleep()方法導(dǎo)致了程序暫停執(zhí)行指定的時(shí)間,讓出cpu該其他線程,但是他的監(jiān)控狀態(tài)依然保持者,當(dāng)指定的時(shí)間到了又會自動(dòng)恢復(fù)運(yùn)行狀態(tài)。
在調(diào)用sleep()方法的過程中,線程不會釋放對象鎖。
而當(dāng)調(diào)用wait()方法的時(shí)候,線程會放棄對象鎖,進(jìn)入等待此對象的等待鎖定池,只有針對此對象調(diào)用notify()方法后本線程才進(jìn)入對象鎖定池準(zhǔn)備獲取對象鎖進(jìn)入運(yùn)行狀態(tài)。
72. 多線程框架
為什么引入Executor線程池框架?
? 每次new Thread()耗費(fèi)性能并且創(chuàng)建的線程缺乏管理,被稱為野線程,可以無限制創(chuàng)建,之間相互競爭,會導(dǎo)致過多占用系統(tǒng)資源導(dǎo)致系統(tǒng)癱瘓。
? 采用線程池的優(yōu)點(diǎn):重用存在的線程,減少對象創(chuàng)建、消亡的開銷,可有效控制最大并發(fā)線程數(shù),提高系統(tǒng)資源的使用率,同時(shí)避免過多資源競爭,避免堵塞,提供定時(shí)執(zhí)行、定期執(zhí)行、單線程、并發(fā)數(shù)控制等功能
Executor框架包括:線程池,Executor,Executors,ExecutorService,CompletionService,F(xiàn)uture,Callable等。
[1] Executor為接口,定義在java.util.concurrent包下,只定義了一個(gè)方法:
public interface Executor {
void execute(Runnable command);
}
[2] Executors工廠類
通過Executors提供四種線程池
1. ExecutorService executorService = Executors.newFixedThreadPool(5)
創(chuàng)建固定數(shù)目線程的線程池。
2. ExecutorService executorService = Executors.newCachedThreadPool();
創(chuàng)建一個(gè)可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程。
3. ExecutorService executorService = Executors.newSingleThreadExecutor()
創(chuàng)建一個(gè)單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。
4. ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
創(chuàng)建一個(gè)定長線程池,支持定時(shí)及周期性任務(wù)執(zhí)行,多數(shù)情況下可用來替代Timer類。表示從提交任務(wù)開始計(jì)時(shí),5000毫秒后執(zhí)行
[3] ExecutorService
ExecutorService是一個(gè)接口,繼承了Executor接口,定義了一些生命周期的方法。下面代碼為ExecutorService接口的定義:
ExecutorService有三種狀態(tài):運(yùn)行、關(guān)閉、終止,ExecutorService在初建時(shí)屬于運(yùn)行狀態(tài),shutdown方法將執(zhí)行平緩的關(guān)閉過程:不再接受新的任務(wù),同時(shí)等待已經(jīng)提交的任務(wù)執(zhí)行完成,包括那些還未開始執(zhí)行的任務(wù)。shotdownNow()方法將粗暴的關(guān)閉過程:它將嘗試取消所有運(yùn)行中的任務(wù),并且不再啟動(dòng)隊(duì)列中尚未開始執(zhí)行的任務(wù)。
73. 反射機(jī)制
反射機(jī)制允許程序在運(yùn)行時(shí)取得任何一個(gè)已知名稱的class的所有屬性和方法。并可于運(yùn)行時(shí)改變fields內(nèi)容或喚起methods。,對于任意一個(gè)對象,都能調(diào)用它的任意一個(gè)方法
Java反射機(jī)制容許程序在運(yùn)行時(shí)加載、探知、使用編譯期間完全未知的class。
換言之,Java可以加載一個(gè)運(yùn)行時(shí)才得知名稱的class,獲得其完整結(jié)構(gòu)。
反射機(jī)制的作用:
l 在運(yùn)行時(shí)判斷任意一個(gè)對象所屬的類;
l 在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對象;
l 在運(yùn)行時(shí)判斷任意一個(gè)類所具有的成員變量和方法;
l 在運(yùn)行時(shí)調(diào)用任意一個(gè)對象的方法;
l 生成動(dòng)態(tài)代理。生成動(dòng)態(tài)代理,面向切片編程(在調(diào)用方法的前后各加棧幀).
1.反編譯:.class-->.java
2.通過反射機(jī)制訪問java類的屬性,方法,構(gòu)造方法等。
java.lang.Class; Class c=0x1234;
java.lang.reflect.Constructor; Constructor c=0x2356;
java.lang.reflect.Field; Field f=0x1478;
java.lang.reflect.Method; Method m=0x2589;
java.lang.reflect.Modifier; Modifie rm=0x2698;
classUser{
private String name;
public User(){}
public void m1(){}
}
1) 反射機(jī)制獲取class類型對象
2) 反射機(jī)制獲取屬性
3) 反射機(jī)制獲取某個(gè)類的方法
4) 獲取構(gòu)造方法并創(chuàng)建新對象
5) 獲取類的父類和父接口
6) 反射機(jī)制在動(dòng)態(tài)代理中的應(yīng)用:
7) 在泛型為Integer的ArrayList中存放一個(gè)String類型的對象。
8) 通過反射取得并修改數(shù)組的信息
9) 通過反射機(jī)制修改數(shù)組的大小
74. 反射的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
l 提程序靈活性
缺點(diǎn):
l 性能問題:
使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求。用于字段和方法接入時(shí)反射要遠(yuǎn)慢于直接代碼。性能問題的程度取決于程序中是如何使用反射的。如果它作為程序運(yùn)行中相對很少涉及的部分,緩慢的性能將不會是一個(gè)問題。
l 使用反射會模糊程序內(nèi)部實(shí)際要發(fā)生的事情:
程序人員希望在源代碼中看到程序的邏輯,反射等繞過了源代碼的技術(shù)會帶來維護(hù)問題。反射代碼比相應(yīng)的直接代碼更復(fù)雜。解決這些問題的最佳方案是保守地使用反射——僅在它可以真正增加靈活性的地方——記錄其在目標(biāo)類中的使用
75. 中間變量緩存機(jī)制
;
76. compareTo()
[1] 單個(gè)字符比較
Stringa="a",b="b";
System.out.println(a.compareto.b);
//則輸出-1;
若a="a",b="a"則輸出0;
若a="b",b="a"則輸出1;
[2] 字符串比較
a) 首字母不同
若a="ab",b="b",則輸出-1;
若a="abcdef",b="b"則輸出-1;
也就是說,如果兩個(gè)字符串首字母不同,則該方法返回首字母的asc碼的差值;
b) 首字母相同呢??
若a="ab",b="a",輸出1;
若a="abcdef",b="a"輸出5;
若a="abcdef",b="abc"輸出3;
若a="abcdef",b="ace"輸出-1;
即參與比較的兩個(gè)字符串如果首字符相同,則比較下一個(gè)字符,直到有不同的為止,返回該不同的字符的asc碼差值,如果兩個(gè)字符串不一樣長,可以參與比較的字符又完全一樣,則返回兩個(gè)字符串的長度差值
77. java創(chuàng)建對象的幾種方法(??)
作為java開發(fā)者,我們每天創(chuàng)建很多對象,但是我們通常使用依賴注入的方式管理系統(tǒng),比如:Spring去創(chuàng)建對象,然而這里有很多創(chuàng)建對象的方法:使用New關(guān)鍵字、使用Class類的newInstance方法、使用Constructor類的newInstance方法、使用Clone方法、使用反序列化。
1. 使用new關(guān)鍵字
2. 使用Clone的方法
無論何時(shí)我們調(diào)用一個(gè)對象的clone方法,JVM就會創(chuàng)建一個(gè)新的對象,將前面的對象的內(nèi)容全部拷貝進(jìn)去,用clone方法創(chuàng)建對象并不會調(diào)用任何構(gòu)造函數(shù)。要使用clone方法,我們必須先實(shí)現(xiàn)Cloneable接口并實(shí)現(xiàn)其定義的clone方法。如:Student stu2=<Student>stu.clone();這也是原型模式的應(yīng)用。
3. 使用反射手段
4. 使用反序列化(從硬盤到內(nèi)存的反序列化)
78. 棧內(nèi)存和堆內(nèi)存
Java把內(nèi)存分成兩種,一種叫做棧內(nèi)存,一種叫做堆內(nèi)存
l 基本類型的變量和對象的引用變量都是在函數(shù)的棧內(nèi)存中分配。當(dāng)在一段代碼塊中定義一個(gè)變量時(shí),java就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)超過變量的作用域后,java會自動(dòng)釋放掉為該變量分配的內(nèi)存空間,該內(nèi)存空間可以立刻被另作他用。
l 堆內(nèi)存用于存放由new創(chuàng)建的對象和數(shù)組。在堆中分配的內(nèi)存,由java虛擬機(jī)自動(dòng)垃圾回收器來管理。在堆中產(chǎn)生了一個(gè)數(shù)組或者對象后,還可以在棧中定義一個(gè)特殊的變量,這個(gè)變量的取值等于數(shù)組或者對象在堆內(nèi)存中的首地址,在棧中的這個(gè)特殊的變量就變成了數(shù)組或者對象的引用變量,以后就可以在程序中使用棧內(nèi)存中的引用變量來訪問堆中的數(shù)組或者對象,引用變量相當(dāng)于為數(shù)組或者對象起的一個(gè)別名,或者代號。
l 引用變量是普通變量,定義時(shí)在棧中分配內(nèi)存,引用變量在程序運(yùn)行到作用域外釋放。而數(shù)組&對象本身在堆中分配,即使程序運(yùn)行到使用new產(chǎn)生數(shù)組和對象的語句所在地代碼塊之外,數(shù)組和對象本身占用的堆內(nèi)存也不會被釋放,數(shù)組和對象在沒有引用變量指向它的時(shí)候,才變成垃圾,不能再被使用,但是仍然占著內(nèi)存,在隨后的一個(gè)不確定的時(shí)間被垃圾回收器釋放掉。這個(gè)也是java比較占內(nèi)存的主要原因,實(shí)際上,棧中的變量指向堆內(nèi)存中的變量,這就是 Java 中的指針!
79. String為空
str==null
"".equals(str)
str.length<=0
str.isEmpty()最優(yōu)
[1] str= =null;null表示這個(gè)字符串不指向任何的東西,如果這時(shí)候你調(diào)用它的方法,那么就會出現(xiàn)空指針異常。
[2] "".equals(str);""表示它指向一個(gè)長度為0的字符串,這時(shí)候調(diào)用它的方法是安全的。null不是對象,""是對象
[3] str.length<=0;length是集合屬性,length( )取得字符串長度
[4] str.isEmpty();
如果str1=null;下面的寫法錯(cuò)誤:
if(str1.equals("")||str1= =null){}
正確的寫法是if(str1==null||str1.equals("")){}
//所以在判斷字符串是否為空時(shí),先判斷是不是對象,如果是,再判斷是不是空字符串文章來源:http://www.zghlxwxcb.cn/news/detail-512203.html
所以,判斷一個(gè)字符串是否為空,首先就要確保他不是null,然后再判斷他的長度。文章來源地址http://www.zghlxwxcb.cn/news/detail-512203.html
到了這里,關(guān)于java面試常見知識點(diǎn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!