TECH NOTES / Java 与 Spring

Java NIO 实现大文本按行拆分文件,仅支持 txt、csv 格式文件

原笔记 2022-10-25 · 整理于 2026-10-05

使用 FileChannel 按换行边界拆分大文本,保留完整示例及文件格式边界说明。

适用范围:这份代码按物理换行拆分文本,不是 CSV 解析器;带引号字段内含换行的 CSV 不适用。示例依赖 Commons IO 与 Spring Assert 等工具类;传入的拆分数量和长度须大于零,使用前请用小文件核对边界与编码。

按行拆分文件,仅支持 txt、csv 格式文件

java
/**
 * 按行拆分文件,仅支持 txt、csv 格式文件
 * <pre>
 *     注意:
 *     1、使用Java NIO 实现,速度更快
 *     2、最后拆分的单个文件大小可能比设置的值略小
 *     3、拆分的文件存放位置和传入文件在同级目录,名称格式为:源文件名称_[1 - fileCount].源文件格式后缀
 * </pre>
 *
 * @param file       文件
 * @param fileLength 单个拆分文件字节大小
 */
public static void splitFileByRowAndLength(File file, int fileLength) throws IOException {
    final long length = file.length();

    if (length <= fileLength) {
        FileUtils.copyFile(file, new File(getOutPutFilePath(file.getPath(), "_1")));
        return;
    }

    // 计算切割文件个数
    int fileCount = Double.valueOf(Math.ceil((double) length / fileLength)).intValue();
    splitFileByRowAndCount(file, fileCount);
}

/**
 * 按行拆分文件,仅支持 txt、csv 格式文件
 * <pre>
 *     注意:
 *     1、使用Java NIO 实现,速度更快
 *     2、最后拆分的文件数量可能比设置的值小,因为数据按行拆分后,拆分数量过大会导致数据不够拆
 *     3、拆分的文件存放位置和传入文件在同级目录,名称格式为:源文件名称_[1 - fileCount].源文件格式后缀
 * </pre>
 *
 * @param file      文件
 * @param fileCount 拆分数量
 */
public static void splitFileByRowAndCount(File file, int fileCount) throws IOException {
    Assert.notNull(file, "file must not null");

    final String canonicalPath = file.getCanonicalPath();

    Assert.isTrue(canonicalPath.endsWith(".txt") || canonicalPath.endsWith(".csv"), "file type not support");

    try (final FileInputStream fis = new FileInputStream(file); final FileChannel inputChannel = fis.getChannel()) {
        // 文件总大小(字节)
        final long fileSize = inputChannel.size();

        // 平均值
        final long average = fileSize / fileCount;

        // 缓存块大小,自行调整
        final long bufferSize = 200;

        // 申请一个缓存区
        final ByteBuffer byteBuffer = ByteBuffer.allocate(Integer.parseInt(bufferSize + ""));

        // 子文件开始位置
        long startPosition = 0;

        // 子文件结束位置
        long endPosition = average < bufferSize ? 0 : average - bufferSize;

        for (int i = 0; i < fileCount; i++) {
            if (i + 1 != fileCount) {
                // 读取数据
                int read = inputChannel.read(byteBuffer, endPosition);
                readW:
                while (read != -1) {
                    //切换读模式
                    byteBuffer.flip();
                    byte[] array = byteBuffer.array();
                    for (int j = 0; j < array.length; j++) {
                        byte b = array[j];
                        //判断\n\r
                        if (b == 10 || b == 13) {
                            endPosition += j;
                            break readW;
                        }
                    }
                    endPosition += bufferSize;
                    //重置缓存块指针
                    byteBuffer.clear();
                    read = inputChannel.read(byteBuffer, endPosition);
                }
            } else {
                //最后一个文件直接指向文件末尾
                endPosition = fileSize;
            }

            if (startPosition >= endPosition) {
                // 不需要再分了,数据已经分完了
                break;
            }

            // 切割的文件名称
            String pathName = getOutPutFilePath(canonicalPath, "_" + (i + 1));

            try (final FileOutputStream fos = new FileOutputStream(pathName); final FileChannel outputChannel = fos.getChannel()) {
                // 通道传输文件数据
                inputChannel.transferTo(startPosition, endPosition - startPosition, outputChannel);
                startPosition = endPosition + 1;
                endPosition += average;
            }

            // 校验文件是否为空
            final File outFile = new File(pathName);
            if (outFile.exists() && outFile.length() <= 0) {
                final boolean delete = outFile.delete();
                log.debug("outFile is null, delete = " + delete);
            }
        }
    }
}


/**
 * Gets out put file path.
 *
 * @param filePath the file path
 * @param suffix   the suffix
 * @return the out put file path
 */
private static String getOutPutFilePath(String filePath, String suffix) {
    final int lastIndexOf = filePath.lastIndexOf(".");
    return filePath.substring(0, lastIndexOf) + suffix + filePath.substring(lastIndexOf);
}

参考:

文章配图