前言
在所有互聯網公司中,Nginx 作為最常用的 7 層負載均衡代理層,每個后端開發人員和運維人員都應該對其有較為深入的理解。
小編分享的這份Java后端開發面試總結包含了JavaOOP、Java集合容器、Java異常、並發編程、Java反射、Java序列化、JVM、Redis、Spring MVC、MyBatis、MySQL數據庫、消息中間件MQ、Dubbo、Linux、ZooKeeper、 分布式&數據結構與算法等26個專題技術點,都是小編在各個大廠總結出來的面試真題,已經有很多粉絲靠這份PDF拿下眾多大廠的offer,今天在這里總結分享給到大家!【已完結】
完整版Java面試題地址:2021最新面試題合集集錦。
1. 什么是IO
2. 在了解不同的IO之前先了解:同步與異步,阻塞與非阻塞的區別
- 同步,一個任務的完成之前不能做其他操作,必須等待(等於在打電話)
- 異步,一個任務的完成之前,可以進行其他操作(等於在聊QQ)
- 阻塞,是相對於CPU來說的, 掛起當前線程,不能做其他操作只能等待
- 非阻塞,,無須掛起當前線程,可以去執行其他操作
3. 什么是BIO
BIO:同步並阻塞,服務器實現一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,沒處理完之前此線程不能做其他操作(如果是單線程的情況下,我傳輸的文件很大呢?),當然可以通過線程池機制改善。BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,並發局限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
4. 什么是NIO
NIO:同步非阻塞,服務器實現一個連接一個線程,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個線程進行處理。NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,並發局限於應用中,編程比較復雜,JDK1.4之后開始支持。
5. 什么是AIO
- AIO:異步非阻塞,服務器實現模式為一個有效請求一個線程,客戶端的I/O請求都是由操作系統先完成了再通知服務器應用去啟動線程進行處理,AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用操作系統參與並發操作,編程比較復雜,JDK1.7之后開始支持。.
- AIO屬於NIO包中的類實現,其實IO主要分為BIO和NIO,AIO只是附加品,解決IO不能異步的實現
- 在以前很少有Linux系統支持AIO,Windows的IOCP就是該AIO模型。但是現在的服務器一般都是支持AIO操作
6. 什么Netty
7. BIO和NIO、AIO的區別
- BIO是阻塞的,NIO是非阻塞的.
- BIO是面向流的,只能單向讀寫,NIO是面向緩沖的, 可以雙向讀寫
- 使用BIO做Socket連接時,由於單向讀寫,當沒有數據時,會掛起當前線程,阻塞等待,為防止影響其它連接,,需要為每個連接新建線程處理.,然而系統資源是有限的,,不能過多的新建線程,線程過多帶來線程上下文的切換,從來帶來更大的性能損耗,因此需要使用NIO進行BIO多路復用,使用一個線程來監聽所有Socket連接,使用本線程或者其他線程處理連接
- AIO是非阻塞 以異步方式發起 I/O 操作。當 I/O 操作進行時可以去做其他操作,由操作系統內核空間提醒IO操作已完成(不懂的可以往下看)
8. IO流的分類
9. 什么是內核空間
10. 五種IO模型
注意:我這里的用戶空間就是應用程序空間
10.1 阻塞BIO(blocking I/O)
10.2.非阻塞NIO(noblocking I/O)
B也在河邊釣魚,但是B不想將自己的所有時間都花費在釣魚上,在等魚上鈎這個時間段中,B也在做其他的事情(一會看看書,一會讀讀報紙,一會又去看其他人的釣魚等),但B在做這些事情的時候,每隔一個固定的時間檢查魚是否上鈎。一旦檢查到有魚上鈎,就停下手中的事情,把魚釣上來。 B在檢查魚竿是否有魚,是一個輪詢的過程。
10.3.異步AIO(asynchronous I/O)
10.4.信號驅動IO(signal blocking I/O)
G也在河邊釣魚,但與A、B、C不同的是,G比較聰明,他給魚竿上掛一個鈴鐺,當有魚上鈎的時候,這個鈴鐺就會被碰響,G就會將魚釣上來。
10.5.IO多路轉接(I/O multiplexing)
11. 什么是比特(Bit),什么是字節(Byte),什么是字符(Char),它們長度是多少,各有什么區別
- Bit最小的二進制單位 ,是計算機的操作部分取值0或者1
- Byte是計算機中存儲數據的單元,是一個8位的二進制數,(計算機內部,一個字節可表示一個英文字母,兩個字節可表示一個漢字。) 取值(-128-127)
- Char是用戶的可讀寫的最小單位,他只是抽象意義上的一個符號。如‘5’,‘中’,‘¥’ 等等等等。在java里面由16位bit組成Char 取值 (0-65535)
- Bit 是最小單位 計算機他只能認識0或者1
- Byte是8個字節 是給計算機看的
- 字符 是看到的東西 一個字符=二個字節
12. 什么叫對象序列化,什么是反序列化,實現對象序列化需要做哪些工作
- 對象序列化,將對象以二進制的形式保存在硬盤上
- 反序列化;將二進制的文件轉化為對象讀取
- 實現serializable接口,不想讓字段放在硬盤上就加transient
13. 在實現序列化接口是時候一般要生成一個serialVersionUID字段,它叫做什么,一般有什么用
- 如果用戶沒有自己聲明一個serialVersionUID,接口會默認生成一個serialVersionUID
- 但是強烈建議用戶自定義一個serialVersionUID,因為默認的serialVersinUID對於class的細節非常敏感,反序列化時可能會導致InvalidClassException這個異常。
- (比如說先進行序列化,然后在反序列化之前修改了類,那么就會報錯。因為修改了類,對應的SerialversionUID也變化了,而序列化和反序列化就是通過對比其SerialversionUID來進行的,一旦SerialversionUID不匹配,反序列化就無法成功。
14. 怎么生成SerialversionUID
15. BufferedReader屬於哪種流,它主要是用來做什么的,它里面有那些經典的方法
屬於處理流中的緩沖流,可以將讀取的內容存在內存里面,有readLine()方法
16. Java中流類的超類主要有那些?
- 超類代表頂端的父類(都是抽象類)
- java.io.InputStream
- java.io.OutputStream
- java.io.Reader
- java.io.Writer
17. 為什么圖片、視頻、音樂、文件等 都是要字節流來讀取
18. IO的常用類和方法,以及如何使用
19. IO基本操作講解
這里的基本操作就是普通的讀取操作,如果想要跟深入的了解不同的IO開發場景必須先了解IO的基本操作
20. 網絡操作IO講解
- 我這使用Socket簡單的來模擬網絡編程IO會帶來的問題
- 不懂Socket可以看我之前的文章,這個東西很容易懂的,就是基於TCP實現的網絡通信,比http要快,很多實現網絡通信的框架都是基於Socket來實現
21. 網絡操作IO編程演變歷史
21.1 BIO編程會出現什么問題?
package com.test.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
//TCP協議Socket使用BIO進行通信:服務端
public class BIOServer {
// 在main線程中執行下面這些代碼
public static void main(String[] args) {
//使用Socket進行網絡通信
ServerSocket server = null;
Socket socket = null;
//基於字節流
InputStream in = null;
OutputStream out = null;
try {
server = new ServerSocket(8000);
System.out.println("服務端啟動成功,監聽端口為8000,等待客戶端連接...");
while (true){
socket = server.accept(); //等待客戶端連接
System.out.println("客戶連接成功,客戶信息為:" +
socket.getRemoteSocketAddress());
in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
//讀取客戶端的數據
while ((len = in.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, len));
}
//向客戶端寫數據
out = socket.getOutputStream();
out.write("hello!".getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCP協議Socket使用BIO進行通信:客戶端(第二執行)
package com.test.io;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
//TCP協議Socket使用BIO進行通信:客戶端
public class Client01 {
public static void main(String[] args) throws IOException {
//創建套接字對象socket並封裝ip與port
Socket socket = new Socket("127.0.0.1", 8000);
//根據創建的socket對象獲得一個輸出流
//基於字節流
OutputStream outputStream = socket.getOutputStream();
//控制台輸入以IO的形式發送到服務器
System.out.println("TCP連接成功 \n請輸入:");
String str = new Scanner(System.in).nextLine();
byte[] car = str.getBytes();
outputStream.write(car);
System.out.println("TCP協議的Socket發送成功");
//刷新緩沖區
outputStream.flush();
//關閉連接
socket.close();
}
}
package com.test.io;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
//TCP協議Socket:客戶端
public class Client02 {
public static void main(String[] args) throws IOException {
//創建套接字對象socket並封裝ip與port
Socket socket = new Socket("127.0.0.1", 8000);
//根據創建的socket對象獲得一個輸出流
//基於字節流
OutputStream outputStream = socket.getOutputStream();
//控制台輸入以IO的形式發送到服務器
System.out.println("TCP連接成功 \n請輸入:");
String str = new Scanner(System.in).nextLine();
byte[] car = str.getBytes();
outputStream.write(car);
System.out.println("TCP協議的Socket發送成功");
//刷新緩沖區
outputStream.flush();
//關閉連接
socket.close();
}
}
- 為了解決堵塞問題,可以使用多線程,請看下面
21.2 多線程解決BIO編程會出現的問題
這時有人就會說,我多線程不就解決了嗎?
- 使用多線程是可以解決堵塞等待時間很長的問題,因為他可以充分發揮CPU
- 然而系統資源是有限的,不能過多的新建線程,線程過多帶來線程上下文的切換,從來帶來更大的性能損耗
package com.test.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
//TCP協議Socket使用多線程BIO進行通行:服務端
public class BIOThreadService {
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(8000);
System.out.println("服務端啟動成功,監聽端口為8000,等待客戶端連接... ");
while (true) {
Socket socket = server.accept();//等待客戶連接
System.out.println("客戶連接成功,客戶信息為:" +
socket.getRemoteSocketAddress());
//針對每個連接創建一個線程, 去處理I0操作
//創建多線程創建開始
Thread thread = new Thread(new Runnable() {
public void run() {
try {
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
//讀取客戶端的數據
while ((len = in.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, len));
}
//向客戶端寫數據
OutputStream out = socket.getOutputStream();
out.write("hello".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 為了解決線程太多,這時又來了,線程池
21.3 線程池解決多線程BIO編程會出現的問題
package com.test.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//TCP協議Socket使用線程池BIO進行通行:服務端
public class BIOThreadPoolService {
public static void main(String[] args) {
//創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(30);
try {
ServerSocket server = new ServerSocket(8000);
System.out.println("服務端啟動成功,監聽端口為8000,等待客戶端連接...");
while (true) {
Socket socket = server.accept();
//等待客戶連接
System.out.println("客戶連接成功,客戶信息為:" +
socket.getRemoteSocketAddress());
//使用線程池中的線程去執行每個對應的任務
executorService.execute(new Thread(new Runnable() {
public void run() {
try {
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
//讀取客戶端的數據
while ((len = in.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, len));
}
//向客戶端寫數據
OutputStream out = socket.getOutputStream();
out.write("hello".getBytes());
}
catch (IOException e) {
e.printStackTrace();
}
}
}
)
);
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
21.4 使用NIO實現網絡通信
package com.test.io;
import com.lijie.iob.RequestHandler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws IOException {
//111111111
//Service端的Channel,監聽端口的
ServerSocketChannel serverChannel = ServerSocketChannel.open();
//設置為非阻塞
serverChannel.configureBlocking(false);
//nio的api規定這樣賦值端口
serverChannel.bind(new InetSocketAddress(8000));
//顯示Channel是否已經啟動成功,包括綁定在哪個地址上
System.out.println("服務端啟動成功,監聽端口為8000,等待客戶端連接..."+
serverChannel.getLocalAddress());
//22222222
//聲明selector選擇器
Selector selector = Selector.open();
//這句話的含義,是把selector注冊到Channel上面,
//每個客戶端來了之后,就把客戶端注冊到Selector選擇器上,默認狀態是Accepted
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
//33333333
//創建buffer緩沖區,聲明大小是1024,底層使用數組來實現的
ByteBuffer buffer = ByteBuffer.allocate(1024);
RequestHandler requestHandler = new RequestHandler();
//444444444
//輪詢,服務端不斷輪詢,等待客戶端的連接
//如果有客戶端輪詢上來就取出對應的Channel,沒有就一直輪詢
while (true) {
int select = selector.select();
if (select == 0) {
continue;
}
//有可能有很多,使用Set保存Channel
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
//使用SelectionKey來獲取連接了客戶端和服務端的Channel
SelectionKey key = iterator.next();
//判斷SelectionKey中的Channel狀態如何,如果是OP_ACCEPT就進入
if (key.isAcceptable()) {
//從判斷SelectionKey中取出Channel
ServerSocketChannel channel = (ServerSocketChannel)
key.channel();
//拿到對應客戶端的Channel
SocketChannel clientChannel = channel.accept();
//把客戶端的Channel打印出來
System.out.println("客戶端通道信息打印:" + clientChannel.getRemoteAddress());
//設置客戶端的Channel設置為非阻塞
clientChannel.configureBlocking(false);
//操作完了改變SelectionKey中的Channel的狀態OP_READ
clientChannel.register(selector, SelectionKey.OP_READ);
}
//到此輪訓到的時候,發現狀態是read,開始進行數據交互
if (key.isReadable()) {
//以buffer作為數據橋梁
SocketChannel channel = (SocketChannel) key.channel();
//數據要想讀要先寫,必須先讀取到buffer里面進行操作
channel.read(buffer);
//進行讀取
String request = new String(buffer.array()).trim();
buffer.clear();
//進行打印buffer中的數據
System.out.println(String.format("客戶端發來的消息: %s : %s",
channel.getRemoteAddress(), request));
//要返回數據的話也要先返回buffer里面進行返回
String response = requestHandler.handle(request);
//然后返回出去
channel.write(ByteBuffer.wrap(response.getBytes()));
}
iterator.remove();
}
}
}
}
package com.test.io;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
//TCP協議Socket:客戶端
public class Client01 {
public static void main(String[] args) throws IOException {
//創建套接字對象socket並封裝ip與port
Socket socket = new Socket("127.0.0.1", 8000);
//根據創建的socket對象獲得一個輸出流
OutputStream outputStream = socket.getOutputStream();
//控制台輸入以IO的形式發送到服務器
System.out.println("TCP連接成功 n請輸入:");
while(true){
byte[] car = new Scanner(System.in).nextLine().getBytes();
outputStream.write(car);
System.out.println("TCP協議的Socket發送成功");
//刷新緩沖區
outputStream.flush();
}
}
}
21.5 使用Netty實現網絡通信
- Netty是由JBOSS提供的一個Java開源框架。Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。
- Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶,服務端應用。Netty相當簡化和流線化了網絡應用的編程開發過程,例如,TCP和UDP的Socket服務開發。