5.1、I/O基础
5.1.1、Java有几种文件拷贝方式,哪一种效率最高?
答:
以下是三种不同方式的Java代码示例:
- 使用字节流进行传输:可以使用FileInputStream和FileOutputStream类来完成文件的读取和写入,逐字节地拷贝文件内容。这种方式比较简单,但效率较低,特别是对于大文件而言。
示例:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamCopy {
public static void main(String[] args) {
String sourceFile = "source.txt";
String destFile = "dest.txt";
try (FileInputStream fis = new FileInputStream(sourceFile);
FileOutputStream fos = new FileOutputStream(destFile)) {
int byteData;
while ((byteData = fis.read()) != -1) {
fos.write(byteData);
}
System.out.println("文件拷贝完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 使用缓冲区进行传输:可以使用BufferedInputStream和BufferedOutputStream类,结合字节数组进行文件的读取和写入操作。这种方式比较高效,因为可以减少实际的I/O操作次数。
示例:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedStreamCopy {
public static void main(String[] args) {
String sourceFile = "source.txt";
String destFile = "dest.txt";
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
System.out.println("文件拷贝完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 使用NIO进行传输:可以使用java.nio包中的Channel和Buffer类来实现文件的读写操作。这种方式使用了直接内存缓冲区,能够更好地利用操作系统底层的文件传输机制,因此效率更高。
示例:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public class NIOCopy {
public static void main(String[] args) {
String sourceFile = "source.txt";
String destFile = "dest.txt";
Path sourcePath = Path.of(sourceFile);
Path destPath = Path.of(destFile);
try {
Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("文件拷贝完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用NIO进行文件拷贝的效率最高,尤其是对于大文件而言。使用字节流进行传输的效率较低,特别是在处理大文件时,可能会导致内存溢出的问题。使用缓冲区进行传输的效率稍微高一些,但仍然比不上NIO方式。
扩展:
在Java中,使用缓冲区进行传输比使用字节流进行传输效率更高的原因如下:
- 减少对底层I/O设备的访问次数:使用字节流进行传输时,每次读取或写入数据都会直接与底层I/O设备进行交互,这会导致频繁的设备访问。而使用缓冲区传输时,数据会先被写入到缓冲区中,然后再一次性地与底层I/O设备进行交互。这样可以减少对底层I/O设备的访问次数,提高效率。
- 提高数据读写的速度:使用字节流进行传输时,每次读取或写入数据都是以一个字节为单位进行操作,这会导致频繁的读写操作。而使用缓冲区传输时,可以一次性读取或写入一定量的数据,大大提高了数据读写的速度。
- 减少系统调用次数:使用字节流进行传输时,每次读取或写入数据都会触发系统调用,并涉及到内核态和用户态的切换。而使用缓冲区传输时,减少了系统调用的次数,减少了系统资源的消耗。
- 提供了更灵活的读写方式:缓冲区提供了一系列的读写方法,可以根据实际需求来选择不同的读写方式。可以通过一次性读取或写入多个字节来提高效率,也可以使用标记和重置等方法来回退或跳过数据。
5.1.2、I/O和NIO的区别是什么?
答:
在Java中,I/O(输入/输出)和NIO(新I/O)是两种不同的输入输出模型。它们的区别在于以下几个方面:
-
I/O是基于流(Stream)的模型,而NIO是基于缓冲区(Buffer)的模型。I/O通过流按照字节序列一个接一个地处理数据,而NIO通过将数据先读入缓冲区,然后再进行处理。
-
I/O是面向字节的,而NIO是面向块(Block)的。I/O以字节为单位进行读写操作,而NIO以块为单位进行读写操作,一个块可以包含多个字节。
-
I/O是阻塞式的,而NIO是非阻塞式的。在I/O中,当进行读操作时,如果没有数据可读,线程将被阻塞,直到有数据可读。而在NIO中,当进行读操作时,如果没有数据可读,线程不会被阻塞,可以继续执行其他操作。
-
I/O是单向的,而NIO是双向的。在I/O中,一个流只能作为读流或写流使用,而在NIO中,一个通道(Channel)可以同时用于读和写。
扩展:
I/O适用于低负载、低并发的场景,而NIO适用于高负载、高并发的场景。使用NIO可以提供更高的吞吐量和更低的延迟。
5.1.3、谈谈你对I/O多路复用机制的理解
答:
I/O多路复用是指通过一个线程来管理多个I/O事件的机制。在Java中,可以使用NIO的Selector来实现I/O多路复用。
I/O多路复用的基本原理是通过一个线程同时监听多个输入流的可读、可写等事件,当某个输入流有事件发生时,就会通知线程进行处理,从而实现同时处理多个I/O事件的能力。
在Java中,使用Selector类来创建一个选择器,然后将多个通道(Channel)注册到选择器上。在循环中调用选择器的select()方法,该方法会阻塞直到至少有一个通道有事件发生。然后通过selectedKeys()方法获取发生事件的通道集合,进行相应的处理。
使用NIO的Selector实现多路复用的示例:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NioMultiplexingServer {
public static void main(String[] args) throws IOException {
// 创建ServerSocketChannel,并绑定到指定端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
serverSocketChannel.configureBlocking(false);
// 创建Selector,并注册ServerSocketChannel的ACCEPT事件
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞等待事件发生
int readyChannels = selector.select();
if (readyChannels == 0) {
continue;
}
// 获取发生事件的SelectionKey集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
// 处理ACCEPT事件
ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 处理READ事件
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
buffer.flip();
String data = new String(buffer.array()).trim();
System.out.println("Received data: " + data);
socketChannel.close();
}
// 从集合中删除已处理的SelectionKey
iterator.remove();
}
}
}
}
首先创建了一个ServerSocketChannel,并将其绑定到指定端口。
然后创建了一个Selector,并注册ServerSocketChannel的ACCEPT事件。
在主循环中,调用Selector的select()方法阻塞等待事件发生,当有事件发生时,
通过selectedKeys()方法获取发生事件的SelectionKey集合,并遍历处理。
如果是ACCEPT事件,则调用accept()方法获取SocketChannel,并将其注册到Selector上以监听READ事件。
如果是READ事件,则从SocketChannel中读取数据并处理,然后关闭SocketChannel。
I/O多路复用的优点是能够同时处理多个I/O事件,而且不需要创建多个线程来处理每个事件,从而节省了系统资源。它适用于需要同时处理多个连接的高并发场景。
标签:5.1,java,NIO,编程,网络,事件,import,字节,String From: https://blog.csdn.net/weixin_61769871/article/details/142367742扩展:
I/O多路复用也有一些限制。首先,它对于处理一些复杂的逻辑可能不够灵活,因为它只关注事件的发生而并不关心事件的具体内容。其次,如果某个事件的处理时间过长,会影响其他事件的处理速度。因此,在使用I/O多路复用时需要考虑事件处理的效率和逻辑复杂度。