static關鍵字代表一個成員變量或者是成員方法能夠在沒有所屬的類的實例變量的狀況下被訪問。
Java中static方法不能被覆蓋,由於方法覆蓋(override)是基於運行時動態綁定的,而 static 方法是編譯時靜態綁定的。static 方法跟類的任何實例都不相關,因此概念上不適用。
不能夠在static 環境中訪問非static 變量,static變量在Java中是屬於類的,它在全部的實例中的值是同樣的。當類被 Java 虛擬機載入的時候,會對 static 變量進行初始化(與類同生死)。非static變量,只有類被建立時,纔會分配空間(若是是方法內的局部變量,那麼在方法被調用的時候才建立)。javascript
- 接口比抽象類更加抽象,由於抽象類中能夠定義構造器,能夠有抽象方法和具體方法,而接口中不能定義構造器並且其中的方法所有都是抽象方法。
- 類能夠實現不少個接口,可是隻能繼承一個抽象類。一個類若是繼承了某個抽象類或者實現了某個接口都須要對其中的抽象方法所有進行實現, 不然該類仍然須要被聲明爲抽象類。
- 抽象類能夠在不提供接口方法實現的狀況下實現接口。
- Java 接口中聲明的變量默認都是final的。抽象類能夠包含非final的變量。
- Java 接口中的成員函數默認是 public 的。抽象類的成員函數能夠是 private,protected 或者是 public。
- 接口是絕對抽象的,不能夠被實例化。抽象類也不能夠被實例化,可是,若是它包含 main方法的話是能夠被調用的。(抽象類和接口都不可以實例化,但能夠定義抽象類和接口類型的引用)
- 接口能夠繼承接口。抽象類能夠實現(implements)接口,抽象類可繼承具體類,但前提是具體類必須有明確的構造函數。
- 有抽象方法的類必須被聲明爲抽象類,而抽象類未必要有抽象方法。
接口能夠繼承接口,並且支持多重繼承。抽象類能夠實現(implements)接口,抽象類可繼承具體類也能夠繼承抽象類。
- 進程是程序的一次動態執行過程,每一個進程都有本身獨立的內存空間。一個應用程序能夠同時啓動多個進程(好比瀏覽器能夠開多個窗口,每一個窗口就是一個進程),進程是執行着的應用程序,而線程是進程內部的一個執行序列。一個進程能夠有多個線程。線程又叫作輕量級進程。
- 多進程操做系統可以運行多個進程,每一個進程都可以循環利用所須要的CPU時間片,使的全部進程看上去像在同時運行同樣。
- 線程是進程的一個執行流程,是CPU調度和分 派的基本單位。一個進程能夠由多個線程組成,也就是一個進程能夠同時運行多個不一樣的線程,每一個線程完成不一樣的任務。
- 線程的併發運行:就是一個進程內若干個線程同時運行。(好比:word的拼寫檢查功能和首字母自動大寫功能是word進程中的線程)
- 線程和進程的關係是一個局部和總體的關係,每一個進程都由操做系統分配獨立的內存地址空間,而同一進程的全部線程都在同一地址空間工做。(進程在執行時一般擁有獨立的內存單元,而線程之間能夠實現內存共享)
- 雖然使用多線程的編程一般可以帶來更好的性能和用戶體驗,可是多線程佔用更多的CPU資源,對於其餘的進程並不友好(可能佔用了更多的CPU 資源),也不是線程越多, 程序的性能就越好,由於線程之間的調度和切換也會浪費CPU 時間。
一個線程的完整生命週期要經歷5中狀態:新建、就緒、運行、阻塞、死亡。
java
- 新建狀態:使用new和某種線程的構造方法來建立線程對象,該線程就會進入新建狀態,系統爲該線程對象分配內存空間。處於新建狀態的線程能夠經過調用start()方法進入就緒狀態。
- 就緒狀態(Runnable):此時線程已經具有了運行的條件,進入了線程隊列,等待系統分配CPU資源,一旦得到CPU資源,該線程就會進入運行狀態。(線程準備運行,不必定立馬就能開始執行)
- 運行狀態(Running):進入運行在狀態,線程會執行本身的run()方法中的代碼。就是進程正在執行線程的代碼。
- 阻塞狀態(Blocked):一個正在執行的線程,若是執行了suspend、join或sleep方法,或等待io設備的使用權,那麼該線程將會讓出本身的CUP控制權並暫時停止本身的執行,進入阻塞狀態。阻塞的線程,不可以進入就緒隊列,只有當阻塞緣由被消除的時候,線程才能進入就緒狀態,從新進入線程隊列中排隊等待CPU資源,而後繼續執行。
- 死亡狀態(Dead):一個線程完成了所有工做或者被提早強制性的停止,該線程就處於死亡狀態。
- 睡眠狀態(Sleeping):線程經過
Thread.sleep(線程睡眠)、Object.wait(線程等待)、LockSupport.park(線程暫停)
三種方式 使線程進入休眠狀態。
同步阻塞(Blocked on Synchronization):sleep( ) 使線程在必定的時間內進入阻塞狀態(不會釋放鎖資源)、wait( ) 使線程進入阻塞狀態,(釋放本身佔有的鎖資源,搭配notify( )使用)、suspend( ) 使線程進入阻塞狀態(必須其對應的resume( )被調用,才能使線程從新進入可執行狀態)
wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常;
notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM 肯定喚醒哪一個線程,並且與優先級無關;
notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;mysql
Thread類的sleep()方法和對象的wait()方法c++
sleep()*方法(休眠)是*線程類( Thread)*的*靜態方法,調用此方法讓當前線程暫停執行指定的時間, 將執行機會( CPU)讓給其餘線程, 可是
對象的鎖依然保持
,所以休眠時間結束後會自動恢復(線程回到就緒狀態)。
*wait()* 方法是Object 類的方法,調用對象的wait()方法致使當前線程放棄對象的鎖
(線程暫停執行), 進入對象的等待池(wait pool),只有調用對象的notify()
方法(或notifyAll()
方法)時才能喚醒等待池中的線程進入等鎖池(lock pool), 若是線程從新得到對象的鎖就能夠進入就緒狀態。程序員線程的sleep()方法和yield()方法web
1)sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
2)線程執行sleep()方法後轉入阻塞( blocked)狀態, 而執行yield()方法後轉入就緒( ready)狀態;
3)sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
4)sleep()方法比yield()方法(跟操做系統CPU調度相關)具備更好的可移植性。面試
在面向對象編程中,建立和銷燬對象是很費時間的,由於建立一個對象要獲取內存資源或者其它更多資源。在Java 中,虛擬機將試圖跟蹤每個對象,以便可以在對象銷燬後進行垃圾回收。因此提升服務程序效率的一個手段就是儘量減小建立和銷燬對象的次數,特別是一些很耗資源的對象建立和銷燬,這就是**
池化資源技術
**產生的緣由。
線程池顧名思義就是事先建立若干個可執行的線程放入一個池(容器)中,須要的時候從池中獲取線程不用自行建立, 使用完畢不須要銷燬線程而是放回池中,從而減小建立和銷燬線程對象的開銷。
Java 5+中的Executor接口定義一個執行線程的工具。它的子類型即線程池接口是ExecutorService。工具類Executors
面提供了一些靜態工廠方法,生成一些經常使用的線程池。
工具類Executors
生成線程池的經常使用方法:正則表達式1)newSingleThreadExecutor:建立一個單線程的線程池。這個線程池只有一個線程在工做,也就是至關於單線程串行執行全部任務。若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它。此線程池保證全部任務的執行順序按照任務的提交順序執行。
2) newFixedThreadPool:建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結束,那麼線程池會補充一個新線程(返回到線程池中)。【推薦使用】
3)newCachedThreadPool:建立一個可緩存的線程池。若是線程池的大小超過了處理任務所須要的線程,那麼就會回收部分空閒(60 秒不執行任務)的線程,當任務數增長時,此線程池又能夠智能的添加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系統(或者說JVM)可以建立的最大線程大小。
4)newScheduledThreadPool:建立一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。
5)newSingleThreadExecutor:建立一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。算法若是但願在服務器上使用線程池,強烈建議使用newFixedThreadPool方法來建立線程池,這樣能得到更好的性能。sql
java 中的線程分爲兩種:守護線程( Daemon)和用戶線程( User)。
j任何線程均可以設置爲守護線程和用戶線程,經過方法Thread.setDaemon(boolon);
true則把該線程設置爲守護線程,false則設置爲用戶線程。Thread.setDaemon()
必須在Thread.start()以前調用,不然運行時會拋出異常。守護線程和本地線程二者的區別
惟一的區別是判斷虛擬機(JVM)什麼時候離開,守護線程Daemon是爲其餘線程提供服務,若是所有的用戶現場Thread 已經撤離, Daemon 沒有可服務的線程,JVM 撤離。也可
以理解爲守護線程是JVM 自動建立的線程( 但不必定),用戶線程是程序建立的線程;好比JVM 的垃圾回收線程是一個守護線程,當全部線程已經撤離,再也不產生垃圾,守護線程天然就沒事可幹了,當垃圾回收線程是Java 虛擬機上僅剩的線
程時,Java 虛擬機會自動離開。
1)synchronized 關鍵字能夠將對象或者方法標記爲同步,以實現對對象和方法的互斥訪問,能夠用
synchronized(對象) { }
定義同步代碼塊,或者在聲明方法時將synchronized做爲方法的修飾符
。
2)不能進入。
其它線程只能訪問該對象的非同步方法,同步方法則不能進入。由於非靜態方法上的synchronized修飾符要求執行方法時要得到對象的鎖,若是已經進入A方法說明對象鎖已經被取走,那麼試圖進入B 方法的線程就只能在等鎖池( 注意不是等待池)中等待對象的鎖。
Lock是JDK1.5 之後引入的新的API,和關鍵字synchronized 的異同:
相同點:Lock 能完成synchronized 所實現的全部功能;
主要不一樣點:Lock有比synchronized 更精確的線程語義和更好的性能,並且不強制性的要求必定要得到鎖。synchronized會自動釋放鎖,而Lock必定要求程序員手工釋放,而且最好在finally 塊中釋放(這是釋放外部資源的最好的地方)。
- 死鎖、活鎖、飢餓 的概念,之間的區別
1)死鎖:兩個進程都在等待對方執行完畢才能繼續往下執行的時候就發生了死鎖。結果就是兩個進程都陷入了無限的等待中。因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去
2)活鎖:任務或者執行者沒有被阻塞,因爲某些條件沒有知足,致使一直重複嘗試,失敗,嘗試, 失敗。
3)飢餓:一個或者多個線程由於種種緣由沒法得到所須要的資源,致使一直沒法執行的狀態。活鎖和死鎖的區別:
處於活鎖的實體是在不斷的改變狀態,所謂的「 活」, 而
處於死鎖的實體表現爲等待;活鎖有可能自行解開,死鎖則不能。Java 中致使飢餓的緣由:
1)高優先級線程吞噬全部的低優先級線程的CPU 時間。
2)線程被永久堵塞在一個等待進入同步塊的狀態,由於其餘線程老是能在它以前
持續地對該同步塊進行訪問。
3)線程在等待一個自己也處於永久等待完成的對象(好比調用這個對象的wait 方
法),由於其餘線程老是被持續地得到喚醒。
- 產生死鎖的必要條件:
1)互斥條件:所謂互斥就是進程在某一時間內獨佔資源。
2)請求與保持條件:一個進程因請求資源而阻塞時,對已得到的資源保持不放。
3)不剝奪條件:進程已得到資源,在末使用完以前,不能強行剝奪。
4)循環等待條件:若干進程之間造成一種頭尾相接的循環等待資源關係。
- 如何確保 N 個線程能夠訪問N 個資源同時又不致使死鎖
使用多線程的時候,一種很是簡單的避免死鎖的方式就是:指定獲取鎖的順序,並強制線程按照指定的順序獲取鎖。所以,若是全部的線程都是以一樣的順序加鎖和釋放鎖,就不會出現死鎖了。
多線程的上下文
多線程會共同使用一組計算機上的CPU,而線程數大於給程序分配的CPU 數量時,爲了讓各個線程都有執行的機會,就須要輪轉使用CPU。不一樣的線程切換使用CPU發生的切換數據等就是上下文切換。
- 堆:存放方法對象(經過new 關鍵字和構造器建立的對象)
- 棧:存放方法以及方法中的局部變量
- 方法區:(也叫共享區)存放代碼片斷、靜態屬性、常量池(好比常量池中存放字符串的值)
一般咱們定義一個基本數據類型的變量,一個對象的引用,函數調用的現場保存都使用JVM 中的棧空間;而經過new 關鍵字和構造器建立的對象則放在堆空間,堆是垃圾收集器管理的主要區域,因爲如今的垃圾收集器都採用分代收集算法,因此堆空間還能夠細分爲新生代和老生代,再具體一點能夠分爲Eden、Survivor(又可分爲From Survivor 和To Survivor)、Tenured;方法區和堆都
是各個線程共享的內存區域,用於存儲已經被JVM 加載的類信息、常量、靜態變
量、JIT 編譯器編譯後的代碼等數據;程序中的字面量(literal)如直接書寫的100、」
hello」 和常量都是放在常量池中, 常量池是方法區的一部分。棧空間操做起來
最快可是棧很小,一般大量的對象都是放在堆空間,棧和堆的大小均可以經過JVM
的啓動參數來進行調整,棧空間用光了會引起StackOverflowError,而堆和常量
池空間不足則會引起OutOfMemoryError。
String str = new String("HelloWorld");
,這個代碼中,變量str 存放在棧上,用new 建立出來的字符串對象放在堆上,」HelloWorld」 這個字面量(String)是放在方法區中。
JDK1.5 之前,switch()的參數中,只能是
byte、short、char、int
。
從JDK1.5 開始, Java 中引入了枚舉類型與byte short char int的包裝類。
從JDK 1.7 開始,參數還能夠是字符串( String)類型,可是長整型( long)在目前全部的版本中都是不能夠的。
也就是如今switch支持byte、short、char、int、String、枚舉
JDK1.5,對四個包裝類的支持是由於java編譯器在底層手動進行拆箱,而對枚舉類的支持是由於枚舉類有一個ordinal方法,該方法其實是一個int類型的數值。
jdk1.7開始支持String類型,但實際上String類型有一個hashCode算法,結果也是int類型.而byte short char類型能夠在不損失精度的狀況下向上轉型成int類型,因此總的來講,能夠認爲switch中只支持int.
不對,若是兩個對象x 和y 知足x.equals(y) == true,它們的哈希碼(hash code)
應當相同。
Java 對於eqauls 方法和hashCode 方法是這樣規定的:1)若是兩個對象相同(equals 方法返回true),那麼它們的hashCode 值必定要相同;
2)若是兩個對象的hashCode 相同,它們並不必定相同。固然,你未必要按照要求去作,可是(重寫hashCode)若是你違背了上述原則就會發如今使用容器時,相同的對象能夠出如今Set 集合中,同時增長新元素的效率會大大降低(對於使用哈希存儲的系統,若是哈希碼頻繁的衝突將會形成存取性能急劇降低)。關於equals方法,有如下內容必須知足
首先equals 方法必須知足
1)自反性( x.equals(x)必須返回true)
2)對稱性( x.equals(y)返回true 時, y.equals(x)也必須返回true)
3)傳遞性(x.equals(y)和y.equals(z)都返回true 時, x.equals(z)也必須返回true)
4)一致性(當x和y引用的對象信息沒有被修改時,屢次調用x.equals(y)應該獲得一樣的返回值) ,並且對於任何非null值的引用x,x.equals(null)必須返回false。實現高質量的equals 方法的訣竅包括
1)使用==操做符檢查」參數是否爲這個對象的引用」;
2)使用instanceof 操做符檢查」參數是否爲正確的類型」;
3) 對於類中的關鍵屬性,檢查參數傳入對象的屬性是否與之相匹配;
4)重寫equals方法後,問本身它是否知足自反性、對稱性、傳遞性、一致性;
5) 重寫equals 時老是要重寫hashCode;
6) 不要將equals 方法參數中的Object 對象替換爲其餘的類型,在重寫時不要忘掉@Override 註解。
Java平臺提供了兩種類型的字符串: String 和StringBuffer/StringBuilder,它們能夠儲存和操做字符串。
其中String 是隻讀字符串,也就意味着String 引用的字符串內容是不能被改變的。
而StringBuffer/StringBuilder類表示的字符串對象能夠直接進行修改。StringBuilder 是JDK 1.5 中引入的,它和StringBuffer 的方法徹底相同, 區別在於StringBuffer由於被synchronized 修飾,是線程安全的而StringBuilder是線程不安全的(全部方面都沒有被synchronized 修飾)
由於StringBuilder沒有被synchronized同步修飾,因此StringBuilder【面試題】什麼狀況下用+運算符進行字符串鏈接比調用StringBuffer/StringBuilder 對象的append 方法鏈接字符串性能更好?
首先要清楚如下兩點:
1)String 對象的intern方法會獲得字符串對象在常量池中對應的版本的引用(若是常量池中有一個字符串與String 對象的equals結果是true),若是常量池中沒有對應的字符串,則該字符串將被添加到常量池中, 而後返回常量池中字符串的引用;
2)字符串的+操做其本質是建立了StringBuilder對象進行append操做,而後將拼接後的StringBuilder 對象用toString方法處理成String 對象。
方法的重載和重寫都是實現多態的方式,區別在於重載實現的是編譯時的多態性,而重寫實現的是運行時的多態性。
所謂的重載是編譯時多態性,就是根據實際的參數列表,在編譯的時候就可以肯定執行重載方法中的哪個了。
所謂的重寫是運行時多態性,就好比父類對象引用子類實例時,調用方法只有在運行時才知道到底執行了哪一個方法。
重載發生在一個類中,同名的方法若是有不一樣的參數列表( 參數類型不一樣、參數個數不一樣或者兩者都不一樣)則視爲重載;(重載對返回類型沒有特殊的要求,只考慮參數列表)
重寫發生在子類與父類之間(須要繼承),重寫要求子類被重寫方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常( 里氏代換原則)。
Java 虛擬機是一個能夠執行 Java 字節碼的虛擬機進程。Java 源文件被編譯成能被 Java 虛擬機執行的字節碼文件。
Java 被設計成容許應用程序能夠運行在任意的平臺,而不須要程序員爲每個平臺單獨重寫或者是從新編譯。Java 虛擬機讓這個變爲可能,由於它知道底層硬件平臺的指令長度和其餘特性。
Java代碼在JVM中的執行流程
JVM的類加載原理圖
JVM 中類的裝載是由類加載器(ClassLoader
)和它的子類來實現的, Java 中的類加載器是一個重要的Java 運行時系統組件,它負責在運行時查找和裝入類文件中的類。
因爲Java 的跨平臺性,通過編譯的Java 源程序並非一個可執行程序,而是一個或多個類class文件。當Java 程序須要使用某個類時,JVM 會確保這個類已經被加載、鏈接( 驗證、準備和解析)和初始化。類的加載是指把類的.class 文件中的數據讀入到內存中,一般是建立一個字節數組讀入.class 文件,而後產生與所加載類對應的Class 對象。加載完成後,Class 對象還不完整,因此此時的類還不可用。當類被加載後就進入鏈接階段,這一階段包括驗證、準備( 爲靜態變量分配內存並設置默認的初始值)和解析( 將符號引用替換爲直接引用)三個步驟。最後JVM 對類進行初始化,包括:
1)若是類存在直接的父類而且這個類尚未被初始化,那麼就先初始化父類;
2)若是類中存在初始化語句, 就依次執行這些初始化語句。類的加載是由類加載器完成的
類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器( System)和用戶自定義類加載器(java.lang.ClassLoader 的子類) 。
從Java 2( JDK 1.2)開始, 類加載過程採起了父親委託機制(PDM)。PDM 更好的保證了Java 平臺的安全性,在該機制中, JVM 自帶的Bootstrap 是根加載器, 其餘的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM 不會向Java 程序提供對Bootstrap 的引用。
Bootstrap
:通常用本地代碼實現,負責加載JVM 基礎核心類庫(rt.jar
);Extension
:從java.ext.dirs 系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;System
:又叫應用類加載器,其父類是Extension。它是應用最普遍的類加載器。它從環境變量classpath 或者系統屬性java.class.path 所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。
理論上,Java由於有垃圾回收機制( GC)不會存在內存泄露問題( 這也是Java 被普遍使用於服務器端編程的一個重要緣由);
然而在實際開發中,可能會存在無用但可達的對象
,這些對象不能被GC 回收,所以也會致使內存泄露的發生。
例如:Hibernate 的Session( 一級緩存)中的對象屬於持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,若是不及時關閉(close)或清空( flush)一級緩存就可能致使內存泄露。
//實現了一個棧(先進後出(FILO))結構代碼 import java.util.Arrays; import java.util.EmptyStackException; public class MyStack<T> { private T[] elements; private int size = 0; private static final int INIT_CAPACITY = 16; public MyStack() { elements = (T[]) new Object[INIT_CAPACITY]; } public void push(T elem) { ensureCapacity(); elements[size++] = elem; } public T pop() { if(size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if(elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } } 123456789101112131415161718192021222324252627282930
上述代碼是實現了一個棧的先進後出(FILO)結構,可是其中的pop 方法卻存在
內存泄露的問題
當咱們用pop 方法彈出棧中的對象時,該對象不會被看成垃圾回收,即便使用棧的程序再也不引用這些對象, 由於棧內部維護着對這些對象的過時引用(obsolete reference)。
在支持垃圾回收的語言中,內存泄露是很隱蔽的,這種內存泄露其實就是無心識的對象保持。若是一個對象引用被無心識的保留起來了,那麼垃圾回收器不會處理這個對象,也不會處理該對象引用的其餘對象,即便這樣的對象只有少數幾個,也可能會致使不少的對象被排除在垃圾回收以外,從而對性能形成重大影響,極端狀況下會引起Disk Paging( 物理內存與硬盤的虛擬內存交換數據),甚至形成OutOfMemoryError。
答案是都不能。
1)抽象方法須要子類重寫,而靜態的方法是沒法被重寫的,所以兩者是矛盾的。
2)本地方法是由本地代碼(如C代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。
3)synchronized 和方法的實現細節有關,抽象方法不涉及實現細節, 所以也是相互矛盾的。
靜態變量是被static修飾符修飾的變量,也稱爲類變量, 它屬於類(與類同生死),不屬於類的任何一個對象,一個類無論建立多少個對象, 靜態變量在內存中有且僅有一個拷貝;靜態變量能夠實現讓多個對象共享內存。
實例變量必須依存於某一實例,須要先建立對象而後經過對象才能訪問到它。
(在Java 開發中, 上下文類和工具類中一般會有大量的靜態成員。)
對象是引用數據類型,對象的賦值僅僅是吧對象的引用賦值給另外一個對象,他們的堆空間是相同的。
克隆就是須要有一個徹底相同的對象,並且二者之間徹底互不影響,克隆對象和原對象是兩個徹底對立的對象。(java.lang.Object類的clone()方法,對象克隆就是對象的複製,即完整的複製出一個對象。)1)實現
Cloneable
接口並重寫Object 類中的clone()方法;(淺克隆對象的克隆默認是淺度克隆,也就是包含在對象內部的對象是不會被克隆的),【經過clone()實現深度克隆:讓克隆對象所包含的類也實現clone功能(實現cloneable接口,重載clone()方法)】
2)實現Serializable
接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆基於序列化和反序列化(
Serializable
)實現的克隆不只僅是深度克隆, 更重要的是經過泛型限定,能夠檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object 類的clone 方法克隆對象。讓問題在編譯的時候暴露出來老是好過把問題留到運行時。
Serializable序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化。能夠對流化後的對象進行讀寫操做,也可將流化後的對象傳輸於網絡之間。
序列化是爲了解決對象流讀寫操做時可能引起的問題( 若是不進行序列化可能會存在數據亂序的問題) 。
要實現序列化,須要讓一個類實現Serializable
接口,該接口是一個標識性接口,標註該類對象是可被序列化的,而後使用一個輸出流來構造一個對象輸出流並經過writeObject(Object)方法就能夠將實現對象寫出(即保存其狀態);
若是須要反序列化則能夠用一個輸入流創建對象輸入流,而後經過readObject 方法從流中讀取對象。序列化除了可以實現對象的持久化以外,還可以用於對象的深度克隆(上面所說的)。
java中由JVM的垃圾回收管理器(GC)來負責回收不須要再使用的堆內存和棧內存。GC 功能能夠自動監測對象是否超過做用域從而達到自動回收內存的目的。
JVM的垃圾回收採用動態存儲管理技術,它可以本身檢測內存的使用狀況,自動地釋放再也不被程序引用的對象,按照特定的垃圾收集算法來實現內存資源自動回收功能。
JVM垃圾回收管理器處理釋放沒用的對象外,還能夠清除內存記錄的碎片(由於建立對象和垃圾收集齊全不斷釋放對其對象所佔用的空間,所以內存會出現碎片)。碎片整理將所佔用的堆內存移動到堆的一端,JVM將整理出的內存分配給新的對象。
JVM垃圾回收器有一個潛在的缺點 : 增長了系統的開銷,影響了程序的性能。由於JVM必須追蹤運行程序中有用的對象,到最終釋放沒用的對象,這個過程須要花費處理器的時間。
一個對象若是再也不被任何棧內存所引用,則該對象成爲垃圾對象。垃圾收集器對垃圾對象的收集時間是不肯定的,也能夠直接使用System.gc()
方法Runtime.getRuntime().gc()
回收垃圾對象,可是這種強制執行垃圾回收程序對系統性能會產生負面影響。(JVM 能夠屏蔽掉顯示的垃圾回收調用,就是顯示調用方法,JVM的GC會自動進行管理)雖然你能夠調用System.gc() 或者Runtime.gc(),可是沒有辦法保證GC的執行。垃圾回收能夠有效的防止內存泄露, 有效的使用能夠使用的內存。垃圾回收器一般是做爲一個單獨的低優先級的線程運行,不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回
收器對某個對象或全部對象進行垃圾回收。在Java 誕生初期,垃圾回收是Java
最大的亮點之一,由於服務器端的編程須要有效的防止內存泄露問題,然而時過
境遷,現在Java 的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶一般
以爲iOS 的系統比Android 系統有更好的用戶體驗,其中一個深層次的緣由就在
於Android 系統中垃圾回收的不可預知性。補充:垃圾回收機制有不少種,包括:分代複製垃圾回收、標記垃圾回收、增量
垃圾回收等方式。標準的Java 進程既有棧又有堆。棧保存了原始型局部變量,堆
保存了要建立的對象。Java 平臺對堆內存回收和再利用的基本算法被稱爲標記和
清除,可是Java 對其進行了改進,採用「 分代式垃圾收集」。這種方法會跟Java
對象的生命週期將堆內存劃分爲不一樣的區域, 在垃圾收集過程當中,可能會將對象
移動到不一樣區域:
1)將字符串轉換爲基本數據類型
調用基本數據類型對應的包裝類中的方法parseXXX(String)或valueOf(String)
便可返回相應基本類型;
2)將基本數據類型轉換爲字符串
一種方法是將基本數據類型與空字符串""
經過+
鏈接便可得到其所對應的字符串;
另外一種方法是調用String 類中的valueOf()方法返回相應字符串。String.valueOf()
Java和JavaScript介紹
JavaScript 與Java 是兩個公司開發的不一樣的兩個產品。Java 是原Sun Microsystems 公司推出的面向對象的程序設計語言,特別適合於互聯網應用程序開發;而JavaScript 是Netscape 公司的產品,爲了擴展Netscape瀏覽器的功能而開發的一種能夠嵌入Web 頁面中運行的基於對象和事件驅動的解釋性語言。
JavaScript 的前身是LiveScript;而Java 的前身是Oak 語言。Java和JavaScript的區別
1)Java是面向對象的而JavaScript是基於對象的。
Java 是一種真正的面向對象的語言,即便是開發簡單的程序,必須設計對象;JavaScript 是種腳本語言,它能夠用來製做與網絡無關的,與用戶交互做用的複雜軟件。它是一種基於對象(Object-Based)和事件驅動(Event-Driven)的編程語言,於是它自己提供了很是豐富的內部對象供設計人員使用。
2)Java是編譯運行,而JavaScript是解釋執行的。
Java 的源代碼在執行以前,必須通過編譯。JavaScript 是一種解釋性編程語言,其源代碼不需通過編譯,由瀏覽器解釋執行。(目前的瀏覽器幾乎都使用了JIT(即時編譯)技術來提高JavaScript 的運行效率
3)Java變量必須先聲明後使用,JavaScript是弱類型語言,直接使用。
Java採用強類型變量檢查,即全部變量在編譯以前必須做聲明;JavaScript 中變量是弱類型的,甚至在使用變量前能夠不做聲明,JavaScript 的解釋器在運行時檢查推斷其數據類型。
4)Java是靜態語言,JavaScript是動態語言。
try{}裏有一個return語句返回,那麼緊跟在這個try後的finally{}裏的代碼也是會被執行的,是在方法返回調用前(return前執行)
若是存在finally代碼塊,try中的return語句不會立馬返回調用者,而是記錄下返回值待finally代碼塊執行完畢以後再向調用者返回其值(執行return)。
在finally中改變返回值的作法是很差的,若是在finally 中修改了返回值,就會返回修改後的值。在finally 中返回或者修改返回值會對程序形成很大的困擾,C#中直接用編譯錯誤的方式來阻止程序員幹這種齷齪的事情,Java 中也能夠經過提高編譯器的語法檢查級別來產生警告或錯誤(idea、eclipse也能本身設置)
異常表示程序運行過程當中可能出現的非正常狀態。
運行時異常:表示虛擬機的一般操做中可能遇到的異常,是一種常見運行錯誤,只要程序設計得沒有問題一般就不會發生。(在運行是拋出)
受檢查異常跟程序運行的上下文環境有關,即便程序設計無誤, 仍然可能因使用的問題而引起。Java 編譯器要求方法必須聲明拋出可能發生的受檢查異常,可是並不要求必須聲明拋出未被捕獲的運行時異常。
常見的運行時異常:ArithmeticException(算術異常)
ClassCastException (類轉換異常)
IllegalArgumentException (非法參數異常)
IndexOutOfBoundsException (下標越界異常)
NullPointerException (空指針異常)
SecurityException (安全異常)
final關鍵字
一個基本數據類型聲明爲final,就只能進行一次賦值(初始化),編譯器會自動檢查代碼,若是須要修改final的初始化,就會編譯報錯。final聲明的引用數據類型,當前引用是不能改變的,可是能夠改變引用指向的內存空間的值。final通常和static搭配使用做爲常量。
final關鍵字的三種用法:
1)修飾類:表示該類不能被繼承;
2)修飾方法:表示方法不能被重寫;
3)修飾變量: 表示變量只能一次賦值之後值不能被修改(常量)(final修飾成員變量必須在聲明時初始化或者再構造器中初始化,不然報編譯錯誤,並且final修飾的變量一般都是常量,常量名所有大寫)
若是一個類被聲明爲final,意味着它不能再派生出新的子類,即不能被繼承,所以它和abstract 是反義詞。將變量聲明爲final,能夠保證它們在使用中不被改變,被聲明爲final 的變量必須在聲明時給定初值,而在之後的引用中只能讀取不可修改。被聲明爲final 的方法也一樣只能使用,不能在子類中被重寫。final優點:
1)final關鍵字能夠提升性能,JVM和Java應用都會緩存final變量。
2)final變量能夠在安全的多線程環境下進行資源共享,而不須要額外的同步開銷。finally
一般放在try…catch…的後面構造老是執行代碼塊,這就意味着程序不管正常執行仍是發生異常,這裏的代碼只要JVM 不關閉都能執行,能夠將釋放外部資源的代碼寫在finally 塊中(關閉數據庫鏈接、釋放資源)。
finalize
Object 類中定義的方法,Java 中容許使用finalize()方法在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。這個方法是由垃圾收集器在銷燬對象時調用的,經過重寫finalize()方法能夠整理系統資源或者執行其餘清理工做。
List以特定索引來存取元素,能夠有重複元素。
Set 不能存放重複元素(用對象的equals()*方法來區分元素是否重複)。
**Map**保存*鍵值對( key-value)映射,映射關係能夠是一對一或多對一。(Map不支持一對多,可是能夠用Map<Integer, List>
這種格式來達到一對多的系,多對多相似)Set和Map容器都有基於哈希存儲和排序樹的兩種實現版本,基於哈希存儲的版本理論存取時間複雜度爲O(1),而基於排序樹版本的實如今插入或刪除元素時會按照元素或元素的鍵( key)構成排序樹從而達到排序和去重的效果。
ArrayList和Vector都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增長和插入元素,它們都容許直接按序號索引元素,可是插入元素要涉及數組元素移動等內存操做,因此索引查詢數據快而插入數據慢,
Vector 中的方法因爲添加了synchronized 修飾,所以Vector 是線程安全的容器,但性能上較ArrayList 差,所以已是Java 中的遺留容器。
LinkedList 使用雙向鏈表
實現存儲( 將內存中零散的內存單元經過附加的引用關聯起來,造成一個能夠按序號索引的線性結構,這種鏈式存儲方式與數組的連續存儲方式相比, 內存的利用率更高) ,按序號索引數據須要進行前向或後向遍歷,可是插入數據時只須要記錄本項的先後項便可,因此插入速度較快。
Vector 屬於遺留容器(Java 早期的版本中提供的容器, 除此以外,Hashtable、Dictionary、BitSet、Stack、Properties都是遺留容器),已經不推薦使用, 可是因爲ArrayList和LinkedListed都是非線程安全的
, 若是遇到多個線程操做同一個容器的場景,則能夠經過工具Collections 中的synchronizedList 方法將其轉換成線程安全的容器後再使用( 這是對裝潢模式的應用, 將已有對象傳入另外一個類的構造器中建立新的對象來加強實現)。
Collection是一個接口, 它是Set、List 等容器的父接口。
Collections是個一個工具類,提供了一系列的靜態方法來輔助容器操做,這些方法包括對容器的搜索、排序、線程安全化等等。Collections工具類的sort方法有兩種重載的形式,第一種要求傳入
的待排序容器中存放的對象比較實現Comparable接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較, 可是要求傳入第二個參數, 參數是Comparator接口的子類型(須要重寫compare 方法實現元素的比較),至關於一個臨時定義的排序規則,其實就是經過接口注入比較元素大小的算法, 也是對回調模式的應用( Java 中對函數式編程的支持)。TreeSet要求存放的對象所屬的類必須實現
Comparable
接口,該接口提供了比較元素的compareTo()
方法,當插入元素時會回調該方法比較元素的大小。
TreeMap要求存放的鍵值對映射的鍵必須實現Comparable 接口從而根據鍵對元素進行排序。
1)XML文檔定義分爲DTD和Schema兩種形式,兩者都是對XML語法的約束。
2)DTD和Schema兩種形式的本質區別在於Schema自己也是一個XML文件,能夠被XML 解析器解析,並且能夠爲XML 承載的數據定義類型,約束能力較之DTD更強大。
3)對XML的解析主要有DOM(文檔對象模型,Document Object Model)、SAX( Simple API forXML)和StAX(JDK1.6 中引入的新的解析XML的方式,Streaming API for XML)。其中DOM處理大型文件時其性能降低的很是厲害,這個問題是由DOM 樹結構佔用的內存較多形成的,並且DOM 解析方式必須在解析文件以前把整個文檔裝入內存,適合對XML 的隨機訪問( 典型的用空間換取時間的策略);
SAX是事件驅動型的XML解析方式,它順序讀取XML 文件,不須要一次所有裝載整個文件。當遇到像文件開頭,文檔結束,或者標籤開頭與標籤結束時,它會觸發一個事件,用戶經過事件回調代碼來處理XML文件,適合對XML 的順序訪問;顧名思義,
StAX 把重點放在流上,實際上StAX與其餘解析方式的本質區別就在於應用程序可以把XML做爲一個事件流來處理。SAX 也是這樣作的,但不一樣之處在於StAX 容許應用程序代碼把這些事件逐個拉出來,而不用提供在解析器方便時從解析器中接收事件的處理程序。
XML的主要做用有兩個方面:數據交換和信息配置。
在作數據交換時,XML將數據用標籤組裝成起來, 而後壓縮打包加密後經過網絡傳送給接收者,接收解密與解壓縮後再從XML文件中還原相關信息進行處理,XML曾經是異構系統間交換數據的事實標準,但此項功能幾乎已經被JSON( JavaScript Object Notation)取而代之。固然,目前不少軟件仍然使用XML 來存儲配置信息,咱們在不少項目中
一般也會將做爲配置信息的硬代碼寫在XML 文件中,Java 的不少框架也是這麼作的, 並且這些框架都選擇了dom4j 做爲處理XML 的工具,(由於Sun 公司的官方API 實在不怎麼好用。)
//1. 加載JDBC驅動程序(加載MySQL驅動類) Class.forName("com.mysql.jdbc.Driver"); //2. 提供JDBC鏈接的URL來建立鏈接 //databaseName數據庫名稱,useUnicode=true:表示使用Unicode字符集,characterEncoding=UF-8字符編碼方式utf-8, udrtnsmr和password是mysql鏈接用戶名和密碼 Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/databaseName?useUnicode=true&characterEncoding=UF-8;",username,password); // 也能夠將mysql驅動寫到DriverManager.getConnection()中 //3. PreparedStatement預編譯sql String sql = "select * from dept where id = ? and name = ?"; PreparedStatement ps = con.prepareStatement(sql); ps.setInt(1, 10); ps.setInt(2, "研究員"); //4. 執行SQL語句 ResultSet rs = ps.executeQuery(); //5. 處理結果 while(rs.next()) { System.out.println(rs.getInt("id")); System.out.println(rs.getInt("name")); } //6. 在finally裏面進行釋放資源(關閉外部資源的順序應該和打開的順序相反) if(rs !=null){ rs.close; } if(ps != null){ ps.close(); } if(con != null) { con.close(); } 1234567891011121314151617181920212223242526272829
PreparedStatement性能更好
1)PreparedStatement接口表明預編譯的語句,它主要的優點在於能夠減小SQL 的編譯錯誤並增長SQL的安全性(減小SQL 注射攻擊的可能性)
2) PreparedStatement 中的SQL 語句是能夠帶參數的,避免了用字符串鏈接拼接SQL 語句的麻煩和不安全;
3)當批量處理SQL 或頻繁執行相同的查詢時,PreparedStatement 有明顯的性能上的優點,因爲數據庫能夠將編譯優化後的SQL 語句緩存起來,下次執行相同結構的語句時就會很快(不用再次編譯和生成執行計劃)。
Blob
是指二進制大對象(Binary Large Object)
Clob
是指大字符對象(Character Large Objec)
Blob是爲存儲大的二進制數據而設計的,而Clob 是爲存儲大的文本數據而設計的。JDBC 的PreparedStatement和
ResultSet都提供了相應的方法來支持Blob和Clob操做(流的操做)。
因爲建立鏈接和釋放鏈接都有很大的開銷(尤爲是數據庫服務器不在本地時,每次創建鏈接都須要進行TCP的三次握手,釋放鏈接須要進行四次揮手,形成很大的開銷),爲了提高系統訪問數據庫的性能,能夠事先建立若干鏈接置於鏈接池中,須要時直接從鏈接池獲取, 使用結束時歸還鏈接池而沒必要關閉鏈接,從而避免頻繁建立和釋放鏈接所形成的開銷,這是典型的用空間換時間的策略(浪費了空間存儲鏈接,但節省了建立和釋放鏈接的時間)。
池化技術在Java 開發中是很常見的,在使用線程時建立線程池的道理與此相同。基於Java 的開源數據庫鏈接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid
等。
其實大型網站性能優化的一個關鍵就是使用緩存,,而緩
存和鏈接池很是相似, 也是使用空間換時間的策略。能夠將熱點數據置於緩存中,當用戶查詢這些數據時能夠直接從緩存中獲得, 避免頻繁的訪問數據庫形成大量的開銷(如今主要用Redis
實現緩存)
DAO( Data Access Object)是一個爲數據庫或其餘持久化機制提供了抽象接口的對象,在不暴露底層持久化方案實現細節的前提下提供了各類數據訪問操做。在實際的開發中,應該將全部對數據源的訪問操做進行抽象化後封裝在一個公共API 中。
用程序設計語言來講, 就是創建一個接口,接口中定義了此應用程序中將會用到的全部事務方法。在這個應用程序中,當須要和數據源進行交互的時候則使用這個接口,而且編寫一個單獨的類來實現這個接口,在邏輯上該類對應一個特定的數據存儲。DAO 模式實際上包含了兩個模式,一是Data Accessor(數據訪問器),二是Data Object(數據對象),前者要解決如何訪問數據的問題,然後者要解決的是如何用對象封裝數據。
1)原子性(Atomicity):事務是一個不可分割的工做單位,事務中的操做要麼都發生,要麼都不發生
2)一致性(Consistency):事務在完成後數據的完整性必須保持一致
3)隔離性(Isolation):多個用戶併發訪問數據庫時,一個用戶的事務不能被其餘用戶的事務所幹擾,多個併發事務之間的數據要相互隔離
4)持久性(Durability):一個事務一旦被提交,它對數據庫中數據的改變應該是永久性的,即便數據庫發生故障也不該該對其有任何影響若是整個事務執行過程當中,有任何一個地方出現異常/錯誤,那麼都會進行事務回滾,回滾以後數據的狀態將和事務執行以前徹底一致。
數據庫爲用戶提供了自動鎖機制,只要用戶指定會話的事務隔離級別, 數據庫就會經過分析SQL語句而後爲事務訪問的資源加上合適的鎖。
隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 接口中定義有五個表示隔離級別的常量(用於解決併發問題)通常狀況下使用中間兩種就行。
TransactionDefinition 接口事務隔離級別 | 描述 |
---|---|
READ_UNCOMMITTED | 該隔離級別表示一個事務能夠讀取另外一個事務修改但尚未提交的數據。該級別不能防止髒讀,不可重複讀和幻讀,所以不多使用該隔離級別。 |
READ_COMMITTED | 系統默認值,該隔離級別表示一個事務只能讀取另外一個事務已經提交的數據。能夠防止髒讀,是大多數狀況下的推薦值。 |
REPEATABLE_READ | 該隔離級別表示一個事務在整個過程當中能夠屢次重複執行某個查詢,而且每次返回的記錄都相同。該級別能夠防止髒讀和不可重複讀。 |
SERIALIZABLE | 全部的事務依次逐個執行,事務之間就徹底不可能產生干擾。該級別能夠防止髒讀、不可重複讀以及幻讀。可是這將嚴重影響程序的性能。一般狀況下也不會用到該級別。 |
隔離級別(√:容許出現 ×:不容許出現) | 髒讀 | 不可重複讀 | 幻讀 | 第一類丟失更新 | 第二類丟失更新 |
---|---|---|---|---|---|
READ_UNCOMMITTED | √ | √ | √ | × | √ |
READ_COMMITTED | × | √ | √ | × | √ |
REPEATABLE_READ | × | × | √ | × | × |
SERIALIZABLE | × | × | × | × | × |
事務隔離級別和數據訪問的併發性是對立的,事務隔離級別越高併發性就越差。因此要根據具體的應用來肯定合適的事務隔離級別,這個地方沒有萬能的原則。
首先要知道,只有存在併發數據訪問時才須要事務(提交/回滾就結束事務),當多個事務訪問同一數據時,可能會存在5類併發問題,包括3 類數據讀取問題( 髒讀、不可重複讀和幻
讀和2類數據更新問題(第1 類丟失更新和第2 類丟失更新)
3類數據讀取問題1)髒讀(Dirty Read): 一個事務讀到了另外一個事務的尚未提交數據. (好比A事務讀取B事務還沒有提交的數據並在此基礎上操做,而B事務執行回滾,那麼A 讀取到的數據就是髒數據。)(很嚴重的行爲,必須處理,否則可能有很大的影響,好比轉帳事務)
2)不可重複讀(Unrepeatable Read): 一個事務中屢次讀到的數據不一致,一個事務讀到了另外一個事務修改後的數據。(不可重複讀,保證數據修改後,不會出現兩次同樣的數據)
3) 幻讀(虛讀Phantom Read):一個事務讀到了另外一個事務insert提交的數據。(好比事務A 從新執行一個查詢,返回一系列符合查詢條件的行,發現其中插入了被事務B 提交的行)(不可能出如今MySQL中,只會出如今Oracle中)兩類丟失更新問題
1)第一類丟失更新:
事務A撤銷時, 把已經提交的事務B的更新數據覆蓋了(好比 取款事務A開啓事務查詢餘額1000元,轉帳事務B開啓事務轉帳100給A,A取出100,提交事務以後,再查詢餘額仍是1000元)
2)事務A覆蓋事務 已經提交的數據,形成事務B 所作的操做丟失(好比:取款事務A和轉帳B前後開啓事務,前後查詢餘額都是1000元,取款事務A取出100,餘額變成900,提交事務,可是此時轉帳事務B存入100,將餘額修改成1100元,提交事務,而後再查詢賬戶餘額就是1100,取款事務A的操做丟失)
JDBC如何進行事務處理:
Connection
提供了事務處理的方法,經過調用setAutoCommit(false)
能夠設置手動提交事務。當事務完成後用commit()
顯式提交事務;若是在事務處理過程當中發生異常則經過rollback()
進行事務回滾。除此以外, 從JDBC 3.0 中還引入了Savepoint( 保存點)的概念,容許經過代碼設置保存點並讓事務回滾到指定的保存點。
在編寫處理字符串的程序時,常常會有查找符合某些複雜規則的字符串的須要。正則表達式就是用於描述這些規則的工具。換句話說, 正則表達式就是記錄文本規則的代碼。正則表達式就是在進行字符串匹配和處理的時候最爲強大的工具,絕大多數語言都提供了對正則表達式的支。
Java中的String類提供了支持正則表達式操做的方法,包括:matches()、replaceAll()、replaceFirst()、split()
此外,Java 中能夠用Pattern類表示正則表達式對象, 它提供了豐富的API 進行各類正則表達式操做。
獲取class對象的三種方法:
1)每一個類經過class屬性獲取。【
類名.class
】
2) 每一個對象經過getClass()方法獲取。【對象.getClass()
】
3)經過Class.forName(「類的完整名」)獲取,須要捕獲異常。【Class.forName("類的完整名")
】第一種方式,
類名.class
不會將類加載到內存,第三種Class.forName()
會將類加載到內存,對象.getClass()(建立對象了)會加載到內存中
經過反射建立對象的兩種方法:
1)經過類對象(class)調用1newInstance()1方法,例如建立String對象:
String.class.newInstance()
2)經過類對象(class)的getConstructor()getDeclaredConstructor()
方法得到構造器(Constructor)對象並調用其newInstance()方法建立對象,例如:
·String.class.getConstructor(String.class).newInstance(「Hello」);·
類型 | 設計模式 |
---|---|
建立型 | 工廠方法模式(FactoryMethod)、抽象工廠模式(AbstractFactory)、建造者模式(Builder)、原型模式(Prototype)、單例模式(Singleton) |
結構型 | 適配器模式(Adapter)、橋接模式(Bridge)、組合模式(Composite)、裝飾器模式(Decorator)、門面模式(Facade)、享元模式(Flyweight)、代理模式(Proxy) |
行爲型 | 解釋器模式(Interpreter)、模板方法模式(TemplateMethod)、責任鏈模式(ChainofResponsibility)、命令模式(Command)、迭代器模式(Iterator)、調解者模式(Mediator)、備忘錄模式(Memento)、觀察者模式(Observer)、狀態模式(State)、策略模式(Strategy)、訪問者模式(Visitor) |
單例模式:指一個類只有一個實例,且該類能自行建立這個實例的一種模式。(懶漢式、餓漢式寫法)
原型模式:用一個已經建立的實例做爲原型,經過複製該原型對象來建立一個和原型相同或類似的新對象。
策略模式:定義了一系列算法,並將每一個算法封裝起來,使它們能夠相互替換,且算法的變化不會影響使用算法的客戶。策略模式屬於對象行爲模式,它經過對算法進行封裝,把使用算法的責任和算法的實現分割開來,並委派給不一樣的對象對這些算法進行管理。
工廠模式:定義一個建立產品對象的工廠接口,將產品對象的實際建立工做推遲到具體子工廠類當中。工廠類能夠根據條件生成不一樣的子類實例,這些子類有一個公共的抽象父類而且實現了相同的方法,可是這些方法針對不一樣的數據進行了不一樣的操做(多態方法)。當獲得子類的實例後,開發人員能夠調用基類中的方法而沒必要考慮到底返回的是哪個子類的實例。
代理模式:給一個對象提供一個代理對象,並由代理對象控制原對象的引用。實際開發中,按照使用目的的不一樣,代理能夠分爲:遠程代理、虛擬代理、保護代理、Cache 代理、防火牆代理、同步化代理、智能引用代理。
適配器模式:把一個類的接口變換成客戶端所期待的另外一種接口,從而使本來因接口不匹配而沒法在一塊兒使用的類可以一塊兒工做。
模板方法模式:提供一個抽象類,將部分邏輯以具體方法或構造器的形式實現,而後聲明一些抽象方法來迫使子類實現剩餘的邏輯。不一樣的子類能夠以不一樣的方式實現這些抽象方法(多態實現),從而實現不一樣的業務邏輯。
狀態模式:對有狀態的對象,把複雜的「判斷邏輯」提取到不一樣的狀態對象中,容許狀態對象在其內部狀態發生改變時改變其行爲。
裝飾者模式:指在不改變現有對象結構的狀況下,動態地給該對象增長一些職責(即增長其額外功能)的模式,它屬於對象結構型模式。
瞭解其餘的能夠從這裏面看:23種設計模式詳解
原則 | 描述 |
---|---|
開閉原則(OCP) | 軟件實體應當對擴展開放,對修改關閉 |
里氏替換原則(LSP) | 闡述了有關繼承的一些原則(何時使用繼承),里氏替換原是繼承複用的基礎,反映了基類與子類之間的關係,是對開閉原則的補充,是對實現抽象化的具體步驟的規範。能夠這麼理解:子類能夠擴展父類的功能,但不能改變父類原有的功能,也就是子類繼承父類時,除添加新的方法完成新增功能外,儘可能不要重寫父類的方法。 |
依賴倒置原則(DIP) | 核心思想是:要面向接口編程,不要面向實現編程。(抽象層相對穩定,所以以抽象爲基礎搭建起來的架構要比以細節(實現類)爲基礎搭建起來的架構要穩定得多) |
單一職責原則(SRP) | 提出對象不該該承擔太多職責。單一職責一樣也適用於方法。一個方法應該儘量作好一件事情。若是一個方法處理的事情太多,其顆粒度會變得很粗,不利於重用。 |
接口隔離原則(ISP) | 要求程序員儘可能將臃腫龐大的接口拆分紅更小的和更具體的接口,讓接口中只包含客戶感興趣的方法。接口要小而專,毫不能大而全。 |
迪米特法則(LoD) | 又叫做最少知識原則(LKP),一個對象應當對其餘對象有儘量少的瞭解。若是兩個軟件實體無須直接通訊,那麼就不該當發生直接的相互調用,能夠經過第三方轉發該調用。其目的是下降類之間的耦合度,提升模塊的相對獨立性。 |
合成複用原則(CRP) | 叫組合/聚合複用原則(CARP)要求在軟件複用時,要儘可能先使用組合或者聚合等關聯關係來實現,其次才考慮使用繼承關係來實現。 |
概述:
開閉原則是總綱,它告訴咱們要對擴展開放,對修改關閉;
里氏替換原則告訴咱們不要破壞繼承體系;
依賴倒置原則告訴咱們要面向接口編程;
單一職責原則告訴咱們實現類要職責單一;
接口隔離原則告訴咱們在設計接口的時候要精簡單一;
迪米特法則告訴咱們要下降耦合度;
合成複用原則告訴咱們要優先使用組合或者聚合關係複用,少用繼承關係複用。
UML是統一建模語言( Unified Modeling Language)的縮寫,它發表於1997年, 綜合了當時已經存在的面向對象的建模語言、方法和過程, 是一個支持模型化和軟件系統開發的圖形化語言,爲軟件開發的全部階段提供模型化和可視化支持。使用UML 能夠幫助溝通與交流,輔助應用設計和文檔的生成,還可以闡釋系統的結構和行爲。
經常使用UML圖
用例圖(use case diagram)、類圖(class diagram)、時序圖(sequencediagram)、協做圖(collaboration diagram)、狀態圖(statechart diagram)、活動圖(activity diagram)、構件圖(component diagram)、部署圖(deploymentdiagram)
用例圖:用來捕獲需求,描述系統的功能,經過該圖能夠迅速的瞭解系統的功能模塊及其關係
類圖:描述類以及類與類之間的關係,經過該圖能夠快速瞭解系統
時序圖:描述執行特定任務時對象之間的交互關係以及執行順序, 經過該圖能夠了解對象能接收的消息也就是說對象可以向外界提供的服務。
JDK1.8中,HashMap採用
位桶+鏈表+紅黑樹
實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減小了查找時間。
HashCode是jdk根據對象的地址或字符串或者數字利用hash算法計算出的int類型的數值。
HashMap實現原理:首先有一個每一個元素都是鏈表的數組,當添加一個元素(key-value)時,就首先計算元素key的hash值,以此肯定插入數組中的位置,可是可能存在同一hash值的元素已經被放在數組同一位置了,這時就添加到同一hash值的元素的後面,他們在數組的同一位置,可是造成了鏈表,同一各鏈表上的Hash值是相同的,因此說數組存放的是鏈表。而當鏈表長度太長時,鏈表就轉換爲紅黑樹,這樣大大提升了查找的效率。
在jdk8中,HashMap處理「碰撞」增長了紅黑樹這種數據結構,當碰撞結點較少時,採用鏈表存儲,當較大時(>8個),採用紅黑樹(特色是查詢時間是O(logn))存儲(有一個閥值控制,大於閥值(8個),將鏈表存儲轉換成紅黑樹存儲)紅黑樹是一種自平衡二叉查找樹
紅黑樹是每一個節點都帶有顏色屬性的二叉查找樹,顏色或紅色或黑色。
對於任何有效的紅黑樹都有如下要求:
1)節點是紅色或黑色。
2)根節點是黑色。
3)每一個葉節點是黑色的。
4)每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點)
5從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。
若是系統中存在臨界資源(資源數量少於競爭資源的線程數量的資源), 例如正在寫的數據之後可能被另外一個線程讀到,或者正在讀的數據可能已經被另外一個線程寫過了,那麼這些數據就必須進行同步存取(數據庫操做中的排他鎖就是最好的例子)。
當應用程序在對象上調用了一個須要花費很長時間來執行的方法,而且不但願讓程序等待方法的返回時, 就應該使用異步編程,在不少狀況下采用異步途徑每每更有效率。
事實上,所謂的同步就是指阻塞式操做, 而異步就是非阻塞式操做。(同步必須等待返回結果才能繼續執行,異步就是瀏覽器發送請求,無論服務器是否返回結果,均可以繼續執行)
Web容器加載Servlet並將其實例化後,Servlet生命週期開始,容器運行其init()方法進行Servlet的初始化;請求到達時調用Servlet的service方法,service方法會調用與請求對應的doGet或doPost等方法;當服務器關閉會項目被卸載時服務器會將Servlet實例銷燬,此時會調用Servlet的destroy方法。
1)get請求用來從服務器上得到資源,而post是用來向服務器提交數據
2)get將表單中數據按照name=value的形式,添加到action 所指向的URL 後面,而且二者使用「?」鏈接,而各個變量之間使用「&」鏈接;post是將表單中的數據放在HTML頭部(header),傳遞到action所指向URL
3)get傳輸的數據要受到URL長度限制(1024字節);而post能夠傳輸大量的數據,上傳文件只能使用post方式
4)使用get時參數會顯示在地址欄上,若是這些數據不是敏感數據,那麼能夠使用get;對於敏感數據仍是應用使用post
1)Web客戶向Servlet容器發出Http請求;
2)Servlet容器解析Web客戶的Http請求;
3)Servlet容器建立一個HttpRequest對象,在這個對象中封裝Http請求信息;
4)Servlet容器建立一個HttpResponse對象;
5)Servlet容器調用HttpServlet的service方法,這個方法中會根據request的Method來判斷具體是執行doGet仍是doPost,把HttpRequest和HttpResponse對象做爲service方法的參數傳給HttpServlet對象;
6)HttpServlet調用HttpRequest的有關方法,獲取HTTP請求信息;
7)HttpServlet調用HttpResponse的有關方法,生成響應數據;
8)Servlet容器把HttpServlet的響應結果傳給Web客戶
Callable 接口相似於Runnable,可是Runnable 不會返回結果,而且沒法拋出返回結果的異常,而Callable 功能更強大一些,被線程執行後,能夠返回值,這個返回值能夠被Future 拿到,也就是說,Future 能夠拿到異步執行任務的返回值。能夠認爲是帶有回調的Runnable。
Future 接口表示異步任務,是尚未完成的任務給出的將來結果。因此說Callable用於產生結果,Future 用於獲取結果。
Math.round(11.5)的返回值是12, Math.round(-11.5)的返回值是-11。四舍五
入的原理是在參數上加0.5 而後進行下取整。
short s1 = 1; s1 = s1 + 1;
有錯嗎? short s1 = 1; s1 += 1;
有錯嗎?1)
short s1 = 1; s1 = s1 + 1;
編譯錯誤。因爲1 是int 類型,所以s1+1運算結果也是int型, 須要強制轉換類型才能賦值給short 型。
2)short s1 = 1; s1 += 1;
編譯正確,由於s1+= 1;至關於s1 = (short)(s1 + 1);其中有隱含的強制類型轉換(自增加自動轉換,除非越界)
<<
和>>
的概念)
2 << 3
(左移3 位至關於乘以2 的3 次方,右移3 位至關於除以2 的3 次方。關於運算符
<<
和>>
x >> n
就是 先將x轉成二進制,不讀後面的n位
x << n
就是 先將x轉成二進制,往二進制數據後面加n個0案例:
int x = 16;
x >> 1
輸出x=8(先將x轉成二進制 10000, 不讀最後一位0, 輸出 1000, 轉爲10進制即爲8)
x << 1
輸出x=32(先將x轉成二進制 10000,,往最後再讀取一位0( 或根據是否已經有移位), 輸出 100000, 轉爲10進制即爲32)
數組沒有length()方法,只有有length 的屬性。String 有length()方法。可是在JavaScript中 ,得到字符串的長度是經過length 屬性獲得的(很是容易和Java搞混)
是值傳遞。Java 語言的方法調用只支持參數的值傳遞。當一個對象實例做爲一個參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的屬性能夠在被調
用過程當中被改變,但對對象引用的改變是不會影響到調用者的。C++和C#中能夠
經過傳引用或傳輸出參數來改變傳入的參數的值。在C#中能夠編寫以下所示的代
碼, 可是在Java 中卻作不到。
String str = new String(「xyz」);
建立了幾個字符串對象?兩個對象,一個是靜態區(方法區常量池)的」xyz「字符串,一個是用new 建立在堆上的對象str。
Java中主要是字節流和字符流。字節流繼承於InputStream、OutputStream,字符流繼承於Reader、Writer。
在java.io 包中還有許多其餘的流,主要是爲了提升性能和使用方便
關於Java 的I/O 須要注意的有兩點:1)兩種對稱性( 輸入和輸出的對稱性,字節和字符的對稱性)
2)兩種設計模式(適配器模式和裝潢模式)。
public static void fileCopy(String source, String target) throws IOException { try (InputStream in = new FileInputStream(source)) { try (OutputStream out = new FileOutputStream(target)) { byte[] buffer = new byte[4096]; int bytesToRead; while((bytesToRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesToRead); } } } } 123456789101112
NIO方式
public static void fileCopyNIO(String source, String target) throws IOException { try (FileInputStream in = new FileInputStream(source)) { try (FileOutputStream out = new FileOutputStream(target)) { FileChannel inChannel = in.getChannel(); FileChannel outChannel = out.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(4096); while(inChannel.read(buffer) != -1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); } } } } 12345678910111213141516
//統計這個字符串在這個文件中出現的次數 public static int countWordInFile(String filename, String word) { int counter = 0; //統計數 try (FileReader fr = new FileReader(filename)) { try (BufferedReader br = new BufferedReader(fr)) { String line = null; while ((line = br.readLine()) != null) { int index = -1; while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) { counter++; line = line.substring(index + word.length()); } } } } catch (Exception ex) { ex.printStackTrace(); } return counter; } 12345678910111213141516171819
public static void main(String[] args) { File f = new File("/Users/Downloads"); for(File temp : f.listFiles()) { if(temp.isFile()) { System.out.println(temp.getName()); } } } 12345678
2)對於當前文件夾下的文件夾繼續展開顯示全部文件
private static void queryDirectory(File f, int level) { if(f.isDirectory()) { for(File temp : f.listFiles()) { queryDirectory(temp, level + 1); //遞歸 } }else { for(int i = 0; i < level - 1; i++) { System.out.print("\t"); //\t四個空格 } System.out.println(f.getName()); //輸出文件名 } } public static void main(String[] args) { queryDirectory(new File("/Users/Downloads"),0); } 12345678910111213141516
JDK1.7以後能夠使用NIO.2的API實現
public static void main(String[] args) throws IOException { Path initPath = Paths.get("/Users/Downloads"); Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributesattrs) throws IOException { System.out.println(file.getFileName().toString()); return FileVisitResult.CONTINUE; } }); } 12345678910
import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; //socket,TCP編程,服務端程序 public class Server { public static void serverInfo(){ ServerSocket server = null; Socket client = null; PrintStream out = null; try { //在服務器8000端口等待客戶鏈接 server = new ServerSocket(8000); System.out.println("服務器正在等待客戶端的鏈接......"); //程序阻塞,等待客戶端的鏈接 client = server.accept(); System.out.println("鏈接客戶端成功!!"); //實例化打印流對象,用於向客戶端發送輸出信息 out = new PrintStream(client.getOutputStream()); System.out.println("請輸入您要向客戶端發送的信息:"); Scanner scan = new Scanner(System.in); //獲取輸入流 //準備向客戶端發送的信息 String info = "服務端向客戶端發送信息:" + scan.nextLine(); //輸出信息 out.println(info); scan.close(); //關閉輸入流 out.close(); //關閉輸出打印流 client.close(); //關閉客戶端 server.close(); //關閉服務器端的練級 } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { serverInfo();//開啓服務端 } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546
客戶端:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; //socket編程,客戶端,要與服務端的端口號一致 public class Client { public static void clientInfo(){ //聲明socket對象 Socket client = null; try { //實例化socket對象,指定鏈接的主機名稱和端口號 client = new Socket("localhost",8000); System.out.println("客戶端鏈接成功"); //聲明緩存字符流,用來接收信息 BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream())); //讀取信息 String info = buf.readLine(); //輸出讀取的信息 System.out.println("客戶端收到服務器("+ client.getInetAddress() +")端發來的信息:【" + info + "】"); client.close(); buf.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { clientInfo(); } } 123456789101112131415161718192021222324252627282930313233343536
運行:
一、 開啓服務端Server
Server:服務器正在等待客戶端的鏈接…
2.、開啓客戶端Client
Server:(輸出內容)
服務器正在等待客戶端的鏈接…
鏈接客戶端成功!!
請輸入您要向客戶端發送的信息:
Client:(輸出內容)
客戶端鏈接成功三、Server輸入內容HelloWorld
客戶端Client接收到信息:
客戶端收到服務器(localhost/127.0.0.1)端發來的信息:【服務端向客戶端發送信息:Hello World!!!】
1) 將構造器私有,不容許外界經過構造器
建立對象;
2) 經過公開的靜態方法向外界返回類的惟一實例
餓漢式單例模式:
//餓漢式 不延遲實例化的作法,直接靜態實例化建立 保證線程安全 public class HungarySingleton { private static HungarySingleton instance=new HungarySingleton(); private HungarySingleton(){ } public static HungarySingleton getInstance(){ return instance;//返回惟一實例 } } 123456789
懶漢式單例模式:
public class LazySingleton { private static LazySingleton instance = null; private Singleton() {} public static synchronized LazySingleton getInstance(){ if (instance == null) instance = new LazySingleton (); return instance; } } 12345678
volatile關鍵字,DCL機制實現懶漢式,提升性能
//懶漢式 DCL機制 //T1先從主存拷貝數據到本身的線程內存,進行處理數據,處理完數據更新到主存 //T2從主存拷貝數據到本身的線程內存 public class LazySingleton { private static volatile LazySingleton instance = null; private LazySingleton(){ } //unlock happen-before lock 語義 //T1,T2 ---可見性 T1 happen-before T2 /** * A->B,B->C===A->C * T2調用getInstance()和調用getName() * T1調用getInstance() * a++ * T1 ->T2 * 構造器內存操做->T1 ->T2 */ public static LazySingleton getInstance(){ //T2 if(instance==null){ //T1 同步化 全部爲null的只能進入一個,等待執行 synchronized(LazySingleton.class){ if(instance==null){ instance=new LazySingleton(); //JSR-133 //1.開闢空間 //2.初始化對象 //3.把地址給isntance變量---CPU } } } return instance; } } 1234567891011121314151617181920212223242526272829303132333435
能夠作強制轉換,可是Java中int 是32位(4byte)的,而byte 是8位(1byte)的,因此,若是強制轉化是,int 類型的高24 位將會被丟棄,byte 類型的範圍是從-128 到128。
java.lang.Cloneable 是一個標示性接口,不包含任何方法, clone 方法在object 類中定義。而且clone() 方法是一個本地方法,這意味着它是由c 或c++ 或其餘本地語言實現。
不是線程安全的操做。它涉及到多個指令,如讀取變量值,增長/減小,而後存儲回內存,這個過程可能會出現多個線程交差。
a = a + b
與a += b
的區別
+=
隱式的將加操做的結果類型強制轉換爲持有結果的類型(好比a += b
會將運算結果a+b
轉換爲須要a對應的類型)。若是兩這個整型相加,如byte、short 或者int,首先會將它們提高到int 類型,而後在執行加法操做,最後再轉換爲接收的數據的類型。
而a = a + b
不會進行類型轉換,若是越界就拋出異常
// byte取值 -128到128,超過就越界要轉換類型 byte a = 120; byte b = 120; a = a + b; //編譯報錯:cannot convert from int to byte,由於不會將結果(a+b)自動強制類型轉換 a += b; //編譯正確,b = -16 //隱式的將加操做的結果類型強制轉換爲持有結果的類型(a+b的時候隱式轉換爲int相加 //結果強制轉換爲接收結果a的類型byte,a+b=240(int類型)轉換爲a的類型byte=-16) 1234567
Integer包裝類會佔用更多的內存。Integer 是一個對象,須要存儲對象的元數據。可是int 是一個原始類型的數據,因此佔用的空間更少
若是a和b都是對象,則a==b 是比較兩個對象的引用,只有當a和b指向的是堆中的同一個對象纔會返回true,a.equals(b) 是進行邏輯比較,因此一般須要重寫該方法來提供邏輯一致性的比較。例如,String 類重寫equals() 方法, 因此能夠用於兩個不一樣對象,可是包含的字母相同的比較
hashCode()方法是相應對象整型的hash值。它經常使用於基於hash 的集合類,如Hashtable、HashMap、LinkedHashMap 等等。它與equals() 方法關係特別緊密。根據Java 規範,兩個使用equal() 方法來判斷相等的對象,必須具備相同的hash code。
poll()方法和remove()方法的區別
poll() 和remove() 都是從隊列中取出一個元素,可是poll() 在獲取元素失敗的時候會返回空,可是remove() 失敗的時候會拋出異常。
LinkedHashMap 和PriorityQueue 的區別
PriorityQueue保證最高或者最低優先級的的元素老是在隊列頭部,可是LinkedHashMap 維持的順序是元素插入的順序。當遍歷一個PriorityQueue時,沒有任何順序保證,可是LinkedHashMap 課保證遍歷順序是元素插入的順序。
ArrayList 與LinkedList 的主要區別
ArrrayList 底層的數據結構是數組,支持隨機訪問,而
LinkedList的底層數據結構是鏈表(雙向鏈表),不支持隨機訪問(可是優化了更新操做)。使用下標訪問一個元素,ArrayList 的時間複雜度是O(1),而LinkedList 是O(n)。Hashtable 與HashMap 有什麼不一樣
1)Hashtable 是JDK 1 遺留下來的類,而HashMap 是後來增長的。
2)Hashtable 是同步的,比較慢,但HashMap 沒有同步策略,因此會更快。
3)Hashtable不容許有個空的key,可是HashMap容許出現一個null key。Java中的HashSet,內部是如何工做
HashSet 的內部採用HashMap 來實現。因爲Map 須要key 和value,因此全部key 的都有一個默認value。相似於HashMap, HashSet 不容許重複的key,只容許有一個null key,意思就是HashSet 中只容許存儲一個null 對象。
ArrayList 和HashMap 的默認大小
ArrayList 的默認大小是10 個元素, HashMap的默認大小是16 個元素(必須是2的冪次)
兩個相同的對象的hash code必定相同,兩個對象有相同的hash Code,但兩個對象不必定相同。
兩個不相等的對象可能會有相同的hashcode 值, 這就是爲何在hashmap 中會有衝突。相等hashcode 值的規定只是說若是兩個對象相等, 必須有相同的hashcode 值, 可是沒有關於不相等對象的任何規定
Java中的TreeMap是使用紅黑樹實現的。
本文做者:strive_day 本文連接:https://blog.csdn.net/qq_40542534/article/details/109241330
更多信息請關注公衆號:「軟件老王」,關注不迷路,軟件老王和他的IT朋友們,分享一些他們的技術看法和生活故事。