笔记:nio + 多线程 实现文件分片上传

Nio的概念:

NIO(Non-blocking I/O)是 Java 中的一种 I/O 处理机制,提供了一种与传统阻塞 I/O 相对的非阻塞方式。它允许程序在进行 I/O 操作时不必等待操作完成,从而提高性能和响应性。NIO 包括几个关键组件:

  1. 缓冲区(Buffer):用于存储数据的容器,提供了读写数据的功能。
  2. 通道(Channel):与传统的流不同,通道可以同时进行读写操作。
  3. 选择器(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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值