Java NIO(New Input/Output)是Java 1.4引入的一套新的I/O API,提供了与标准Java I/O不同的I/O处理方式。它支持面向缓冲区的、基于通道的I/O操作,提供了更高的性能和更灵活的I/O操作方式。
Java NIO的核心组件包括:
通道(Channel)是Java NIO中的一个抽象类,用于数据的读写操作。它类似于流,但支持双向读写,并且可以与缓冲区配合使用。常见的通道实现包括FileChannel、SocketChannel、ServerSocketChannel和DatagramChannel。
缓冲区(Buffer)是Java NIO中用于存储数据的容器。它是一个线性、有限的数据集合,可以存储各种数据类型(如字节、字符、整数等)。缓冲区的主要属性包括容量(Capacity)、位置(Position)、限制(Limit)和标记(Mark)。
选择器(Selector)是Java NIO中的一个类,用于实现多路复用I/O。它允许一个线程监控多个通道的I/O事件(如读、写、连接等),从而在一个线程中处理多个通道的I/O操作,提高了并发性能。
缓冲区(Buffer)的基本属性包括:
可以通过Buffer的静态方法创建不同类型的缓冲区,例如:
// 创建一个容量为1024字节的字节缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 创建一个容量为1024字符的字符缓冲区
CharBuffer charBuffer = CharBuffer.allocate(1024);
Java NIO提供了多种类型的缓冲区,包括:
通过put()
方法将数据写入Buffer,例如:
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put((byte)1); // 写入一个字节
buffer.put(new byte[]{1, 2, 3}); // 写入字节数组
通过get()
方法从Buffer读取数据,例如:
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put((byte)1);
buffer.flip(); // 切换为读模式
byte b = buffer.get(); // 读取一个字节
byte[] dst = new byte[3];
buffer.get(dst); // 读取字节数组
flip()
方法将Buffer从写模式切换为读模式,具体操作:
limit
设置为当前position
。position
重置为0。clear()
方法清空Buffer,准备写入数据,具体操作:
position
重置为0。limit
设置为capacity
。clear()
不会清空数据,只是重置指针。rewind()
方法将position
重置为0,mark
被丢弃,用于重新读取Buffer中的数据。
compact()
方法将未读的数据移动到Buffer的开头,并将position
设置为未读数据的末尾,limit
设置为capacity
,用于保留未读数据并准备写入新数据。
Channel是Java NIO中用于数据传输的抽象,类似于流,但支持双向读写,可以与Buffer配合使用。Channel是双向的,可以从Channel读取数据到Buffer,也可以从Buffer写入数据到Channel。
常见的Channel类型包括:
通过FileInputStream
、FileOutputStream
或RandomAccessFile
获取FileChannel:
FileChannel channel = new FileInputStream("file.txt").getChannel();
FileChannel channel = new FileInputStream("file.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer); // 读取数据到Buffer
buffer.flip(); // 切换为读模式
// 处理Buffer中的数据
channel.close();
FileChannel channel = new FileOutputStream("file.txt").getChannel();
ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes());
channel.write(buffer); // 写入数据到文件
channel.close();
SocketChannel是Java NIO中用于TCP网络通信的Channel,支持非阻塞模式,可以通过SocketChannel.open()
创建或通过ServerSocketChannel.accept()
接受连接。
SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress("localhost", 8080));
SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress("localhost", 8080));
ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes());
channel.write(buffer); // 发送数据
buffer.clear();
channel.read(buffer); // 接收数据
buffer.flip();
// 处理接收到的数据
channel.close();
ServerSocketChannel是Java NIO中用于监听TCP连接的Channel,支持非阻塞模式,通过ServerSocketChannel.open()
创建,并调用bind()
绑定端口。
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
SocketChannel clientChannel = serverChannel.accept(); // 阻塞等待客户端连接
// 处理客户端连接
clientChannel.close();
serverChannel.close();
Java NIO的文件锁(FileLock)是一种机制,用于控制多个进程或线程对同一文件的并发访问。文件锁可以防止多个进程同时修改同一个文件,从而避免数据不一致的问题。
文件锁的主要作用是提供对文件的互斥访问,确保在同一时间只有一个进程或线程可以对文件的特定部分进行读写操作,从而保证数据的完整性和一致性。
可以通过FileChannel的tryLock()
或lock()
方法获取文件锁:
FileChannel channel = FileChannel.open(Paths.get("file.txt"), StandardOpenOption.WRITE);
FileLock lock = channel.tryLock(); // 尝试获取锁
if (lock != null) {
// 操作文件
lock.release(); // 释放锁
}
channel.close();
文件锁分为两种类型:
内存映射文件(Memory-mapped File)是一种将文件直接映射到内存的技术,通过将文件映射到虚拟内存空间,可以直接通过内存操作来读写文件,从而提高I/O性能。
内存映射文件的主要作用是提高大文件的读写性能,减少用户空间和内核空间之间的数据拷贝,适用于需要频繁访问大文件的场景。
可以通过FileChannel的map()
方法创建内存映射文件:
FileChannel channel = FileChannel.open(Paths.get("file.txt"), StandardOpenOption.READ);
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
// 通过buffer访问文件内容
channel.close();
内存映射文件的性能优势包括:
Java NIO的管道(Pipe)是一种用于线程间单向数据传输的机制,由SourceChannel
和SinkChannel
组成,数据只能从SinkChannel
写入,从SourceChannel
读取。
管道的主要作用是实现线程间的单向数据通信,适用于生产者-消费者模式或其他需要线程间数据传递的场景。
可以通过Pipe.open()
方法创建管道:
Pipe pipe = Pipe.open();
Pipe.SinkChannel sinkChannel = pipe.sink(); // 写入通道
Pipe.SourceChannel sourceChannel = pipe.source(); // 读取通道
Pipe pipe = Pipe.open();
Pipe.SinkChannel sinkChannel = pipe.sink();
Pipe.SourceChannel sourceChannel = pipe.source();
// 线程1:写入数据
new Thread(() -> {
ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes());
sinkChannel.write(buffer);
}).start();
// 线程2:读取数据
new Thread(() -> {
ByteBuffer buffer = ByteBuffer.allocate(1024);
sourceChannel.read(buffer);
buffer.flip();
// 处理读取到的数据
}).start();
零拷贝技术是一种减少数据在内存中拷贝次数的技术,通过直接将数据从文件或网络传输到目标缓冲区,避免了内核空间和用户空间之间的数据拷贝。
零拷贝技术的主要作用是提高数据传输效率,减少CPU和内存的开销,适用于大文件传输或网络通信场景。
可以通过FileChannel
的transferTo()
或transferFrom()
方法实现零拷贝:
FileChannel sourceChannel = new FileInputStream("source.txt").getChannel();
FileChannel targetChannel = new FileOutputStream("target.txt").getChannel();
sourceChannel.transferTo(0, sourceChannel.size(), targetChannel);
Scatter/Gather是一种分散读和集中写的I/O操作方式:
Scatter/Gather的作用是提高I/O操作的灵活性,适用于需要将数据分散到多个缓冲区或从多个缓冲区集中写入的场景。
FileChannel channel = FileChannel.open(Paths.get("file.txt"), StandardOpenOption.READ);
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {header, body};
channel.read(buffers); // Scatter读取
// 处理数据后写入
channel.write(buffers); // Gather写入
字符集(Charset)是Java NIO中用于字符编码和解码的机制,定义了字符与字节之间的映射关系。
字符集的主要作用是实现文本数据的编码和解码,确保不同平台或系统之间的文本数据能够正确传输和显示。
Charset charset = Charset.forName("UTF-8");
ByteBuffer byteBuffer = charset.encode("Hello"); // 编码
CharBuffer charBuffer = charset.decode(byteBuffer); // 解码
常见的Charset实现包括:
Path是Java NIO中用于表示文件系统路径的接口,替代了传统的File
类,提供了更丰富的路径操作方法。
Path的主要作用是表示和操作文件系统路径,支持路径的拼接、解析、规范化等操作。
Path path = Paths.get("file.txt");
Files.readAllBytes(path); // 读取文件
Files.write(path, "Hello".getBytes()); // 写入文件
Files类的常用方法包括:
readAllBytes(Path)
:读取文件所有字节。write(Path, byte[])
:写入字节到文件。copy(Path, Path)
:复制文件。delete(Path)
:删除文件。exists(Path)
:检查文件是否存在。Java NIO的零拷贝技术是一种优化数据传输的方式,它允许数据直接在内存和I/O设备之间传输,而无需经过应用程序缓冲区的多次拷贝。这种技术可以显著提高数据传输效率,减少CPU和内存的开销。
零拷贝技术的主要作用是减少数据在内存中的拷贝次数,从而提高数据传输速度,降低CPU使用率,并减少内存带宽的占用。它特别适用于大文件传输和网络通信等需要高效数据传输的场景。
在Java NIO中,可以通过FileChannel
的transferTo()
和transferFrom()
方法实现零拷贝。例如,将一个文件的内容直接传输到另一个文件或网络通道:
FileChannel sourceChannel = new FileInputStream("source.txt").getChannel();
FileChannel targetChannel = new FileOutputStream("target.txt").getChannel();
sourceChannel.transferTo(0, sourceChannel.size(), targetChannel);
Scatter/Gather是Java NIO中的一种I/O操作方式,允许数据分散地从多个缓冲区读取(Scatter)或集中地写入到多个缓冲区(Gather)。这种方式可以提高数据处理的灵活性和效率。
Scatter/Gather的作用是简化复杂的数据处理流程,特别是在需要将数据分散到多个缓冲区或从多个缓冲区集中读取的场景中。它可以提高数据传输的效率,并减少编程复杂性。
使用FileChannel
的read()
和write()
方法,传入一个ByteBuffer
数组即可实现Scatter/Gather操作。例如:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {header, body};
fileChannel.read(buffers); // Scatter读取
// 处理数据后
fileChannel.write(buffers); // Gather写入
Java NIO的字符集(Charset)是用于字符编码和解码的机制,它定义了字符与字节之间的映射关系。字符集确保了文本数据在不同系统和平台之间的正确传输和显示。
字符集的主要作用是实现文本数据的编码和解码,支持多种字符编码标准(如UTF-8、ISO-8859-1等),确保文本数据在不同环境中的兼容性和一致性。
使用Charset
类的encode()
和decode()
方法可以进行字符编码和解码。例如:
Charset charset = Charset.forName("UTF-8");
ByteBuffer byteBuffer = charset.encode("你好"); // 编码为字节
CharBuffer charBuffer = charset.decode(byteBuffer); // 解码为字符
常见的字符集实现包括:
Java NIO的Path
接口是用于表示文件系统路径的抽象,它提供了比传统File
类更丰富的路径操作功能,如路径拼接、解析、规范化等。
Path
的作用是提供一种更现代、更灵活的方式来处理文件系统路径,支持跨平台操作,并简化了路径操作的代码编写。
通过Files
工具类结合Path
可以方便地进行文件操作,例如:
Path path = Paths.get("example.txt");
Files.createFile(path); // 创建文件
Files.write(path, "Hello".getBytes()); // 写入内容
List<String> lines = Files.readAllLines(path); // 读取内容
Files
类的常用方法包括:
createFile(Path)
:创建文件。write(Path, byte[])
:写入字节到文件。readAllBytes(Path)
:读取文件所有字节。copy(Path, Path)
:复制文件。delete(Path)
:删除文件。exists(Path)
:检查文件是否存在。WatchService
是Java NIO提供的一个用于监控文件系统变化的机制,可以监听目录中的文件创建、修改、删除等事件。
WatchService
的作用是实时监控文件系统的变化,当指定目录中的文件发生修改时,能够及时通知应用程序,适用于需要响应文件变化的场景。
使用WatchService
的步骤如下:
WatchService
实例。WatchService watchService = FileSystems.getDefault().newWatchService();
Path dir = Paths.get("path/to/dir");
dir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
// 处理事件
}
key.reset();
}
AsynchronousChannel
是Java NIO中支持异步I/O操作的通道接口,允许应用程序在I/O操作完成时得到通知,而无需阻塞等待。
AsynchronousChannel
的作用是提供非阻塞的I/O操作,提高应用程序的并发性能和响应速度,特别适用于高并发的网络通信和文件操作。
通过AsynchronousFileChannel
、AsynchronousSocketChannel
等实现类进行异步I/O操作,例如:
AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("file.txt"));
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
// 读取完成后的处理
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
// 读取失败的处理
}
});
AsynchronousFileChannel
是Java NIO中用于异步文件I/O操作的通道类,支持在后台线程中执行文件读写操作,并通过回调机制通知操作结果。
使用AsynchronousFileChannel
的read()
和write()
方法,并传入CompletionHandler
来处理异步操作的结果,例如:
AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("file.txt"));
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
// 处理读取的数据
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
// 处理错误
}
});
AsynchronousSocketChannel
是Java NIO中用于异步网络通信的通道类,支持非阻塞的TCP连接和数据传输。
通过AsynchronousSocketChannel
的connect()
、read()
和write()
方法进行异步网络通信,例如:
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
channel.connect(new InetSocketAddress("localhost", 8080), null, new CompletionHandler<Void, Void>() {
@Override
public void completed(Void result, Void attachment) {
// 连接成功后的操作
}
@Override
public void failed(Throwable exc, Void attachment) {
// 连接失败的处理
}
});
AsynchronousServerSocketChannel
是Java NIO中用于异步监听和接受TCP连接的通道类,支持高并发的网络服务器实现。
通过AsynchronousServerSocketChannel
的bind()
和accept()
方法实现异步服务器,例如:
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel client, Void attachment) {
// 处理客户端连接
server.accept(null, this); // 继续接受新连接
}
@Override
public void failed(Throwable exc, Void attachment) {
// 处理错误
}
});
Java NIO的API设计更加面向对象和灵活,使用通道(Channel)和缓冲区(Buffer)进行数据传输,支持非阻塞I/O和选择器(Selector)实现多路复用。而传统IO的API设计较为简单,基于流(Stream)模型,仅支持阻塞I/O。
Java NIO的线程模型支持单线程管理多个通道(通过Selector),适用于高并发场景。通过事件驱动的方式,一个线程可以处理多个I/O操作,减少了线程切换的开销。
在使用Java NIO进行多线程编程时,需要注意:
在Java NIO中,异常处理通常通过try-catch
块捕获I/O操作抛出的异常(如IOException
),并在回调方法(如CompletionHandler
的failed()
方法)中处理异步操作的错误。例如:
try {
FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
// 操作channel
} catch (IOException e) {
// 处理异常
}