TECH NOTES / Java 与 Spring
Java NIO 实现大文本按行拆分文件,仅支持 txt、csv 格式文件
原笔记 2022-10-25 · 整理于 2026-10-05
使用 FileChannel 按换行边界拆分大文本,保留完整示例及文件格式边界说明。
适用范围:这份代码按物理换行拆分文本,不是 CSV 解析器;带引号字段内含换行的 CSV 不适用。示例依赖 Commons IO 与 Spring Assert 等工具类;传入的拆分数量和长度须大于零,使用前请用小文件核对边界与编码。
按行拆分文件,仅支持 txt、csv 格式文件
/**
* 按行拆分文件,仅支持 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);
}参考: