聲明:此文章非本人所 原創,是別人分享所得,如有知道原作者是誰可以聯系本人,如有轉載請加上此段話
Set 特點:元素無放入順序,元素不可重復,重復元素會覆蓋掉,(元素雖然無放入順序,但是元素在set中的位置是有該元素的 HashCode 決定的,其位置其實是固定的,加入Set 的 Object 必須定義 equals ()方法 ,另外list支持for循環,也就是通過下標來遍歷,也可以用迭代器,但是set只能用迭代,因為他無序,無法用下標來取得想要的值。) Set和List對比 Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變。
List:和數組類似,List可以動態增長,查找元素效率高,插入刪除元素效率低,因為會引起其他元素位置改變
向 HashSet 中 add ()元素時,判斷元素是否存在的依據,不僅要比較hash值,同時還要結合 equles 方法比較。
HashSet 中的 add ()方法會使用 HashMap 的 add ()方法。以下是 HashSet 部分源碼:
1 private static final Object PRESENT = new Object(); 2 private transient HashMap<E,Object> map; 3 public HashSet() { 4 map = new HashMap<>(); 5 } 6 public boolean add(E e) { 7 return map.put(e, PRESENT)==null; 8 9 }
HashMap 的 key 是唯一的,由上面的代碼可以看出 HashSet 添加進去的值就是作為 HashMap 的key。所以不會重復( HashMap 比較key是否相等是先比較 hashcode 在比較 equals )。
如果有兩個線程A和B,都進行插入數據,剛好這兩條不同的數據經過哈希計算后得到的哈希碼是一樣的,且該位置還沒有其他的數據。所以這兩個線程都會進入我在上面標記為1的代碼中。假設一種情況,線程A通過if判斷,該位置沒有哈希沖突,進入了if語句,還沒有進行數據插入,這時候 CPU 就把資源讓給了線程B,線程A停在了if語句里面,線程B判斷該位置沒有哈希沖突(線程A的數據還沒插入),也進入了if語句,線程B執行完后,輪到線程A執行,現在線程A直接在該位置插入而不用再判斷。這時候,你會發現線程A把線程B插入的數據給覆蓋了。發生了線程不安全情況。本來在 HashMap 中,發生哈希沖突是可以用鏈表法或者紅黑樹來解決的,但是在多線程中,可能就直接給覆蓋了。
上面所說的是一個圖來解釋可能更加直觀。如下面所示,兩個線程在同一個位置添加數據,后面添加的數據就覆蓋住了前面添加的。

如果上述插入是插入到鏈表上,如兩個線程都在遍歷到最后一個節點,都要在最后添加一個數據,那么后面添加數據的線程就會把前面添加的數據給覆蓋住。則在擴容的時候也可能會導致數據不一致,因為擴容是從一個數組拷貝到另外一個數組。

HashMap的擴容過程
當向容器添加元素的時候,會判斷當前容器的元素個數,如果大於等於閾值(知道這個閾字怎么念嗎?不念 fa 值,念 yu 值四聲)---即當前數組的長度乘以加載因子的值的時候,就要自動擴容啦。
擴容( resize )就是重新計算容量,向 HashMap 對象里不停的添加元素,而 HashMap 對象內部的數組無法裝載更多的元素時,對象就需要擴大數組的長度,以便能裝入更多的元素。當然 Java 里的數組是無法自動擴容的,方法是使用一個新的數組代替已有的容量小的數組,就像我們用一個小桶裝水,如果想裝更多的水,就得換大水桶。
HashMap hashMap=new HashMap(cap);
cap =3, hashMap 的容量為4;
cap =4, hashMap 的容量為4;
cap =5, hashMap 的容量為8;
cap =9, hashMap 的容量為16;

JDK1.8 中,當同一個hash值( Table 上元素)的鏈表節點數不小於8時,將不再以單鏈表的形式存儲了,會被調整成一顆紅黑樹。這就是 JDK7 與 JDK8 中 HashMap 實現的最大區別。
其下基於 JDK1.7.0_80 與 JDK1.8.0_66 做的分析
JDK1.7中
使用一個 Entry 數組來存儲數據,用key的 hashcode 取模來決定key會被放到數組里的位置,如果 hashcode 相同,或者 hashcode 取模后的結果相同( hash collision ),那么這些 key 會被定位到 Entry 數組的同一個格子里,這些 key 會形成一個鏈表。
在 hashcode 特別差的情況下,比方說所有key的 hashcode 都相同,這個鏈表可能會很長,那么 put/get 操作都可能需要遍歷這個鏈表,也就是說時間復雜度在最差情況下會退化到 O(n)
JDK1.8中key的對象,必須正確的實現了 Compare 接口如果沒有實現 Compare 接口,或者實現得不正確(比方說所有 Compare 方法都返回0)那 JDK1.8 的 HashMap 其實還是慢於 JDK1.7 的
向 HashMap 中 put/get 1w 條 hashcode 相同的對象
JDK1.7: put 0.26s , get 0.55s
JDK1.8 (未實現 Compare 接口): put 0.92s , get 2.1s但是如果正確的實現了 Compare 接口,那么 JDK1.8 中的 HashMap 的性能有巨大提升,這次 put/get 100W條hashcode 相同的對象
JDK1.8 (正確實現 Compare 接口,): put/get 大概開銷都在320 ms 左右finally一般作用在try-catch代碼塊中,在處理異常的時候,通常我們將一定要執行的代碼方法finally代碼塊中,表示不管是否出現異常,該代碼塊都會執行,一般用來存放一些關閉資源的代碼。
finalize是一個方法,屬於Object類的一個方法,而Object類是所有類的父類,該方法一般由垃圾回收器來調用,當我們調用System.gc() 方法的時候,由垃圾回收器調用finalize(),回收垃圾,一個對象是否可回收的最后判斷。
對象的四種引用Object obj = new Object(); User user=new User();
可直接通過obj取得對應的對象 如 obj.equels(new Object()); 而這樣 obj 對象對后面 new Object 的一個強引用,只有當 obj 這個引用被釋放之后,對象才會被釋放掉,這也是我們經常所用到的編碼形式。
軟引用非必須引用,內存溢出之前進行回收,可以通過以下代碼實現1 Object obj = new Object(); 2 SoftReference<Object> sf = new SoftReference<Object>(obj); 3 4 obj = null; 5 6 sf.get();//有時候會返回null
這時候sf是對obj的一個軟引用,通過sf.get()方法可以取到這個對象,當然,當這個對象被標記為需要回收的對象時,則返回null; 軟引用主要用戶實現類似緩存的功能,在內存足夠的情況下直接通過軟引用取值,無需從繁忙的真實來源查詢數據,提升速度;當內存不足時,自動刪除這部分緩存數據,從真正的來源查詢這些數據。
1 Object obj = new Object(); 2 WeakReference<Object> wf = new WeakReference<Object>(obj); 3 4 obj = null; 5 6 wf.get();//有時候會返回null 7 wf.isEnQueued();//返回是否被垃圾回收器標記為即將回收的垃圾
弱引用是在第二次垃圾回收時回收,短時間內通過弱引用取對應的數據,可以取到,當執行過第二次垃圾回收時,將返回null。弱引用主要用於監控對象是否已經被垃圾回收器標記為即將回收的垃圾,可以通過弱引用的isEnQueued 方法返回對象是否被垃圾回收器標記。
1 public class ThreadLocal<T> { 2 static class ThreadLocalMap { 3 static class Entry extends WeakReference<ThreadLocal<?>> { 4 5 /** The value associated with this ThreadLocal. */ 6 7 Object value; 8 9 Entry(ThreadLocal<?> k, Object v) { 10 11 super(k); 12 value = v; 13 } 14 } 15 //.... 16 } 17 //..... 18 19 }
虛引用垃圾回收時回收,無法通過引用取到對象值,可以通過如下代碼實現
1 Object obj = new Object(); 2 PhantomReference<Object> pf = new PhantomReference<Object>(obj); 3 4 obj=null; 5 6 pf.get();//永遠返回null 7 pf.isEnQueued();//返回是否從內存中已經刪除
虛引用是每次垃圾回收的時候都會被回收,通過虛引用的get方法永遠獲取到的數據為null,因此也被成為幽靈引用。虛引用主要用於檢測對象是否已經從內存中刪除。
1 public class Student { 2 3 private int id; 4 5 String name; 6 7 protected boolean sex; 8 9 public float score; 10 11 } 12 13 public class Get { 14 //獲取反射機制三種方式 15 public static void main(String[] args) throws ClassNotFoundException { 16 17 //方式一(通過建立對象) 18 19 Student stu = new Student(); 20 21 Class classobj1 = stu.getClass(); 22 23 System.out.println(classobj1.getName()); 24 25 //方式二(所在通過路徑-相對路徑) 26 27 Class classobj2 = Class.forName("fanshe.Student"); 28 29 System.out.println(classobj2.getName()); 30 31 //方式三(通過類名) 32 33 Class classobj3 = Student.class; 34 35 System.out.println(classobj3.getName()); 36 37 } 38 39 }
Java反射機制
Class 類與 java.lang.reflect 類庫一起對反射的概念進行了支持,該類庫包含了 Field,Method,Constructor 類 (每個類都實現了 Member 接口)。這些類型的對象時由 JVM 在運行時創建的,用以表示未知類里對應的成員。這樣你就可以使用 Constructor 創建新的對象,用 get() 和 set() 方法讀取和修改與 Field 對象關聯的字段,用invoke() 方法調用與 Method 對象關聯的方法。另外,還可以調用 getFields() getMethods() 和getConstructors() 等很便利的方法,以返回表示字段,方法,以及構造器的對象的數組。這樣匿名對象的信息就能在運行時被完全確定下來,而在編譯時不需要知道任何事情。
1 import java.lang.reflect.Constructor; 2 3 public class ReflectTest { 4 5 public static void main(String[] args) throws Exception { 6 Class clazz = null; 7 clazz = Class.forName("com.jas.reflect.Fruit"); 8 9 Constructor<Fruit> constructor1 = clazz.getConstructor(); 10 11 Constructor<Fruit> constructor2 = clazz.getConstructor(String.class); 12 13 Fruit fruit1 = constructor1.newInstance(); 14 Fruit fruit2 = constructor2.newInstance("Apple"); 15 } 16 } 17 class Fruit{ 18 public Fruit(){ 19 System.out.println("無參構造器 Run..........."); 20 } 21 public Fruit(String type){ 22 System.out.println("有參構造器 Run..........." + type); 23 } 24 }
運行結果: 無參構造器 Run……….. 有參構造器 Run………..Apple
java.util.Collection 是一個集合接口。它提供了對集合對象進行基本操作的通用接口方法。
java.util.Collections 是針對集合類的一個幫助類,他提供一系列靜態方法實現對各種集合的搜索、排序、線程安全等操作。 然后還有混排(Shuffling)、反轉(Reverse)、替換所有的元素(fill)、拷貝(copy)、返回Collections中最小元素(min)、返回Collections中最大元素(max)、返回指定源列表中最后一次出現指定目標列表的起始位置( lastIndexOfSubList )、返回指定源列表中第一次出現指定目標列表的起始位置( IndexOfSubList )、根據指定的距離循環移動指定列表中的元素(Rotate);
事實上Collections.sort方法底層就是調用的array.sort方法,1 public static void sort(Object[] a) { 2 if (LegacyMergeSort.userRequested) 3 legacyMergeSort(a); 4 else 5 ComparableTimSort.sort(a, 0, a.length, null, 0, 0); 6 } 7 //void java.util.ComparableTimSort.sort() 8 9 static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) 10 11 { 12 13 assert a != null && lo >= 0 && lo <= hi && hi <= a.length; 14 15 int nRemaining = hi - lo; 16 17 if (nRemaining < 2) 18 19 return; // Arrays of size 0 and 1 are always sorted 20 // If array is small, do a "mini-TimSort" with no merges 21 if (nRemaining < MIN_MERGE) { 22 int initRunLen = countRunAndMakeAscending(a, lo, hi); 23 binarySort(a, lo, hi, lo + initRunLen); 24 return; 25 26 } 27 28 }
legacyMergeSort (a):歸並排序 ComparableTimSort.sort() : Timsort 排序
Timsort 排序是結合了合並排序(merge sort)和插入排序(insertion sort)而得出的排序算法
Timsort的核心過程
TimSort 算法為了減少對升序部分的回溯和對降序部分的性能倒退,將輸入按其升序和降序特點進行了分區。排序的輸入的單位不是一個個單獨的數字,而是一個個的塊-分區。其中每一個分區叫一個run。針對這些 run 序列,每次拿一個 run 出來按規則進行合並。每次合並會將兩個 run合並成一個 run。合並的結果保存到棧中。合並直到消耗掉所有的 run,這時將棧上剩余的 run合並到只剩一個 run 為止。這時這個僅剩的run 便是排好序的結果。
綜上述過程,Timsort算法的過程包括
(0)如何數組長度小於某個值,直接用二分插入排序算法
(1)找到各個run,並入棧
(2)按規則合並run基於 LinkedHashMap 的訪問順序的特點,可構造一個 LRU(Least Recently Used) 最近最少使用簡單緩存。也有一些開源的緩存產品如 ehcache 的淘汰策略( LRU )就是在 LinkedHashMap 上擴展的。
Cloneable接口實現原理Cloneable接口是Java開發中常用的一個接口, 它的作用是使一個類的實例能夠將自身拷貝到另一個新的實例中,注意,這里所說的“拷貝”拷的是對象實例,而不是類的定義,進一步說,拷貝的是一個類的實例中各字段的值。在開發過程中,拷貝實例是常見的一種操作,如果一個類中的字段較多,而我們又采用在客戶端中逐字段復制的方法進行拷貝操作的話,將不可避免的造成客戶端代碼繁雜冗長,而且也無法對類中的私有成員進行復制,而如果讓需要具備拷貝功能的類實現Cloneable接口,並重寫clone()方法,就可以通過調用clone()方法的方式簡潔地實現實例
拷貝功能
深拷貝(深復制)和淺拷貝(淺復制)是兩個比較通用的概念,尤其在C++語言中,若不弄懂,則會在delete的時候出問題,但是我們在這幸好用的是Java。雖然Java自動管理對象的回收,但對於深拷貝(深復制)和淺拷貝(淺復制),我們還是要給予足夠的重視,因為有時這兩個概念往往會給我們帶來不小的困惑。
淺拷貝是指拷貝對象時僅僅拷貝對象本身(包括對象中的基本變量),而不拷貝對象包含的引用指向的對象。
深拷貝不僅拷貝對象本身,而且拷貝對象包含的引用指向的所有對象。舉例來說更加清楚:對象 A1 中包含對 B1 的引用, B1 中包含對 C1 的引用。淺拷貝 A1 得到 A2 , A2 中依然包含對 B1 的引用, B1 中依然包含對 C1 的引用。深拷貝則是對淺拷貝的遞歸,深拷貝 A1 得到 A2 , A2 中包含對 B2 ( B1 的 copy )的引用, B2 中包含對 C2 ( C1 的 copy )的引用。若不對clone()方法進行改寫,則調用此方法得到的對象即為淺拷貝

Java標准庫內建了一些通用的異常,這些類以Throwable為頂層父類。
Throwable又派生出Error類和Exception類。
錯誤:Error類以及他的子類的實例,代表了JVM本身的錯誤。錯誤不能被程序員通過代碼處理,Error很少出現。因此,程序員應該關注Exception為父類的分支下的各種異常類。
異常:Exception以及他的子類,代表程序運行時發送的各種不期望發生的事件。可以被Java異常處理機制使用, 是異常處理的核心。
非檢查異常( unckecked exception ): Error 和 RuntimeException 以及他們的子類。 javac 在編譯時,不會提示和發現這樣的異常,不要求在程序處理這些異常。所以如果願意,我們可以編寫代碼處理(使用 try…catch…finally )這樣的異常,也可以不處理。對於這些異常,我們應該修正代碼,而不是去通過異常處理器處理 。這樣的異常發生的原因多半是代碼寫的有問題。如除0錯誤 ArithmeticException ,錯誤的強制類型轉換錯誤 ClassCastException ,數組索引越界ArrayIndexOutOfBoundsException ,使用了空對象NullPointerException 等等。
檢查異常( checked exception ):除了 Error 和 RuntimeException 的其它異常。 javac 強制要求程序員為這樣的異常做預備處理工作(使用 try…catch…finally 或者 throws )。在方法中要么用 try-catch 語句捕獲它並處理,要么用throws子句聲明拋出它,否則編譯不會通過。這樣的異常一般是由程序的運行環境導致的。因為程序可能被運行在各種未知的環境下,而程序員無法干預用戶如何使用他編寫的程序,於是程序員就應該為這樣的異常時刻准備着。如 SQLException , IOException ,ClassNotFoundException 等。
需要明確的是:檢查和非檢查是對於 javac 來說的,這樣就很好理解和區分了。1 public class Thread implements Runnable { 2 3 public static native void sleep(long millis) throws InterruptedException; 4 5 public static void sleep(long millis, int nanos) throws InterruptedException { 6 7 if (millis < 0) { 8 9 throw new IllegalArgumentException("timeout value is negative"); 10 11 } 12 13 if (nanos < 0 || nanos > 999999) { 14 15 16 throw new IllegalArgumentException( 17 18 "nanosecond timeout value out of range"); 19 20 } 21 22 if (nanos >= 500000 || (nanos != 0 && millis == 0)) { 23 24 millis++; 25 26 } 27 28 sleep(millis); 29 30 } 31 //... 32 33 } 34 35 public class Object { 36 37 public final native void wait(long timeout) throws InterruptedException; 38 public final void wait(long timeout, int nanos) throws InterruptedException { 39 if (timeout < 0) { 40 throw new IllegalArgumentException("timeout value is negative"); 41 42 } 43 if (nanos < 0 || nanos > 999999) { 44 throw new IllegalArgumentException( 45 "nanosecond timeout value out of range"); 46 } 47 if (nanos > 0) { 48 timeout++; 49 } 50 wait(timeout); 51 52 } 53 54 //... 55 56 }
1、 sleep 來自 Thread 類,和 wait 來自 Object 類。
2、最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
3、wait,notify和 notifyAll 只能在同步控制方法或者同步控制塊里面使用,而 sleep 可以在任何地方使用(使用范圍)
4、 sleep 必須捕獲異常,而 wait , notify 和 notifyAll 不需要捕獲異常(1) sleep 方法屬於 Thread 類中方法,表示讓一個線程進入睡眠狀態,等待一定的時間之后,自動醒來進入到可運行狀態,不會馬上進入運行狀態,因為線程調度機制恢復線程的運行也需要時間,一個線程對象調用了 sleep方法之后,並不會釋放他所持有的所有對象鎖,所以也就不會影響其他進程對象的運行。但在 sleep 的過程中過程中有可能被其他對象調用它的 interrupt() ,產生 InterruptedException 異常,如果你的程序不捕獲這個異常,線程就會異常終止,進入 TERMINATED 狀態,如果你的程序捕獲了這個異常,那么程序就會繼續執行catch語句塊(可能還有 finally 語句塊)以及以后的代碼。注意 sleep() 方法是一個靜態方法,也就是說他只對當前對象有效,通過 t.sleep() 讓t對象進入 sleep ,這樣的做法是錯誤的,它只會是使當前線程被 sleep 而不是 t 線程。
(2) wait 屬於 Object 的成員方法,一旦一個對象調用了wait方法,必須要采用 notify() 和 notifyAll() 方法喚醒該進程;如果線程擁有某個或某些對象的同步鎖,那么在調用了 wait() 后,這個線程就會釋放它持有的所有同步資源,而不限於這個被調用了 wait() 方法的對象。 wait() 方法也同樣會在 wait 的過程中有可能被其他對象調用 interrupt() 方法而產生 。
數組在內存中如何分配1 //只是指定初始值,並沒有指定數組的長度,但是系統為自動決定該數組的長度為4 2 String[] computers = {"Dell", "Lenovo", "Apple", "Acer"};//① 3 4 //只是指定初始值,並沒有指定數組的長度,但是系統為自動決定該數組的長度為3 5 6 String[] names = new String[]{"多啦A夢", "大雄", "靜香"}; //②
1 //只是指定了數組的長度,並沒有顯示的為數組指定初始值,但是系統會默認給數組數組元素分配初始值為null 2 3 4 String[] cars = new String[4];//③
因為 Java 數組變量是引用類型的變量,所以上述幾行初始化語句執行后,三個數組在內存中的分配情況如下圖所

由上圖可知,靜態初始化方式,程序員雖然沒有指定數組長度,但是系統已經自動幫我們給分配了,而動態初始化方式,程序員雖然沒有顯示的指定初始化值,但是因為 Java 數組是引用類型的變量,所以系統也為每個元素分配了初始化值 null ,當然不同類型的初始化值也是不一樣的,假設是基本類型int類型,那么為系統分配的初始化值也是對應的默認值0。