字節流與字符流
項 | 字節流 | 字符流 |
操作基本單元 | 字節 | 字符(Unicode碼元) |
是否使用緩衝 | 否 |
是。 若一個程序頻繁對一個資源進行IO操作,效率會非常低。此時,通過緩衝區,先把需要操作的數據暫時放入內存中,以後直接從內存中讀取數據,則可以避免多次的IO操作,提高效率 |
存在位置 |
可存在於文件、內存中。 硬盤上的所有文件都是以字節形式存在的。 |
只存在於內存中 |
使用場景 | 適合操作文本文件之外的文件。例:圖片、音頻、視頻。 | 適合操作文本文件時使用。(效率高。因爲有緩存) |
BIO、NIO、AIO
其他網址
項 | BIO (Block IO) | NIO (New IO) | AIO(Asynchronous I/O) |
JDK版本 | 所有版本 | JDK1.4及之後 | JDK1.7及之後 |
異步/阻塞 |
同步阻塞。 一個連接一個線程,線程發起IO請求,不管內核是否準備好IO操作,從發起請求起,線程一直阻塞,直到操作完成。 數據的讀取寫入必須阻塞在一個線程內等待其完成。 |
同步阻塞/非阻塞。 一個請求一個線程。客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O請求時才啓動一個線程進行處理。用戶進程也需要時不時的詢問IO操作是否就緒,這要求用戶進程不停的去詢問。 |
異步非阻塞。 一個有效請求一個線程。用戶進程只需要發起一個IO操作然後立即返回,等IO操作真正的完成以後,應用程序會得到IO操作完成的通知,此時用戶進程只需要對數據進行處理就好了,不需要進行實際的IO讀寫操作,因爲真正的IO讀取或者寫入操作已經由內核完成了。 |
使用場景 |
適用於連接數目多且連接比較短(輕操作)的操作。例如:聊天服務器。 |
適用於連接數目多且連接比較長(重操作)的架構。例如:相冊服務器。 |
BIO
實例
客戶端
public class IOClient {
public static void main(String[] args) {
// TODO 創建多個線程,模擬多個客戶端連接服務端
new Thread(() -> {
try {
Socket socket = new Socket("127.0.0.1", 3333);
while (true) {
try {
socket.getOutputStream().write((new Date() + ": hello world").getBytes());
Thread.sleep(2000);
} catch (Exception e) {
}
}
} catch (IOException e) {
}
}).start();
}
}
服務端
public class IOServer {
public static void main(String[] args) throws IOException {
// TODO 服務端處理客戶端連接請求
ServerSocket serverSocket = new ServerSocket(3333);
// 接收到客戶端連接請求之後爲每個客戶端創建一個新的線程進行鏈路處理
new Thread(() -> {
while (true) {
try {
// 阻塞方法獲取新的連接
Socket socket = serverSocket.accept();
// 每一個新的連接都創建一個線程,負責讀取數據
new Thread(() -> {
try {
int len;
byte[] data = new byte[1024];
InputStream inputStream = socket.getInputStream();
// 按字節流方式讀取數據
while ((len = inputStream.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
} catch (IOException e) {
}
}).start();
} catch (IOException e) {
}
}
}).start();
}
}
NIO
其他網址
簡介
JAVA NIO:新的IO(New I/O),其實是同一個概念。它是一種同步阻塞/非阻塞的I/O模型,也是I/O多路複用的基礎,已經被越來越多地應用到大型應用服務器,成爲解決高併發與大量連接、I/O處理問題的有效方式。
NIO是一種基於通道和緩衝區的I/O方式,它可以使用Native函數庫直接分配堆外內存(區別於JVM的運行時數據區),然後通過一個存儲在java堆裏面的DirectByteBuffer對象作爲這塊內存的直接引用進行操作。這樣能在一些場景顯著提高性能,因爲避免了在Java堆和Native堆中來回複製數據。
實例
BIO部分的客戶端 IOClient.java 的代碼不變,我們對服務端使用 NIO 進行改造。
package org.example.a;
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws IOException {
// 1. serverSelector負責輪詢是否有新的連接,服務端監測到新的連接之後,不再創建一個新的線程,
// 而是直接將新連接綁定到clientSelector上,這樣就不用 IO 模型中 1w 個 while 循環在死等
Selector serverSelector = Selector.open();
// 2. clientSelector負責輪詢連接是否有數據可讀
Selector clientSelector = Selector.open();
new Thread(() -> {
try {
// 對應IO編程中服務端啓動
ServerSocketChannel listenerChannel = ServerSocketChannel.open();
listenerChannel.socket().bind(new InetSocketAddress(3333));
listenerChannel.configureBlocking(false);
listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
while (true) {
// 監測是否有新的連接,這裏的1指的是阻塞的時間爲 1ms
if (serverSelector.select(1) > 0) {
Set<SelectionKey> set = serverSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = set.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
try {
// (1)每來一個新連接,不需要創建一個線程,而是直接註冊到clientSelector
SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
clientChannel.configureBlocking(false);
clientChannel.register(clientSelector, SelectionKey.OP_READ);
} finally {
keyIterator.remove();
}
}
}
}
}
} catch (IOException ignored) {
}
}).start();
new Thread(() -> {
try {
while (true) {
// (2) 批量輪詢是否有哪些連接有數據可讀,這裏的1指的是阻塞的時間爲 1ms
if (clientSelector.select(1) > 0) {
Set<SelectionKey> set = clientSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = set.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isReadable()) {
try {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// (3) 面向 Buffer
clientChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println(
Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());
} finally {
keyIterator.remove();
key.interestOps(SelectionKey.OP_READ);
}
}
}
}
}
} catch (IOException ignored) {
}
}).start();
}
}
AIO
簡介
AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型。異步 IO 是基於事件和回調機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裏,當後臺處理完成,操作系統會通知相應的線程進行後續的操作。
目前來說 AIO 的應用還不是很廣泛,Netty 之前也嘗試使用過 AIO,不過又放棄了。