上一篇:Java 隊列詳解
IO 介紹
IO 是 Input/Output 的縮寫,它是基於流模型實現的,比如操作文件時使用輸入流和輸出流來寫入和讀取文件等。
IO 分類
傳統的 IO,按照流類型我們可以分爲:
-
字符流
-
字節流
其中,字符流包括 Reader、Writer;字節流包括 InputStream、OutputStream。
傳統 IO 的類關係圖,如下圖所示:
IO 使用
瞭解了 IO 之間的關係,下面我們正式進入實戰環節,分別來看字符流(Reader、Writer)和字節流(InputStream、OutputStream)的使用。
① Writer 使用
Writer 可用來寫入文件,請參考以下代碼:
// 給指定目錄下的文件追加信息Writer writer = new FileWriter("d:\\io.txt",true);
writer.append("老王");
writer.close();
這幾行簡單的代碼就可以實現把信息 老王
追加到 d:\\io.txt
的文件下,參數二表示的是覆蓋文字還是追加文字。
② Reader 使用
Reader 可用來讀取文件,請參考以下代碼:
Reader reader = new FileReader("d:\\io.txt");
BufferedReader bufferedReader = new BufferedReader(reader);
String str = null;
// 逐行讀取信息
while (null != (str = bufferedReader.readLine())) {
System.out.println(str);
}
bufferedReader.close();
reader.close();
③ InputStream 使用
InputStream 可用來讀取文件,請參考以下代碼:
InputStream inputStream = new FileInputStream(new File("d:\\io.txt"));
byte[] bytes = new byte[inputStream.available()];
// 讀取到 byte 數組
inputStream.read(bytes);
// 內容轉換爲字符串
String content = new String(bytes, "UTF-8");
inputStream.close();
④ OutputStream 使用
OutputStream 可用來寫入文件,請參考以下代碼:
OutputStream outputStream = new FileOutputStream(new File("d:\\io.txt"),true);
outputStream.write("老王".getBytes());
outputStream.close();
NIO 介紹
上面講的內容都是 java.io 包下的知識點,但隨着 Java 的不斷髮展,在 Java 1.4 時新的 IO 包出現了 java.nio,NIO(Non-Blocking IO)的出現解決了傳統 IO,也就是我們經常說的 BIO(Blocking IO)同步阻塞的問題,NIO 提供了 Channel、Selector 和 Buffer 等概念,可以實現多路複用和同步非阻塞 IO 操作,從而大大提升了 IO 操作的性能。
前面提到同步和阻塞的問題,那下面來看看同步和阻塞結合都有哪些含義。
組合方式 | 性能分析 |
---|---|
同步阻塞 | 最常用的一種用法,使用也是最簡單的,但是 I/O 性能一般很差,CPU 大部分在空閒狀態 |
同步非阻塞 | 提升 I/O 性能的常用手段,就是將 I/O 的阻塞改成非阻塞方式,尤其在網絡 I/O 是長連接,同時傳輸數據也不是很多的情況下,提升性能非常有效。 這種方式通常能提升 I/O 性能,但是會增加 CPU 消耗,要考慮增加的 I/O 性能能不能補償 CPU 的消耗,也就是系統的瓶頸是在 I/O 還是在 CPU 上 |
異步阻塞 | 這種方式在分佈式數據庫中經常用到。例如,在往一個分佈式數據庫中寫一條記錄,通常會有一份是同步阻塞的記錄,而還有兩至三份是備份記錄會寫到其他機器上,這些備份記錄通常都是採用異步阻塞的方式寫 I/O;異步阻塞對網絡 I/O 能夠提升效率,尤其像上面這種同時寫多份相同數據的情況 |
異步非阻塞 | 這種組合方式用起來比較複雜,只有在一些非常複雜的分佈式情況下使用,像集羣之間的消息同步機制一般用這種 I/O 組合方式。例如,Cassandra 的 Gossip 通信機制就是採用異步非阻塞的方式。它適合同時要傳多份相同的數據到集羣中不同的機器,同時數據的傳輸量雖然不大,但是卻非常頻繁。這種網絡 I/O 用這個方式性能能達到最高 |
瞭解了同步和阻塞的含義,下面來看 NIO 的具體使用,請參考以下代碼:
int port = 6666;
new Thread(new Runnable() {
@Override
public void run() {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {
serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), port));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 阻塞等待就緒的 Channel
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
try (SocketChannel channel = ((ServerSocketChannel) key.channel()).accept()) {
channel.write(Charset.defaultCharset().encode("老王,你好~"));
}
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// Socket 客戶端 1(接收信息並打印)
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
bufferedReader.lines().forEach(s -> System.out.println("客戶端 1 打印:" + s));
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// Socket 客戶端 2(接收信息並打印)
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
bufferedReader.lines().forEach(s -> System.out.println("客戶端 2 打印:" + s));
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
以上代碼創建了兩個 Socket 客戶端,用於收取和打印服務器端的消息。
其中,服務器端通過 SelectionKey(選擇鍵)獲取到 SocketChannel(通道),而通道都註冊到 Selector(選擇器)上,所有的客戶端都可以獲得對應的通道,而不是所有客戶端都排隊堵塞等待一個服務器連接,這樣就實現多路複用的效果了。多路指的是多個通道(SocketChannel),而複用指的是一個服務器端連接重複被不同的客戶端使用。
AIO 介紹
AIO(Asynchronous IO)是 NIO 的升級,也叫 NIO2,實現了異步非堵塞 IO ,異步 IO 的操作基於事件和回調機制。
AIO 實現簡單的 Socket 服務器,代碼如下:
int port = 8888;
new Thread(new Runnable() {
@Override
public void run() {
AsynchronousChannelGroup group = null;
try {
group = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(4));
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(InetAddress.getLocalHost(), port));
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel>() {
@Override
public void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel attachment) {
server.accept(null, this); // 接收下一個請求
try {
Future<Integer> f = result.write(Charset.defaultCharset().encode("Hi, 老王"));
f.get();
System.out.println("服務端發送時間:" + DateFormat.getDateTimeInstance().format(new Date()));
result.close();
} catch (InterruptedException | ExecutionException | IOException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) {
}
});
group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// Socket 客戶端
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
Future<Void> future = client.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
future.get();
ByteBuffer buffer = ByteBuffer.allocate(100);
client.read(buffer, null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer result, Void attachment) {
System.out.println("客戶端打印:" + new String(buffer.array()));
}
@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
Thread.sleep(10 * 1000);
相關面試題
1.使用以下哪個方法來判斷一個文件是否存在?
A:createFile
B:exists
C:read
D:exist
答:B
2.以下說法錯誤的是?
A:同步操作不一定會阻塞
B:異步操作不一定會阻塞
C:阻塞一定是同步操作
D:同步或異步都可能會阻塞
答:C
題目解析:異步操作也可能會阻塞,比如分佈式集羣消息同步,採用的就是異步阻塞的方式。
3.BIO、NIO、AIO 的區別是什麼?
答:它們三者的區別如下。
-
BIO 就是傳統的 java.io 包,它是基於流模型實現的,交互的方式是同步、阻塞方式,也就是說在讀入輸入流或者輸出流時,在讀寫動作完成之前,線程會一直阻塞在那裏,它們之間的調用是可靠的線性順序。它的優點就是代碼比較簡單、直觀;缺點就是 IO 的效率和擴展性很低,容易成爲應用性能瓶頸。
-
NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以構建多路複用的、同步非阻塞 IO 程序,同時提供了更接近操作系統底層高性能的數據操作方式。
-
AIO 是 Java 1.7 之後引入的包,是 NIO 的升級版本,提供了異步非堵塞的 IO 操作方式,因此人們叫它 AIO(Asynchronous IO),異步 IO 是基於事件和回調機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裏,當後臺處理完成,操作系統會通知相應的線程進行後續的操作。
簡單來說 BIO 就是傳統 IO 包,產生的最早;NIO 是對 BIO 的改進提供了多路複用的同步非阻塞 IO,而 AIO 是 NIO 的升級,提供了異步非阻塞 IO。
4.讀取和寫入文件最簡潔的方式是什麼?
答:使用 Java 7 提供的 Files 讀取和寫入文件是最簡潔,請參考以下代碼:
// 讀取文件
byte[] bytes = Files.readAllBytes(Paths.get("d:\\io.txt"));
// 寫入文件
Files.write(Paths.get("d:\\io.txt"), "追加內容".getBytes(), StandardOpenOption.APPEND);
讀取和寫入都是一行代碼搞定,可以說很簡潔了。
5.Files 常用方法都有哪些?
答:Files 是 Java 1.7 提供的,使得文件和文件夾的操作更加方便,它的常用方法有以下幾個:
-
Files. exists():檢測文件路徑是否存在
-
Files. createFile():創建文件
-
Files. createDirectory():創建文件夾
-
Files. delete():刪除一個文件或目錄
-
Files. copy():複製文件
-
Files. move():移動文件
-
Files. size():查看文件個數
-
Files. read():讀取文件
-
Files. write():寫入文件
6.FileInputStream 可以實現什麼功能?
答:FileInputStream 可以實現文件的讀取。
題目解析:因爲 FileInputStream 和 FileOutputStream 很容易被記反,FileOutputStream 纔是用來寫入文件的,所以也經常被面試官問到。
7.不定項選擇:爲了提高讀寫性能,可以採用什麼流?
A:InputStream
B:DataInputStream
C:BufferedReader
D:BufferedInputStream
E:OutputStream
F:BufferedOutputStream
答:D、F
題目解析:BufferedInputStream 是一種帶緩存區的輸入流,在讀取字節數據時可以從底層流中一次性讀取多個字節到緩存區,而不必每次都調用系統底層;同理,BufferedOutputStream 也是一種帶緩衝區的輸出流,通過緩衝區輸出流,應用程序先把字節寫入緩衝區,緩存區滿後再調用操作系統底層,從而提高系統性能,而不必每次都去調用系統底層方法。
8.FileInputStream 和 BufferedInputStream 的區別是什麼?
答:FileInputStream 在小文件讀寫時性能較好,而在大文件操作時使用 BufferedInputStream 更有優勢。
9.以下這段代碼運行在 Windwos 平臺,執行的結果是?
Files.createFile(Paths.get("c:\\pf.txt"), PosixFilePermissions.asFileAttribute( EnumSet.of(PosixFilePermission.OWNER_READ)));
A:在指定的盤符產生了對應的文件,文件只讀
B:在指定的盤符產生了對應的文件,文件只寫
C:在指定的盤符產生了對應的文件,文件可讀寫
D:程序報錯
答:D
題目解析:本題目考察的是 Files.createFile 參數傳遞的問題,PosixFilePermissions 不支持 Windows,因此在 Windows 執行會報錯 java.lang.UnsupportedOperationException: 'posix:permissions' not supported as initial attribute。
總結
在 Java 1.4 之前只有 BIO(Blocking IO)可供使用,也就是 java.io 包下的那些類,它的缺點是同步阻塞式運行的。隨後在 Java 1.4 時,提供了 NIO(Non-Blocking IO)屬於 BIO 的升級,提供了同步非阻塞的 IO 操作方式,它的重要組件是 Selector(選擇器)、Channel(通道)、Buffer(高效數據容器)實現了多路複用的高效 IO 操作。而 AIO(Asynchronous IO)也叫 NIO 2.0,屬於 NIO 的補充和升級,提供了異步非阻塞的 IO 操作。
還有另一個重要的知識點,是 Java 7.0 時新增的 Files 類,極大地提升了文件操作的便利性,比如讀、寫文件 Files.write()、Files.readAllBytes() 等,都是非常簡便和實用的方法。
下一篇:Java 反射和動態代理
在公衆號菜單中可自行獲取專屬架構視頻資料,包括不限於 java架構、python系列、人工智能系列、架構系列,以及最新面試、小程序、大前端均無私奉獻,你會感謝我的哈
往期精選