这种方式不会加大内存占用,经过测试4g内存的文件内存占用一直没有很大。

事实上,常用的 FileInputStream , FileOutputStream 都是通过 FileChannel 进行读写的,而FileChannel可以设置开始读、写的位置,demo如下:

public static void task(String fileName,int batchCnt) throws Exception{
        long start = System.currentTimeMillis();
        File toWrite = new File("src\\"+fileName + ".pdf");
        File toRead = new File("C:\\Users\\86134\\Desktop\\book\\实现领域驱动设计.pdf");
        CountDownLatch cdl = new CountDownLatch(batchCnt);
        long size = toRead.length();
        long patch = size/batchCnt;
        for (int i = 0; i < batchCnt; i++) {
            int finalI = i;
            GlobalThreadPool.execute(()-> {
                long end = patch * (finalI+1);
                try {
                    write(toWrite,toRead,patch* finalI,end);
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    cdl.countDown();
        cdl.await();
        System.out.println(batchCnt+"线程任务耗时"+(System.currentTimeMillis()-start)+"ms");
        //while(toWrite.exists())toWrite.delete();
    public static void write(File toWrite,File toRead,long start,long end) throws Exception{
        FileInputStream fis = new FileInputStream(toRead);
        FileOutputStream fos = new FileOutputStream(toWrite);
        fis.getChannel().position(start);
        fos.getChannel().position(start);
        fos.getChannel().force(true);
        byte[] tmp = new byte[batchCnt];
        int s;
        while (true) {
            int curBatch = (int)Math.min(end-start,batchCnt);
            s = fis.read(tmp,0, curBatch);
            start += s;
            if(s<=0 || start>end) break;
            fos.write(tmp, 0, curBatch);
        fis.close();
        fos.close();

经过测试,文件可以正常打开。

测试代码如下:

    public static void main(String[] args) throws Exception {
        with1Thread();
        with2Thread();
        with3Thread();
        with4Thread();
        with5Thread();
        with6Thread();

1线程任务耗时1297ms 2线程任务耗时705ms 3线程任务耗时659ms 4线程任务耗时468ms 5线程任务耗时508ms 6线程任务耗时399ms

考虑到开线程的损耗,可以说确实起到了需要的效果。

这里为什么不用JMH?

我的考虑是不太清楚具体的原理(相关是如何执行的?),而且每次都需要重新生成一个文件,为了确保条件尽可能类似,需要任务执行完成之后进行删除。因此在书写了对应的JMH测试之后,发现效率特别慢。

但如果将文件变大,多线程反而就没有这种倍增的效果。

mmap设置大小

这次将文件变成了一个大小为4g的来做一下对比。

java中,mmap最大只能映射Integer.MAX_VALUE(2^31-1)的内存块,需要设置起始位置.

mmap实际上也是FileChannel映射的,需要指定start(从哪里开始映射)和size(从start开始映射多少长度)

public static void job() throws IOException, InterruptedException {
    long start = System.currentTimeMillis();
    File w = new File("C:\\Users\\86134\\Desktop\\book\\"+"mmap" + ".7z");
    while(!w.exists() && !w.createNewFile());
    File r = new File("C:\\Users\\86134\\Desktop\\book\\src.7z");
    Path toWrite = w.toPath();
    Path toRead = r.toPath();
    long total = r.length();
    long cnt = total/Integer.MAX_VALUE;
    if(cnt*Integer.MAX_VALUE>total) cnt++;
    CountDownLatch cdl = new CountDownLatch((int)cnt);
    for (long i = 0; i <= cnt; i++) {
        long end = Math.min(total,(i+1)*Integer.MAX_VALUE);
        long finalI = i;
        GlobalThreadPool.execute(()->{
            try {
                part(toWrite,toRead, finalI *Integer.MAX_VALUE,end);
                cdl.countDown();
            } catch (IOException e) {
                e.printStackTrace();
    cdl.await();
    System.out.println("mmap多线程耗时:"+(System.currentTimeMillis()-start)+"ms");
    System.exit(0);
public static void part(Path toWrite,Path toRead,long start,long end) throws IOException {
    FileChannel readChannel = FileChannel.open((toRead), StandardOpenOption.READ);
    FileChannel writeChannel = FileChannel.open((toWrite), StandardOpenOption.WRITE);
    //数据传输
    //坑死了,这里end只能map到Integer.MAX_VALUE(2^31-1),超出去就报错
    MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY,start,end-start);
    writeChannel.position(start);
    writeChannel.write(data);
    readChannel.close();
    writeChannel.close();

同时也将上面FileChannel中,将文件换成了相同的,做了类似的对比,结果如下:

mmap2线程耗时:20922ms

FileChannel2线程任务耗时34565ms

在单纯的速度上mmap占优,但是在内存上,MMAP有一个较大的问题。

  • 测试平台是win10,fileChannel的方式并不会多吃掉多少内存,然而mmap会大概多吃掉2~3g的内存,这个地方需要去研究研究。
  • 分类:
    后端
  •