大文件切割与合并

 2017-04-11 21:22:41     数据收集  Java   882


导读: 在做日志收集和处理的时候,经常会遇到对大文件的操作,当内存有限,又要求快速处理的时候,就需要考虑对文件进行切割,利用多线程将一个大文件切割成多个小文件,然后再对小文件分别操作,这样既可以限制内存的使用,又可以提高效率。

切割与合并,可以使用RandomAccessFile类,它支持对随机访问文件的读取和写入,程序可以直接跳到任意地方来读写数据。

文件切割


大文件的上传、下载、内容处理等操作,都会涉及到文件切割处理。

文件切割的流程:

  • 1、设定切割后的小文件大小,根据原文件大小,计算出需要切割的小文件数量;
  • 2、开启线程进行切割,每个线程根据定位,对原文件进行读取内容,写入到对应的小文件;
  • 3、等所有线程处理完后,进行其它业务逻辑处理。

流程图

流程图

Java代码

计算需要切割的文件数量

private static ExecutorService executorService = Executors.newFixedThreadPool(2);

private static int PART_SIZE = 1024 * 1024 * 20; // 50M

private void test1(File file) {
    long fileLength = file.length();
    //按照字节长度,计算出切割的份数
    int partCount = (int) (fileLength / PART_SIZE);
    if (fileLength % PART_SIZE != 0) {
        partCount++;
    }

    List<FilePart> list = new ArrayList<FilePart>();

    for (int i = 0; i < partCount; i++) {
        long startPos = i * PART_SIZE;
        long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : PART_SIZE;

        FilePart part = new FilePart();
        part.setFileSize(curPartSize);
        part.setPartNumber(i + 1);
        list.add(part);

        executorService.execute(new PartitionThread(file, startPos, Integer.valueOf(curPartSize + ""), i + 1));//多任务并行处理
    }

    executorService.shutdown();
    while (!executorService.isTerminated()) {
        try {
            executorService.awaitTermination(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            System.out.println("Upload file to OSS error!" + e);
        }
    }
}

线程处理切割

@Override
public void run() {
    System.out.println(Thread.currentThread().getName() + " start partition, partNumber: " + this.partNumber + ", startPos: " + this.startPos);
    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
        inputStream = new FileInputStream(this.originFile);
        inputStream.skip(this.startPos);
        outputStream = new FileOutputStream(new File(this.parentFile, this.fileName + "_" + partNumber + this.suffix));
        byte[] buffer = new byte[this.curPartSize];//也可以按行读写,对每行数据进行加工处理
        inputStream.read(buffer, 0, this.curPartSize);
        outputStream.write(buffer);
        outputStream.flush();
    }catch (Exception e) {
        System.out.println("partNumber: " + partNumber + ", error info: " + e);
    } finally {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                System.out.println("close outputStream error!");
            }
        }
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                System.out.println("close inputStream error!");
            }
        }
    }
}

文件合并


与文件切割相对应的,就是文件合并。

文件合并主要用到了RandomAccessFile类,与普通的IO流相比,它最大的特点就是支持任意访问,减少了内存开销。

例如:向一个大文件尾部追加内容,如果用传统的IO流,就有可能导致内存溢出。如果RandomAccessFile,就可以直接像指针一样直接指定到文件尾部,然后进行追加。

Java代码

public void merge() {
    RandomAccessFile randomAccessFile = null;
    byte[] b = new byte[1024 * 1024 * 5];
    FileInputStream fileInputStream = null;
    long offset = 0L;
    int len = 0;
    try {
        randomAccessFile = new RandomAccessFile(this.filePath + this.fileName + "_merge" + this.suffix, "rw");
        for (FilePart part : fileParts) {
            System.out.println("merge number: " + part.getPartNumber());
            randomAccessFile.seek(offset);
            fileInputStream = new FileInputStream(this.filePath + this.fileName + "_" + part.getPartNumber() + this.suffix);
            while ((len = fileInputStream.read(b)) > 0) {
                randomAccessFile.write(b, 0, len);
            }
            fileInputStream.close();
            offset = offset + part.getFileSize();
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (randomAccessFile != null) {
            try {
                randomAccessFile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}