探秘 Java IO 与 NIO:春招面试知识要点
在前文中,我们深入探讨了 Java 多线程与并发相关知识,这对于提升程序性能和处理复杂业务场景至关重要。而在 Java 开发中,输入输出(IO)操作同样不可或缺,无论是读取文件、网络通信还是与数据库交互,都离不开 IO。随着技术发展,Java 又引入了 NIO(New IO),它提供了更高效的 IO 处理方式。在春招面试中,Java IO 与 NIO 也是面试官重点关注的内容,下面让我们一起深入了解。
一、Java IO
Java IO 是传统的输入输出库,它基于流(Stream)的概念,提供了一套丰富的类和接口来处理数据的输入输出。流可以分为字节流和字符流,字节流以字节为单位处理数据,字符流以字符为单位处理数据,更适合处理文本数据。
字节流
字节流主要包括InputStream和OutputStream两个抽象类,以及它们的各种实现类。例如FileInputStream用于从文件中读取字节数据,FileOutputStream用于将字节数据写入文件。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
int data;
while ((data = fis.read())!= -1) {
fos.write(data);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码实现了从input.txt文件读取字节数据,并将其写入output.txt文件的功能。read()方法每次读取一个字节,返回值为读取到的字节数据,当读取到文件末尾时返回 - 1。write()方法将指定字节写入输出流。
字符流
字符流主要包括Reader和Writer两个抽象类及其实现类。例如FileReader用于读取文件中的字符数据,FileWriter用于将字符数据写入文件。为了提高读写效率,通常会使用BufferedReader和BufferedWriter进行缓冲处理。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CharacterStreamExample {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = br.readLine())!= null) {
bw.write(line);
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这段代码使用BufferedReader逐行读取input.txt文件中的内容,然后通过BufferedWriter将读取到的内容写入output.txt文件,readLine()方法用于读取一行字符数据,write()方法写入字符串,newLine()方法写入换行符。
面试题 1:字节流和字符流的区别是什么?
答案:
- 处理数据单位:字节流以字节(8 位)为单位处理数据,适用于处理所有类型的数据,如图片、音频、视频等二进制数据;字符流以字符为单位处理数据,一个字符根据编码方式可能占用 1 - 4 个字节,主要用于处理文本数据。
- 处理方式:字节流直接操作字节,而字符流在操作字符时,会根据指定的字符编码进行字符与字节的转换,例如 UTF - 8、GBK 等。
- 类继承体系:字节流继承自InputStream和OutputStream;字符流继承自Reader和Writer。
二、Java NIO
Java NIO 是 Java 1.4 引入的新 IO 库,它提供了一种基于通道(Channel)和缓冲区(Buffer)的 IO 操作方式,与传统 IO 的流方式不同。NIO 还引入了选择器(Selector),支持非阻塞 IO 操作,大大提高了 IO 效率,尤其适用于高并发场景。
通道和缓冲区
通道是一种双向的、可以进行读写操作的对象,它类似于流,但功能更强大。常见的通道有FileChannel(用于文件 IO)、SocketChannel(用于 TCP 套接字 IO)、DatagramChannel(用于 UDP 套接字 IO)等。缓冲区是一个用于存储数据的容器,所有数据都要先写入缓冲区,然后再通过通道进行传输。常用的缓冲区有ByteBuffer、CharBuffer、IntBuffer等。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (inChannel.read(buffer)!= -1) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
上述代码使用FileChannel和ByteBuffer实现了文件的复制操作。read()方法将数据从通道读取到缓冲区,flip()方法将缓冲区从写模式切换到读模式,write()方法将缓冲区中的数据写入通道,clear()方法将缓冲区清空,准备下一次写入。
选择器
选择器是 NIO 的核心组件之一,它允许一个线程管理多个通道,实现非阻塞的 IO 操作。通过将通道注册到选择器上,并监听通道上的事件(如连接就绪、读就绪、写就绪等),当感兴趣的事件发生时,选择器会通知线程进行相应处理。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class SelectorExample {
public static void main(String[] args) {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = sc.read(buffer);
if (bytesRead > 0) {
buffer.flip();
// 处理读取到的数据
buffer.clear();
}
}
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这段代码创建了一个基于选择器的服务器,监听 8080 端口。当有客户端连接时,将客户端的SocketChannel注册到选择器上,并监听读事件。当有数据可读时,读取数据并进行处理。
面试题 2:NIO 和传统 IO 的区别是什么?
答案:
- IO 模型:传统 IO 是阻塞式 IO,即当进行读写操作时,线程会一直阻塞直到操作完成;NIO 是非阻塞式 IO,线程在进行读写操作时,如果数据未准备好,不会阻塞,而是返回一个状态值,线程可以继续执行其他任务,通过选择器可以监听多个通道的状态,实现单线程管理多个 IO 操作。
- 数据处理方式:传统 IO 基于流,数据是顺序读写的;NIO 基于通道和缓冲区,数据先写入缓冲区,再通过通道进行传输,缓冲区提供了更灵活的数据处理方式,如标记、重置等操作。
- 适用场景:传统 IO 适用于简单的、对性能要求不高的 IO 操作;NIO 适用于高并发、对性能要求较高的场景,如网络服务器开发。
深入理解 Java IO 与 NIO,能让你在春招面试中展现扎实的技术功底。下一篇,我们将走进 Java 反射机制的世界,继续为你的面试备考添砖加瓦。