典型回答:
final能夠用來修飾類、方法、變量,分別有不一樣的意義, final修飾的class表明不能夠繼承擴展, final的變量是不能夠修改的,而final的方法也是不能夠重寫的( override)。java
finally則是Java保證重點代碼必定要被執行的一種機制。咱們可使用try-finally或者try-catch-finally來進行相似關閉JDBC鏈接、保證unlock鎖等動做。算法
finalize是基礎類java.lang.Object的一個方法,它的設計目的是保證對象在被垃圾收集前完成特定資源的回收。 finalize機制如今已經不推薦使用,而且在JDK 9開始被標記
爲deprecated。編程
不一樣的引用類型,主要體現的是對象不一樣的可達性( reachable)狀態和對垃圾收集的影響。segmentfault
所謂強引用( "Strong" Reference),就是咱們最多見的普通對象引用,只要還有強引用指向一個對象,就能代表對象還「活着」,垃圾收集器不會碰這種對象。對於一個普通的對
象,若是沒有其餘的引用關係,只要超過了引用的做用域或者顯式地將相應(強)引用賦值爲null,就是能夠被垃圾收集的了,固然具體回收時機仍是要看垃圾收集策略。數組
軟引用( SoftReference),是一種相對強引用弱化一些的引用,可讓對象豁免一些垃圾收集,只有當JVM認爲內存不足時,纔會去試圖回收軟引用指向的對象。 JVM會確保在拋
出OutOfMemoryError以前,清理軟引用指向的對象。軟引用一般用來實現內存敏感的緩存,若是還有空閒內存,就能夠暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩
存的同時,不會耗盡內存。
andriod中的圖片緩存是軟引用的例子.緩存
弱引用( WeakReference)並不能使對象豁免垃圾收集,僅僅是提供一種訪問在弱引用狀態下對象的途徑。這就能夠用來構建一種沒有特定約束的關係,好比,維護一種非強制性
的映射關係,若是試圖獲取時對象還在,就使用它,不然重現實例化。它一樣是不少緩存實現的選擇。
ThreadLocal中entry的Key是弱引用的例子.安全
對於幻象引用,有時候也翻譯成虛引用,你不能經過它訪問對象。幻象引用僅僅是提供了一種確保對象被fnalize之後,作某些事情的機制,好比,一般用來作所謂的PostMortem清理機制,我在專欄上一講中介紹的Java平臺自身Cleaner機制等,也有人利用幻象引用監控對象的建立和銷燬。服務器
String是Java語言很是基礎和重要的類,提供了構造和管理字符串的各類基本邏輯。它是典型的Immutable類,被聲明成爲fnal class,全部屬性也都是fnal的。也因爲它的不可
變性,相似拼接、裁剪字符串等動做,都會產生新的String對象。因爲字符串操做的廣泛性,因此相關操做的效率每每對應用性能有明顯影響。數據結構
StringBufer是爲解決上面提到拼接產生太多中間對象的問題而提供的一個類,咱們能夠用append或者add方法,把字符串添加到已有序列的末尾或者指定位置。 StringBufer本
質是一個線程安全的可修改字符序列,它保證了線程安全,也隨之帶來了額外的性能開銷,因此除非有線程安全的須要,否則仍是推薦使用它的後繼者,也就是StringBuilder。多線程
StringBuilder是Java 1.5中新增的,在能力上和StringBufer沒有本質區別,可是它去掉了線程安全的部分,有效減少了開銷,是絕大部分狀況下進行字符串拼接的首選。
String是Immutable類的典型實現,原生的保證了基礎線程安全,由於你沒法對它內部數據進行任何修改,這種便利甚至體如今拷貝構造函數中,因爲不可
變, Immutable對象在拷貝時不須要額外複製數據。
爲了實現修改字符序列的目的, StringBufer和StringBuilder底層都是利用可修改的( char, JDK 9之後是byte)數組,兩者都繼承了AbstractStringBuilder,裏面包含了基本
操做,區別僅在於最終的方法是否加了synchronized。
典型回答:
反射機制是Java語言提供的一種基礎功能,賦予程序在運行時自省( introspect,官方用語)的能力。經過反射咱們能夠直接操做類或者對象,好比獲取某個對象的類定義,獲取類
聲明的屬性和方法,調用方法或者構造對象,甚至能夠運行時修改類定義。
動態代理是一種方便運行時動態構建代理、動態處理代理方法調用的機制,不少場景都是利用相似機制作到的,好比用來包裝RPC調用、面向切面的編程( AOP)。
實現動態代理的方式不少,好比JDK自身提供的動態代理,就是主要利用了上面提到的反射機制。還有其餘的實現方式,好比利用傳說中更高性能的字節碼操做機制,類
似ASM、 cglib(基於ASM)、 Javassist等。
咱們知道Spring AOP支持兩種模式的動態代理, JDK Proxy或者cglib,若是咱們選擇cglib方式,你會發現對接口的依賴被克服了。
cglib動態代理採起的是建立目標類的子類的方式,由於是子類化,咱們能夠達到近似使用被調用者自己的效果。
典型回答:
int是咱們常說的整形數字,是Java的8個原始數據類型( Primitive Types, boolean、 byte 、 short、 char、 int、 foat、 double、 long)之一。 Java語言雖然號稱一切都是對象,
但原始數據類型是例外。
Integer是int對應的包裝類,它有一個int類型的字段存儲數據,而且提供了基本操做,好比Math運算、 int和字符串之間轉換等。在Java 5中,引入了自動裝箱和自動拆箱功能
( boxing/unboxing), Java能夠根據上下文,自動進行轉換,極大地簡化了相關編程。
關於Integer的值緩存,這涉及Java 5中另外一個改進。構建Integer對象的傳統方式是直接調用構造器,直接new一個對象。可是根據實踐,咱們發現大部分數據操做都是集中在有
限的、較小的數值範圍,於是,在Java 5中新增了靜態工廠方法valueOf,在調用它的時候會利用一個緩存機制,帶來了明顯的性能改進。按照Javadoc, 這個值默認緩存
是-128到127之間。
這種緩存機制並非只有Integer纔有,一樣存在於其餘的一些包裝類,好比:
注意事項:
[1] 基本類型均具備取值範圍,在大數*大數的時候,有可能會出現越界的狀況。
[2] 基本類型轉換時,使用聲明的方式。例: int result= 1234567890 24 365;結果值必定不會是你所指望的那個值,由於1234567890 24已經超過了int的範圍,若是修改成: long result= 1234567890L 24 * 365;就正常了。
[3] 慎用基本類型處理貨幣存儲。如採用double常會帶來差距,常採用BigDecimal、整型(若是要精確表示分,可將值擴大100倍轉化爲整型)解決該問題。
[4] 優先使用基本類型。原則上,建議避免無心中的裝箱、拆箱行爲,尤爲是在性能敏感的場合,
[5] 若是有線程安全的計算須要,建議考慮使用類型AtomicInteger、 AtomicLong 這樣的線程安全類。部分比較寬的基本數據類型,好比 foat、 double,甚至不能保證更新操做的原子性,
可能出現程序讀取到只更新了一半數據位的數值。
[4].原則上, 建議避免無心中的裝箱、拆箱行爲,尤爲是在性能敏感的場合,建立10萬個Java對象和10萬個整數的開銷可不是一個數量級的,無論是內存使用仍是處理速度,光是對象頭
的空間佔用就已是數量級的差距了。
以咱們常常會使用到的計數器實現爲例,下面是一個常見的線程安全計數器實現。
class Counter { private fnal AtomicLong counter = new AtomicLong(); public void increase() { counter.incrementAndGet(); } }
若是利用原始數據類型,能夠將其修改成
class CompactCounter { private volatile long counter; private satic fnal AtomicLongFieldUpdater<CompactCounter> updater = AtomicLongFieldUpdater.newUpdater(CompactCounter.class, "counter"); public void increase() { updater.incrementAndGet(this); } }
Java原始數據類型和引用類型侷限性:
前面我談了很是多的技術細節,最後再從Java平臺發展的角度來看看,原始數據類型、對象的侷限性和演進。
對於Java應用開發者,設計複雜而靈活的類型系統彷佛已經習覺得常了。可是坦白說,畢竟這種類型系統的設計是源於不少年前的技術決定,如今已經逐漸暴露出了一些反作用,例
如:
這是由於Java的泛型某種程度上能夠算做僞泛型,它徹底是一種編譯期的技巧, Java編譯期會自動將類型轉換爲對應的特定類型,這就決定了使用泛型,必須保證相應類型能夠轉換
爲Object。
置。這種設計雖然帶來了極大靈活性,可是也致使了數據操做的低效,尤爲是沒法充分利用現代CPU緩存機制。
典型回答:
Vector是Java早期提供的線程安全的動態數組,若是不須要線程安全,並不建議選擇,畢竟同步是有額外開銷的。 Vector內部是使用對象數組來保存數據,能夠根據須要自動的增長
容量,當數組已滿時,會建立新的數組,並拷貝原有數組數據。
ArrayList是應用更加普遍的動態數組實現,它自己不是線程安全的,因此性能要好不少。與Vector近似, ArrayList也是能夠根據須要調整容量,不過二者的調整邏輯有所區
別, Vector在擴容時會提升1倍,而ArrayList則是增長50%。
LinkedList顧名思義是Java提供的雙向鏈表,因此它不須要像上面兩種那樣調整容量,它也不是線程安全的。
咱們能夠看到Java的集合框架, Collection接口是全部集合的根,而後擴展開提供了三大類集合,分別是:
今天介紹的這些集合類,都不是線程安全的,對於java.util.concurrent裏面的線程安全容器,我在專欄後面會去介紹。可是,並不表明這些集合徹底不能支持併發編程的場景,
在Collections工具類中,提供了一系列的synchronized方法,好比
static <T> List<T> synchronizedList(List<T> list)
咱們徹底能夠利用相似方法來實現基本的線程安全集合:
List list = Collections.synchronizedList(new ArrayList());
它的實現,基本就是將每一個基本方法,好比get、 set、 add之類,都經過synchronizd添加基本的同步支持,很是簡單粗暴,但也很是實用。注意這些方法建立的線程安全集合,都
符合迭代時fail-fast行爲,當發生意外的併發修改時,儘早拋出ConcurrentModifcationException異常,以免不可預計的行爲。
另一個常常會被考察到的問題,就是理解Java提供的默認排序算法,具體是什麼排序方式以及設計思路等。
這個問題自己就是有點陷阱的意味,由於須要區分是Arrays.sort()仍是Collections.sort() (底層是調用Arrays.sort());什麼數據類型;多大的數據集(過小的數據集,複雜排
序是不必的, Java會直接進行二分插入排序)等。
對於原始數據類型,目前使用的是所謂雙軸快速排序( Dual-Pivot QuickSort),是一種改進的快速排序算法,早期版本是相對傳統的快速排序,你能夠閱讀源碼。
而對於對象數據類型,目前則是使用TimSort,思想上也是一種歸併和二分插入排序( binarySort)結合的優化排序算法。 TimSort並非Java的首創,簡單說它的思路是查找
數據集中已經排好序的分區(這裏叫run),而後合併這些分區來達到排序的目的。
另外, Java 8引入了並行排序算法(直接使用parallelSort方法),這是爲了充分利用現代多核處理器的計算能力,底層實現基於fork-join框架,當處理的數據集比較小的時候,差距不明顯,甚至還表現差一點;可是,當數據集增加到數萬或百萬以上時,提升就很是大了,具體仍是取決於處理器和系統環境。
典型回答
Hashtable、 HashMap、 TreeMap都是最多見的一些Map實現,是以鍵值對的形式存儲和操做數據的容器類型。
Hashtable是早期Java類庫提供的一個哈希表實現,自己是同步的,不支持null鍵和值,因爲同步致使的性能開銷,因此已經不多被推薦使用。
HashMap是應用更加普遍的哈希表實現,行爲上大體上與HashTable一致,主要區別在於HashMap不是同步的,支持null鍵和值等。一般狀況下, HashMap進行put或者get操做,能夠達到常數時間的性能,因此它是絕大部分利用鍵值對存取場景的首選,好比,實現一個用戶ID和用戶信息對應的運行時存儲結構。
TreeMap則是基於紅黑樹的一種提供順序訪問的Map,和HashMap不一樣,它的get、 put、 remove之類操做都是O(log(n))的時間複雜度,具體順序能夠由指定
的Comparator來決定,或者根據鍵的天然順序來判斷。
LinkedHashMap一般提供的是遍歷順序符合插入順序,它的實現是經過爲條目(鍵值對)維護一個雙向鏈表。注意,經過特定構造函數,咱們能夠建立反映訪問順序的實例,所
謂的put、 get、 compute等,都算做「訪問」。
對於TreeMap,它的總體順序是由鍵的順序關係決定的,經過Comparator或Comparable(天然順序)來決定。
HashMap:
而對於負載因子,我建議:
那麼,爲何HashMap要樹化呢?
本質上這是個安全問題。 由於在元素放置過程當中,若是一個對象哈希衝突,都被放置到同一個桶裏,則會造成一個鏈表,咱們知道鏈表查詢是線性的,會嚴重影響存取的性能。而在現實世界,構造哈希衝突的數據並非很是複雜的事情,惡意代碼就能夠利用這些數據大量與服務器端交互,致使服務器端CPU大量佔用,這就構成了哈希碰撞拒絕服務攻擊,國內一線互聯網公司就發生過相似攻擊事件。
Hashtable、 HashMap、 TreeMap比較:
三者均實現了Map接口,存儲的內容是基於key-value的鍵值對映射,一個映射不能有重複的鍵,一個鍵最多隻能映射一個值。
(1) 元素特性
HashTable中的key、 value都不能爲null; HashMap中的key、 value能夠爲null,很顯然只能有一個key爲null的鍵值對,可是容許有多個值爲null的鍵值對; TreeMap中當未實現Comparator 接口時, key 不能夠爲null;當實現 Comparator 接口時,若未對null狀況進行判斷,則key不能夠爲null,反之亦然。
(2)順序特性
HashTable、 HashMap具備無序特性。 TreeMap是利用紅黑樹來實現的(樹中的每一個節點的值,都會大於或等於它的左子樹種的全部節點的值,而且小於或等於它的右子樹中的全部節點的
值),實現了SortMap接口,可以對保存的記錄根據鍵進行排序。因此通常須要排序的狀況下是選擇TreeMap來進行,默認爲升序排序方式(深度優先搜索),可自定義實現Comparator接口
實現排序方式。
(3)初始化與增加方式
初始化時: HashTable在不指定容量的狀況下的默認容量爲11,且不要求底層數組的容量必定要爲2的整數次冪; HashMap默認容量爲16,且要求容量必定爲2的整數次冪。
擴容時: Hashtable將容量變爲原來的2倍加1; HashMap擴容將容量變爲原來的2倍。(4)線程安全性HashTable其方法函數都是同步的(採用synchronized修飾),不會出現兩個線程同時對數據進行操做的狀況,所以保證了線程安全性。也正由於如此,在多線程運行環境下效率表現很是低下。由於當一個線程訪問HashTable的同步方法時,其餘線程也訪問同步方法就會進入阻塞狀態。好比當一個線程在添加數據時候,另一個線程即便執行獲取其餘數據的操做也必須被阻塞,大大下降了程序的運行效率,在新版本中已被廢棄,不推薦使用。HashMap不支持線程的同步,即任一時刻能夠有多個線程同時寫HashMap;可能會致使數據的不一致。若是須要同步(1)能夠用 Collections的synchronizedMap方法;(2)使用ConcurrentHashMap類,相較於HashTable鎖住的是對象總體, ConcurrentHashMap基於lock實現鎖分段技術。首先將Map存放的數據分紅一段一段的存儲方式,而後給每一段數據分配一把鎖,當一個線程佔用鎖訪問其中一個段的數據時,其餘段的數據也能被其餘線程訪問。 ConcurrentHashMap不只保證了多線程運行環境下的數據訪問安全性,並且性能上有長足的提高。(5)一段話HashMapHashMap基於哈希思想,實現對數據的讀寫。當咱們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存值對象。當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。 HashMap使用鏈表來解決碰撞問題,當發生碰撞了,對象將會儲存在鏈表的下一個節點中。 HashMap在每一個鏈表節點中儲存鍵值對對象。當兩個不一樣的鍵對象的hashcode相同時,它們會儲存在同一個bucket位置的鏈表中,可經過鍵對象的equals()方法用來找到鍵值對。若是鏈表大小超過閾值(TREEIFY_THRESHOLD, 8),鏈表就會被改造爲樹形結構。