Nio的概念:
NIO(Non-blocking I/O)是 Java 中的一种 I/O 处理机制,提供了一种与传统阻塞 I/O 相对的非阻塞方式。它允许程序在进行 I/O 操作时不必等待操作完成,从而提高性能和响应性。NIO 包括几个关键组件:
- 缓冲区(Buffer):用于存储数据的容器,提供了读写数据的功能。
- 通道(Channel):与传统的流不同,通道可以同时进行读写操作。
- 选择器(Selector):用于管理多个通道的 I/O 事件,允许单个线程处理多个通道。
transferTo 和 transferFrom 的区别
1.transferTo: 将数据从源 FileChannel 的指定位置传输到目标 FileChannel。它是一个只写操作,因此在目标通道中会写入数据,源通道的数据不会改变。transferTo 在实现上通常能利用零拷贝技术,但在某些环境下可能有大小限制(如 2GB)。
2.transferFrom: 将数据从源 FileChannel 传输到当前 FileChannel。它是一个只读操作,因此源通道的数据会被读取并写入到当前通道。transferFrom 每个线程可以处理较小的数据块(例如 20MB),并在多线程环境下可能更适合处理不同的源文件片段。
代码
基础版:
/**
*
* @param uploadPath 上传目录
* @param fileName 文件名称
* @param inputStream 文件输入流
*/
public static void nioFileUpload(final String uploadPath, final String fileName, final InputStream inputStream) {
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
// 新建文件
File file = uploadBefore(uploadPath, fileName);
// 原通道
inChannel = ((FileInputStream) inputStream).getChannel();
// 目标通道
outChannel = new FileOutputStream(file).getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
closeStream(inChannel, outChannel);
} catch (IOException e) {
e.printStackTrace();
}
}
}
高级版:利用分片+多线程实现文件上传功能
/**
*
* @param uploadPath 上传目录
* @param fileName 文件名称
* @param inputStream 文件输入流
* @param fileSize 文件大小
* @param chunkSize 块大小
*/
public static void nioRandomFileChannelUpload(final String uploadPath,
final String fileName, final InputStream inputStream,
final Long fileSize, final Long chunkSize) {
FileChannel inChannel = null;
try {
System.out.println("开始执行...");
// 新建文件
File newFile = uploadBefore(uploadPath, fileName);
// 原通道
inChannel = ((FileInputStream) inputStream).getChannel();
// cpu核数
int cpuCount = Runtime.getRuntime().availableProcessors();
// 线程池
ExecutorService executor = Executors.newFixedThreadPool(cpuCount * 2);
// 分片大小
Long chunkCount = (fileSize / chunkSize) + (fileSize % chunkSize == 0 ? 0 : 1);
// 计数器
CountDownLatch countDownLatch = new CountDownLatch(chunkCount.intValue());
// 上传文件
for (long index = 0, position = 0, finalEndSize = chunkSize + position; index < chunkCount; index++, position = index * chunkSize) {
System.out.println("index = " + index + ",position = " + position + ",finalEndSize = " + finalEndSize);
Long finalPosition = position;
FileChannel inNewChannel = inChannel;
executor.execute(() -> new RandomFileChannelRun(finalPosition, finalEndSize, fileSize, newFile, inNewChannel, countDownLatch).start());
}
// 等待所有线程执行完毕
countDownLatch.await();
executor.shutdown();
System.out.println("执行结束...");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
closeStream(inChannel);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static class RandomFileChannelRun extends Thread {
//写通道
private File newFile;
//读通道
private FileChannel srcChannel;
//读取或写入的位置
private long position;
//结束位置
private long endSize;
//计数器
private CountDownLatch latch;
//文件大小
private Long fileSize;
public RandomFileChannelRun(Long finalPosition, long finalEndSize, Long fileSize, File newFile, FileChannel fileChannel, CountDownLatch countDownLatch) {
this.newFile = newFile;
this.srcChannel = fileChannel;
this.position = finalPosition;
this.endSize = finalEndSize;
this.latch = countDownLatch;
this.fileSize = fileSize;
}
@Override
public void run() {
FileChannel outChannel = null;
RandomAccessFile randomAccessFile = null;
try {
if (endSize > fileSize) {
endSize = fileSize;
}
// RandomAccessFile 允许在文件的任意位置进行读取和写入操作
randomAccessFile = new RandomAccessFile(newFile, "rw");
outChannel = randomAccessFile.getChannel();
// 设置outChannel当前位置的指针
outChannel.position(position);
srcChannel.transferTo(position, endSize, outChannel);
// 计数器减1
latch.countDown();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
closeStream(outChannel, randomAccessFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 关闭文件流
* @param closeables 流数组
* @throws IOException
*/
private static void closeStream(Closeable... closeables) throws IOException {
for (Closeable closeable : closeables) {
if (closeable != null) {
closeable.close();
}
}
}
private static File uploadBefore(String uploadPath, String fileName) throws IOException {
File directoryFile = new File(uploadPath);
if (!directoryFile.exists()) {
directoryFile.mkdirs();
}
File file = new File(directoryFile, fileName);
if (!file.exists()) {
file.createNewFile();
}
return file;
}