再高大上的框架,也須要紮實的基礎才能玩轉,高頻面試問題更是基礎中的高頻實戰要點。html
Java 學習者和愛好者,有必定工做經驗的技術人,準面試官等。java
本教程是系列教程,包含 Java 基礎,JVM,容器,多線程,反射,異常,網絡,對象拷貝,JavaWeb,設計模式,Spring-Spring MVC,Spring Boot / Spring Cloud,Mybatis / Hibernate,Kafka,RocketMQ,Zookeeper,MySQL,Redis,Elasticsearch,Lucene面試
微信搜:JavaPub,閱讀全套系列面試題教程
算法
[toc]編程
JDK 和 JREwindows
JDK(Java Development Kit)是Java開發運行環境,是java開發工具包,JDK包含了JRE的全部東西,同時還包含了編譯java源碼的編譯器javac,還包含了不少java程序調試和分析的工具:jconsole,jvisualvm等工具軟件,還包含了java程序編寫所需的文檔和demo例子程序。設計模式
JRE(Java Runtime Environment)它是Java運行環境,若是你不須要開發只須要運行Java程序,那麼你能夠安裝JRE。(可是在運行JSP程序時,咱們仍是須要JDK,由於應用服務器會將 JSP 轉換爲 Java servlet,而且須要使用 JDK 編譯 servlet。)數組
若是你須要運行java程序,只需安裝JRE就能夠了。若是你須要編寫java程序,須要安裝JDK。安全
JVM服務器
JVM(Java Virtual Machine) 就是咱們常說的 java 虛擬機是 JRE 的一部分,它是整個 java 實現跨平臺的最核心的部分,全部的 java 程序會首先被編譯爲 .class 的類文件,這種類文件能夠在虛擬機上執行。
JVM 主要工做是解釋本身的指令集(即字節碼)並映射到本地的 CPU 指令集和 OS 的系統調用。Java 語言是跨平臺運行的,不一樣的操做系統會有不一樣的 JVM 映射規則,使之與操做系統無關,完成跨平臺性。
JVM是Java Virtual Machine(Java虛擬機)的縮寫,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。由一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域等組成。JVM屏蔽了與操做系統平臺相關的信息,使得Java程序只須要生成在Java虛擬機上運行的目標代碼(字節碼),就可在多種平臺上不加修改的運行,這也是Java可以「一次編譯,處處運行的」緣由。
附一張關係圖:
總結:使用JDK(調用JAVA API)開發JAVA程序後,經過JDK中的編譯程序(javac)將Java程序編譯爲Java字節碼,在JRE上運行這些字節碼,JVM會解析並映射到真實操做系統的CPU指令集和OS的系統調用。
equals 和 == 都是來判斷兩個對象是否相等
public boolean equals(Object obj) { return (this == obj); }
從源碼能夠看出,裏面使用的就是 == 比較,因此這種狀況下比較的就是它們在內存中的存放地址。
@Override public boolean equals(Object other) { if (other == this) { return true; } if (other instanceof String) { String s = (String)other; int count = this.count; if (s.count != count) { return false; } if (hashCode() != s.hashCode()) { return false; } char[] value1 = value; int offset1 = offset; char[] value2 = s.value; int offset2 = s.offset; for (int end = offset1 + count; offset1 < end; ) { if (value1[offset1] != value2[offset2]) { return false; } offset1++; offset2++; } return true; } else { return false; } }
從源碼能夠看出, String 類複寫了 equals 方法,當使用 == 比較內存的存放地址不相等時,接下來會比較字符串的內容是否 相等,因此 String 類中的 equals 方法會比較二者的字符串內容是否同樣。
答案是不必定的
java.lang.Object類中有兩個很是重要的方法:
public boolean equals(Object obj) public int hashCode()
Object
類是類繼承結構的基礎,因此是每個類的父類。全部的對象,包括數組,都實現了在 Object
類中定義的方法。
如下是Object對象API關於equal方法和hashCode方法的說明:
簡而言之,在集合查找時,hashcode能大大下降對象比較次數,提升查找效率!
Java對象的eqauls方法和hashCode方法是這樣規定的:
對以上倆點的說明
想象一下,假如兩個Java對象A和B,A和B相等(eqauls結果爲true),但A和B的哈希碼不一樣,則A和B存入HashMap時的哈希碼計算獲得的HashMap內部數組位置索引可能不一樣,那麼A和B頗有可能容許同時存入HashMap,顯然相等/相同的元素是不容許同時存入HashMap,HashMap不容許存放重複元素。
也就是說,不一樣對象的hashCode可能相同;假如兩個Java對象A和B,A和B不相等(eqauls結果爲false),但A和B的哈希碼相等,將A和B都存入HashMap時會發生哈希衝突,也就是A和B存放在HashMap內部數組的位置索引相同這時HashMap會在該位置創建一個連接表,將A和B串起來放在該位置,顯然,該狀況不違反HashMap的使用原則,是容許的。固然,哈希衝突越少越好,儘可能採用好的哈希算法以免哈希衝突。
總而言之(all in all):
換句話說,equals()方法不相等的兩個對象,hashcode()有可能相等(個人理解是因爲哈希碼在生成的時候產生衝突形成的)。反過來,hashcode()不等,必定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
這是一個很基礎,很能體現你基礎是否紮實、是否有鑽研精神的知識點。
final關鍵字的字面意思是最終的, 不可修改的. 這彷佛是一個看見名字就大概能知道怎麼用的語法。
這三點必需要答出來:
要注意final類中的全部成員方法都會被隱式地指定爲final方法。
第三點尤其重要
當final修飾的是一個基本數據類型數據時, 這個數據的值在初始化後將不能被改變; 當final修飾的是一個引用類型數據時, 也就是修飾一個對象時, 引用在初始化後將永遠指向一個內存地址, 不可修改. 可是該內存地址中保存的對象信息, 是能夠進行修改的.
JavaPub參考巨人(有一些簡單例子,便於更好的理解final):https://www.cnblogs.com/dolph...
返回值:-1
四捨五入的原理是在參數上加0.5而後作向下取整。
一些案例:
public class test { public static void main(String[] args){ System.out.println(Math.round(1.3)); //1 System.out.println(Math.round(1.4)); //1 System.out.println(Math.round(1.5)); //2 System.out.println(Math.round(1.6)); //2 System.out.println(Math.round(1.7)); //2 System.out.println(Math.round(-1.3)); //-1 System.out.println(Math.round(-1.4)); //-1 System.out.println(Math.round(-1.5)); //-1 System.out.println(Math.round(-1.6)); //-2 System.out.println(Math.round(-1.7)); //-2 } }
固然,每個Java學習者都知道它不是基礎類型,可是你要知道更多細節。
Java中的數據類型分爲兩大類,基本數據類型和引用數據類型。
基本數據類型只有8種,可按照以下分類
引用數據類型很是多,大體包括:
類、 接口類型、 數組類型、 枚舉類型、 註解類型、 字符串型。
簡單來講,全部的非基本數據類型都是引用數據類型。
在方法中定義的非全局基本數據類型變量的具體內容是存儲在棧中的
只要是引用數據類型變量,其具體內容都是存放在堆中的,而棧中存放的是其具體內容所在內存的地址(引用/句柄)
ps:經過變量地址能夠找到變量的具體內容,就如同經過房間號能夠找到房間通常
在方法中定義的非全局基本數據類型變量,調用方法時做爲參數是按數值傳遞的
//基本數據類型做爲方法參數被調用 public class Main{ public static void main(String[] args){ int msg = 100; System.out.println("調用方法前msg的值:\n"+ msg); //100 fun(msg); System.out.println("調用方法後msg的值:\n"+ msg); //100 } public static void fun(int temp){ temp = 0; } }
引用數據類型變量,調用方法時做爲參數是按引用傳遞的
//引用數據類型做爲方法參數被調用 class Book{ String name; double price; public Book(String name,double price){ this.name = name; this.price = price; } public void getInfo(){ System.out.println("圖書名稱:"+ name + ",價格:" + price); } public void setPrice(double price){ this.price = price; } } public class Main{ public static void main(String[] args){ Book book = new Book("Java開發指南",66.6); book.getInfo(); //圖書名稱:Java開發指南,價格:66.6 fun(book); book.getInfo(); //圖書名稱:Java開發指南,價格:99.9 } public static void fun(Book temp){ temp.setPrice(99.9); } }
調用時爲temp在棧中開闢新空間,並指向book的具體內容,方法執行完畢後temp在棧中的內存被釋放掉
JavaPub參考巨人:https://blog.csdn.net/py1215/...
String、StringBuffer、StringBuilder
答:不同。
由於內存的分配方式不同。String str="i"的方式,Java 虛擬機會將其分配到常量池中;而 String str=new String(「i」)方式,則會被分到堆內存中。
String str1 = "i"; String str2 = "i"; String str3 = new String("i"); System.out.println(str1 == str2);//ture System.out.println(str2 == str3);//false
解釋:
在String str1="i"中,把i值存在常量池,地址賦給str1。假設再寫一個String str2="i",則會把i的地址賦給str2,可是i對象不會從新建立,他們引用的是同一個地址值,共享同一個i內存。(須要注意的是:String str="i"; 由於String 是final類型的,因此「i」應該是在常量池。)
假設再寫一個String str3=new String(「i」),則會建立一個新的i對象,而後將新對象的地址值賦給str3。雖然str3和str1的值相同可是地址值不一樣。(而new String("i");則是新建對象放到堆內存中。)
拓展知識:
倆種辦法:
public static void main(String[] args) { String str = "ABCDE"; System.out.println(reverseStringByStringBuilderApi(str)); System.out.println(reverseString(str)); } /** * 和StringBuffer()同樣,都用了Java自實現的方法,使用位移來實現 * @param * @return */ public static String reverseStringByStringBuilderApi(String str) { if (str != null && str.length() > 0) { return new StringBuilder(str).reverse().toString(); } return str; } public static String reverseString(String str) { if (str != null && str.length() > 0) { int len = str.length(); char[] chars = new char[len]; for (int i = len - 1; i >= 0; i--) { chars[len - 1 - i] = str.charAt(i); } return new String(chars); } return str; }
更多交換方式參考:https://blog.csdn.net/py1215/...
下面列舉了20個經常使用方法。格式:返回類型 方法名 做用。
答案是:沒必要須
這道題考察的是抽象類的知識:
抽象類的基本使用示例:
//定義一個抽象類 abstract class A{ //普通方法 public void fun(){ System.out.println("存在方法體的方法"); } //抽象方法,沒有方法體,有abstract關鍵字作修飾 public abstract void print(); } //單繼承 //B類是抽象類的子類,是一個普通類 class B extends A{ //強制要求覆寫 @Override public void print() { System.out.println("Hello World !"); } } public class TestDemo { public static void main(String[] args) { //向上轉型 A a = new B(); //被子類所覆寫的過的方法 a.print(); } }
JavaPub參考巨人:https://www.jianshu.com/p/053...
包含抽象方法的類稱爲抽象類,但並不意味着抽象類中只能有抽象方法,它和普通類同樣,一樣能夠擁有成員變量和普通的成員方法。注意,抽象類和普通類的主要有三點區別:
不能,抽象類是被用於繼承的,final修飾表明不可修改、不可繼承的。
這個在前面幾題有過介紹。
語法層面上的區別,也是咱們平常官方的一些說法:
接口的設計目的,是對類的行爲進行約束。而抽象類的設計目的,是代碼複用。總結來講,繼承是一個 "是否是"的關係,而 接口 實現則是 "有沒有"的關係。若是一個類繼承了某個抽象類,則子類一定是抽象類的種類,而接口實現則是有沒有、具有不具有的關係。
抽象和繼承是 Java 中很是重要的東西,深刻理解可讓咱們對 Java 技術理解更深入,參考的這篇知乎博文很是好。
JavaPub參考巨人:https://www.zhihu.com/questio...
Java中的流分爲兩種,一種是字節流,另外一種是字符流,分別由四個抽象類來表示(每種流包括輸入和輸出兩種因此一共四個):InputStream,OutputStream,Reader,Writer。Java中其餘多種多樣變化的流均是由它們派生出來的.
字符流和字節流是根據處理數據的不一樣來區分的。字節流按照8位傳輸,字節流是最基本的,全部文件的儲存是都是字節(byte)的儲存,在磁盤上保留的並非文件的字符而是先把字符編碼成字節,再儲存這些字節到磁盤。
讀文本的時候用字符流,例如txt文件。讀非文本文件的時候用字節流,例如mp3。理論上任何文件都可以用字節流讀取,但當讀取的是文本數據時,爲了能還原成文本你必須再通過一個轉換的工序,相對來講字符流就省了這個麻煩,能夠有方法直接讀取。
字符流處理的單元爲2個字節的Unicode字符,分別操做字符、字符數組或字符串,而字節流處理單元爲1個字節, 操做字節和字節數組。因此字符流是由Java虛擬機將字節轉化爲2個字節的Unicode字符爲單位的字符而成的,因此它對多國語言支持性比較好!
代碼Demo參考:https://www.yisu.com/zixun/12...
BIO
BIO全稱是Blocking IO,是JDK1.4以前的傳統IO模型,自己是同步阻塞模式。
線程發起IO請求後,一直阻塞IO,直到緩衝區數據就緒後,再進入下一步操做。針對網絡通訊都是一請求一應答的方式,雖然簡化了上層的應用開發,但在性能和可靠性方面存在着巨大瓶頸,試想一下若是每一個請求都須要新建一個線程來專門處理,那麼在高併發的場景下,機器資源很快就會被耗盡。
NIO
NIO也叫Non-Blocking IO 是同步非阻塞的IO模型。線程發起io請求後,當即返回(非阻塞io)。同步指的是必須等待IO緩衝區內的數據就緒,而非阻塞指的是,用戶線程不原地等待IO緩衝區,能夠先作一些其餘操做,可是要定時輪詢檢查IO緩衝區數據是否就緒。Java中的NIO 是new IO的意思。實際上是NIO加上IO多路複用技術。普通的NIO是線程輪詢查看一個IO緩衝區是否就緒,而Java中的new IO指的是線程輪詢地去查看一堆IO緩衝區中哪些就緒,這是一種IO多路複用的思想。IO多路複用模型中,將檢查IO數據是否就緒的任務,交給系統級別的select或epoll模型,由系統進行監控,減輕用戶線程負擔。
NIO主要有buffer、channel、selector三種技術的整合,經過零拷貝的buffer取得數據,每個客戶端經過channel在selector(多路複用器)上進行註冊。服務端不斷輪詢channel來獲取客戶端的信息。channel上有connect,accept(阻塞)、read(可讀)、write(可寫)四種狀態標識。根據標識來進行後續操做。因此一個服務端可接收無限多的channel。不須要新開一個線程。大大提高了性能。
AIO
AIO是真正意義上的異步非阻塞IO模型。
上述NIO實現中,須要用戶線程定時輪詢,去檢查IO緩衝區數據是否就緒,佔用應用程序線程資源,其實輪詢至關於仍是阻塞的,並不是真正解放當前線程,由於它仍是須要去查詢哪些IO就緒。而真正的理想的異步非阻塞IO應該讓內核系統完成,用戶線程只須要告訴內核,當緩衝區就緒後,通知我或者執行我交給你的回調函數。
AIO能夠作到真正的異步的操做,但實現起來比較複雜,支持純異步IO的操做系統很是少,目前也就windows是IOCP技術實現了,而在Linux上,底層仍是是使用的epoll實現的。
資料:
BIO (Blocking I/O): 同步阻塞I/O模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。在活動鏈接數不是特別高(小於單機1000)的狀況下,這種模型是比較不錯的,可讓每個鏈接專一於本身的 I/O 而且編程模型簡單,也不用過多考慮系統的過載、限流等問題。線程池自己就是一個自然的漏斗,能夠緩衝一些系統處理不了的鏈接或請求。可是,當面對十萬甚至百萬級鏈接的時候,傳統的 BIO 模型是無能爲力的。所以,咱們須要一種更高效的 I/O 處理模型來應對更高的併發量。NIO (New I/O): NIO是一種同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,對應 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N能夠理解爲Non-blocking,不單純是New。它支持面向緩衝的,基於通道的I/O操做方法。 NIO提供了與傳統BIO模型中的 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不一樣的套接字通道實現,兩種通道都支持阻塞和非阻塞兩種模式。阻塞模式使用就像傳統中的支持同樣,比較簡單,可是性能和可靠性都很差;非阻塞模式正好與之相反。對於低負載、低併發的應用程序,可使用同步阻塞I/O來提高開發速率和更好的維護性;對於高負載、高併發的(網絡)應用,應使用 NIO 的非阻塞模式來開發
AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型。異步 IO 是基於事件和回調機制實現的,也就是應用操做以後會直接返回,不會堵塞在那裏,當後臺處理完成,操做系統會通知相應的線程進行後續的操做。AIO 是異步IO的縮寫,雖然 NIO 在網絡操做中,提供了非阻塞的方法,可是 NIO 的 IO 行爲仍是同步的。對於 NIO 來講,咱們的業務線程是在 IO 操做準備好時,獲得通知,接着就由這個線程自行進行 IO 操做,IO操做自己是同步的。查閱網上相關資料,我發現就目前來講 AIO 的應用還不是很普遍,Netty 以前也嘗試使用過 AIO,不過又放棄了。
Files.exists():檢測文件路徑是否存在。
Files.createFile():建立文件。
Files.createDirectory():建立文件夾。
Files.delete():刪除一個文件或目錄。
Files.copy():複製文件。
Files.move():移動文件。
Files.size():查看文件個數。
Files.read():讀取文件。
Files.write():寫入文件。
微信關注:JavaPub ,帶走全套寶典