想在面試、工做中脫穎而出?想在最短的時間內快速掌握 Java 的核心基礎知識點?想要成爲一位優秀的 Java工程師?本篇文章能助你一臂之力!正所謂萬丈高樓平地起,只有把基礎掌握的牢固,才能走的更遠,面對不斷更新的技術才能快速掌握,同時在面試、工做中也更能脫穎而出!
java
說明:如下全部答案均爲我的的理解和網上的一些資料的整合程序員
另外本人整理了20年面試題大全,包含spring、併發、數據庫、Redis、分佈式、dubbo、JVM、微服務等方面總結,下圖是部分截圖,須要的話點這裏點這裏,暗號博客園。面試
List , Set 都是繼承自 Collection 接口 List 特色:元素有放入順序,元素可重複 。spring
Set 特色:元素無放入順序,元素不可重複,重複元素會覆蓋掉,(元素雖然無放入順序,可是元素在set中的位置是有該元素的 HashCode 決定的,其位置實際上是固定的,加入Set 的 Object 必須定義 equals ()方法 ,另外list支持for循環,也就是經過下標來遍歷,也能夠用迭代器,可是set只能用迭代,由於他無序,沒法用下標來取得想要的值。)數據庫
Set和List對比數組
向 HashSet 中 add ()元素時,判斷元素是否存在的依據,不只要比較hash值,同時還要結合 equles 方法比較。HashSet 中的 add ()方法會使用 HashMap 的 add ()方法。如下是 HashSet 部分源碼:安全
private static final Object PRESENT = new Object(); private transient HashMap<E,Object> map; public HashSet() { map = new HashMap<>(); } public boolean add(E e) { return map.put(e, PRESENT)==null; }
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 中,發生哈希衝突是能夠用鏈表法或者紅黑樹來解決的,可是在多線程中,可能就直接給覆蓋了。分佈式
上面所說的是一個圖來解釋可能更加直觀。以下面所示,兩個線程在同一個位置添加數據,後面添加的數據就覆蓋住了前面添加的。
若是上述插入是插入到鏈表上,如兩個線程都在遍歷到最後一個節點,都要在最後添加一個數據,那麼後面添加數據的線程就會把前面添加的數據給覆蓋住。則
在擴容的時候也可能會致使數據不一致,由於擴容是從一個數組拷貝到另一個數組。
當向容器添加元素的時候,會判斷當前容器的元素個數,若是大於等於閾值(知道這個閾字怎麼念嗎?不念 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;
若是 cap 是2的n次方,則容量爲 cap ,不然爲大於 cap 的第一個2的n次方的數。
HashMap結構圖
在 JDK1.7 及以前的版本中, HashMap 又叫散列鏈表:基於一個數組以及多個鏈表的實現,hash值衝突的時候,就將對應節點以鏈表的形式存儲。
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中
使用一個 Node 數組來存儲數據,但這個 Node 多是鏈表結構,也多是紅黑樹結構
那麼即便 hashcode 徹底相同,因爲紅黑樹的特色,查找某個特定元素,也只須要O(log n)的開銷
也就是說put/get的操做的時間複雜度最差只有 O(log 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 左右
1.經過new對象實現反射機制
2.經過路徑實現反射機制
3.經過類名實現反射機制
public class Student { private int id; String name; protected boolean sex; public float score; }
public class Get { //獲取反射機制三種方式 public static void main(String[] args) throws ClassNotFoundException { //方式一(經過創建對象) Student stu = new Student(); Class classobj1 = stu.getClass(); System.out.println(classobj1.getName()); //方式二(所在經過路徑-相對路徑) Class classobj2 = Class.forName("fanshe.Student"); System.out.println(classobj2.getName()); //方式三(經過類名) Class classobj3 = Student.class; System.out.println(classobj3.getName()); } }
Java 反射機制是在運行狀態中,對於任意一個類,都可以得到這個類的全部屬性和方法,對於任意一個對象都可以調用它的任意一個屬性和方法。這種在運行時動態的獲取信息以及動態調用對象的方法的功能稱爲 Java 的反射機制。
Class 類與 java.lang.reflect 類庫一塊兒對反射的概念進行了支持,該類庫包含了Field,Method,Constructor 類 (每一個類都實現了 Member 接口)。這些類型的對象時由 JVM 在運行時建立的,用以表示未知類裏對應的成員。
這樣你就可使用 Constructor 建立新的對象,用 get() 和 set() 方法讀取和修改與 Field 對象關聯的字段,用invoke() 方法調用與 Method 對象關聯的方法。另外,還能夠調用 getFields() getMethods() 和
getConstructors() 等很便利的方法,以返回表示字段,方法,以及構造器的對象的數組。這樣匿名對象的信息就能在運行時被徹底肯定下來,而在編譯時不須要知道任何事情。
import java.lang.reflect.Constructor; public class ReflectTest { public static void main(String[] args) throws Exception { Class clazz = null; clazz = Class.forName("com.jas.reflect.Fruit"); Constructor<Fruit> constructor1 = clazz.getConstructor(); Constructor<Fruit> constructor2 = clazz.getConstructor(String.class); Fruit fruit1 = constructor1.newInstance(); Fruit fruit2 = constructor2.newInstance("Apple"); } } class Fruit{ public Fruit(){ System.out.println("無參構造器 Run..........."); } public Fruit(String type){ System.out.println("有參構造器 Run..........." + type); } }
運行結果: 無參構造器 Run………… 有參構造器 Run…………Apple
對於 Java 數組的初始化,有如下兩種方式,這也是面試中常常考到的經典題目:
靜態初始化:初始化時由程序員顯式指定每一個數組元素的初始值,由系統決定數組長度,如:
//只是指定初始值,並無指定數組的長度,可是系統爲自動決定該數組的長度爲4 String[] computers = {"Dell", "Lenovo", "Apple", "Acer"}; //① //只是指定初始值,並無指定數組的長度,可是系統爲自動決定該數組的長度爲3 String[] names = new String[]{"多啦A夢", "大雄", "靜香"}; //②
動態初始化:初始化時由程序員顯示的指定數組的長度,由系統爲數據每一個元素分配初始值,如:
//只是指定了數組的長度,並無顯示的爲數組指定初始值,可是系統會默認給數組數組元素分配初始值爲null String[] cars = new String[4]; //③
由於 Java 數組變量是引用類型的變量,因此上述幾行初始化語句執行後,三個數組在內存中的分配狀況以下圖所示:
由上圖可知,靜態初始化方式,程序員雖然沒有指定數組長度,可是系統已經自動幫咱們給分配了,而動態初始化方式,程序員雖然沒有顯示的指定初始化值,可是由於 Java 數組是引用類型的變量,因此係統也爲每一個元素分配了初始化值 null ,固然不一樣類型的初始化值也是不同的,假設是基本類型int類型,那麼爲系統分配的初始化值,也是對應的默認值0。
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()方法進行改寫,則調用此方法獲得的對象即爲淺拷貝
互聯網大廠比較喜歡的人才特色:對技術有熱情,強硬的技術基礎實力;主動,善於團隊協做,善於總結思考。技術基礎以及的問題多看看書準備,不懂的直接說不懂不要緊的;在項目細節上多把關一下,根據項目有針對性的談本身的技術亮點,能表達清楚,能夠引導面試官來問你比較擅長的技術問題。
另外本人整理收藏了20年多家公司面試知識點整理 以及各類知識點整理 下面有部分截圖 想要資料的話:【點擊這裏暗號博客園】