java.io 包下的類有哪些 + 面試題
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 操作的性能。
了解了同步和阻塞的含義,下面來看 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” ,將會有大禮相送!!! 祝各位面試成功!!!
%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.png)