IO应用——文件分割

介绍

很多下载软件下载的速度飞快是因为使用了多线程下载(至于多线程的知识,后面还是会学习的),将文件分割成不同的小块,然后不同的小块同时下载,最后再将这些小块的文件进行合并,这样下载就完成了,这样可以显著的提高下载的速度。这里我们显然不是做一个这样的下载器,而是做其中的一部分,将文件分割成许多小块,然后再将这些小文件合并。

思路

其实之前没有看过任何人的思路或者写法,我就自己就我之前学过的IO流的知识来实现这个功能。其实说简单也不简单。能用就行其实确实是可以用,不过代码中的漏洞或者交互性应该是很差的。

我的代码

public class FileSplit {
    public static void split(String src, String dest, int num) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            File file = new File(src);
            fis = new FileInputStream(file);
            byte[] buff = new byte[(int) file.length() / num + 1];
            fis.read(buff);
            int a = 1;
            while (a <= num) {
                fos = new FileOutputStream(new File(dest + a));
                fos.write(buff);
                fos.flush();
                a++;
                fis.read(buff);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void merge(String src, String dest, int num) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        int a = 1;
        try {
            fos = new FileOutputStream(new File(dest));
            while (a <= num) {
                File file = new File(src + a);
                fis = new FileInputStream(file);
                fos.write(fis.readAllBytes());
                a++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        split("Exercise/src/test.jpg", "D:/dst", 10);
        merge("D:/dst","D:/merge.jpg",10);
    }
}

这个思路故事是个人都会做。不过这个代码的问题也是非常的大的。在分割图片你的时候或许我们察觉不到分割后然后合并的文件和之前的文件有什么区别,但是分割文本文件额时候,不一样的地方就非常的明显了。我们可以轻易的发现,文本的最后会多出一点儿的内容,而且这一点儿的内容非常的奇怪,我也不清楚这一小块的内容是从哪儿来的。现在也非常的惆怅。我真的是喵了个咪了!

为什么会出现最后多出了一部分你的内容呢?是因为我们没有考虑到最后一块的大小,一般来说最后一块的大小肯定是要比前面的几块都要小的,所以说在写入最后一块的时候不能再用前面的大小。

经过修改后的代码 如下所示

package com.sher.ioinjava;

import java.io.*;

/**
 * @author SHeR
 * @time 9/27/2019 8:36 PM
 * @describe
 */
public class FileSplit {
    public static void split(String src, String dest, int num) {
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        try {
            File file = new File(src);
            if (!file.isFile()) {
                throw new IllegalArgumentException("Src must be a file");
            }
            File dst = new File(dest);

            if (!dst.exists()){
                if (!dst.mkdir()){
                    return;
                }
            }
            fis = new FileInputStream(file);
            bis = new BufferedInputStream(fis);
            byte[] buff = new byte[(int) file.length() / num + 1];
            int cot = bis.read(buff);
            int a = 1;
            while (a <= num) {
                fos = new FileOutputStream(new File(dest +"/"+ a));
                bos = new BufferedOutputStream(fos);
                bos.write(buff, 0, cot);
                bos.flush();
                a++;
                cot = bis.read(buff);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void merge(String src, String dest) {
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;

        File srcDir = new File(src);
        if (!srcDir.exists()) {
            throw new RuntimeException("Src does not exist!");
        }

        File [] files = srcDir.listFiles();
        if (files == null) {
            throw new RuntimeException("Src is empty");
        }
        int num = files.length;

        int a = 1;
        try {
            fos = new FileOutputStream(new File(dest));
            bos = new BufferedOutputStream(fos);
            while (a <= num) {
                File file = new File(src + "/" + a);
                fis = new FileInputStream(file);
                bis = new BufferedInputStream(fis);
                byte[] buff = new byte[1024];
                int cot = -1;
                while ((cot = bis.read(buff))!=-1){
                    bos.write(buff, 0, cot);
                }
                a++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        split("Exercise/src/SimpleInput.java", "D:/dst", 3);
        merge("D:/dst","D:/hello.java");
    }
}

经过我的测试,上面的代码基本上是没什么错误的地方了。那么这个题目我们就算是完成了。思路大概就是分割的时候一个输入流多个输出流,合并的时候一个输出流多个输入流。

不过这个代码并不能说是很完善的,我们看一下别人写的代码。

package com.sher.ioinjava;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

/**
 * @author SHeR
 * @time 9/28/2019 11:30 AM
 * @describe
 */
public class SplitFile {
    //源头
    private File src;
    //目的地(文件夹)
    private String destDir;
    //所有分割后的文件存储路径
    private List<String> destPaths;
    //每块大小
    private int blockSize;
    //块数: 多少块
    private int size;

    public SplitFile(String srcPath,String destDir) {
        this(srcPath,destDir,1024);
    }
    public SplitFile(String srcPath,String destDir,int blockSize) {
        this.src =new File(srcPath);
        this.destDir =destDir;
        this.blockSize =blockSize;
        this.destPaths =new ArrayList<String>();

        //初始化
        init();
    }
    //初始化
    private void init() {
        //总长度
        long len = this.src.length();
        //块数: 多少块
        this.size =(int) Math.ceil(len*1.0/blockSize);
        //路径
        for(int i=0;i<size;i++) {
            this.destPaths.add(this.destDir +"/"+i+"-"+this.src.getName());
        }
    }
    /**
     * 分割
     * 1、计算每一块的起始位置及大小
     * 2、分割
     * @throws IOException
     */
    public void split() throws IOException {
        //总长度
        long len = src.length();
        //起始位置和实际大小
        int beginPos = 0;
        int actualSize = (int)(blockSize>len?len:blockSize);
        for(int i=0;i<size;i++) {
            beginPos = i*blockSize;
            if(i==size-1) { //最后一块
                actualSize = (int)len;
            }else {
                actualSize = blockSize;
                len -=actualSize; //剩余量
            }
            splitDetail(i,beginPos,actualSize);
        }
    }
    /**
     * 指定第i块的起始位置 和实际长度
     * @param i
     * @param beginPos
     * @param actualSize
     * @throws IOException
     */
    private  void splitDetail(int i,int beginPos,int actualSize ) throws IOException {
        RandomAccessFile raf =new RandomAccessFile(this.src,"r");
        RandomAccessFile raf2 =new RandomAccessFile(this.destPaths.get(i),"rw");
        //随机读取
        raf.seek(beginPos);
        //读取
        //3、操作 (分段读取)
        byte[] flush = new byte[1024]; //缓冲容器
        int len = -1; //接收长度
        while((len=raf.read(flush))!=-1) {
            if(actualSize>len) { //获取本次读取的所有内容
                raf2.write(flush, 0, len);
                actualSize -=len;
            }else {
                raf2.write(flush, 0, actualSize);
                break;
            }
        }
        raf2.close();
        raf.close();
    }
    /**
     * 文件的合并
     * @throws IOException
     */
    public void merge(String destPath) throws IOException {
        //输出流
        OutputStream os =new BufferedOutputStream( new FileOutputStream(destPath,true));
        Vector<InputStream> vi=new Vector<InputStream>();
        SequenceInputStream sis =null;
        //输入流
        for(int i=0;i<destPaths.size();i++) {
            vi.add(new BufferedInputStream(new FileInputStream(destPaths.get(i))));
        }
        sis =new SequenceInputStream(vi.elements());
        //拷贝
        //3、操作 (分段读取)
        byte[] flush = new byte[1024]; //缓冲容器
        int len = -1; //接收长度
        while((len=sis.read(flush))!=-1) {
            os.write(flush,0,len); //分段写出
        }
        os.flush();
        sis.close();
        os.close();
    }
    public static void main(String[] args) throws IOException {
        SplitFile sf = new SplitFile("Exercise/src/SimpleInput.java","dest") ;
        sf.split();
        sf.merge("aaa.java");
    }
}

里面使用了两个新的流RandomAccessFileSequenceInputStream。我们可以稍微学习一下这个流,然后对我们直接的代码进行一些改进。

不过我发现使用上面的第一个类好像更加的繁琐,而且我完全没有必须去随机读取,所有说使用上面的第一个流就是没事找事干的。而上面的第二个流是将多个输入流合成一个输入流,使用后面操作更加的简单,我感觉这个功能还是稍微可以的。所以说这个可以有!

原来的分割文件的代码我没有进行修改,合并文件的方法我使用了SequenceInputStream进行了修改。修改后代码如下所示:

public static void merge(String src, String dest) {
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    FileOutputStream fos = null;
    BufferedOutputStream bos = null;

    File srcDir = new File(src);
    if (!srcDir.exists()) {
        throw new RuntimeException("Src does not exist!");
    }

    File [] files = srcDir.listFiles();
    if (files == null) {
        throw new RuntimeException("Src is empty");
    }
    int num = files.length;

    SequenceInputStream sinput = null;
    Vector<InputStream> vector = new Vector<>();

    int a = 1;
    try {
        fos = new FileOutputStream(new File(dest));
        bos = new BufferedOutputStream(fos);

        while (a <= num) {
            File file = new File(src + "/" + a);
            fis = new FileInputStream(file);
            bis = new BufferedInputStream(fis);
            vector.add(bis);
            a++;
        }

        sinput = new SequenceInputStream(vector.elements());
        int cot = -1;
        byte[] buff = new byte[1024];
        while ((cot = sinput.read(buff))!=-1){
            bos.write(buff, 0, cot);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (bis != null) {
                bis.close();
            }
            if (bos != null) {
                bos.close();
            }
            if (sinput != null) {
                sinput.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

其实修改的地方并不多,只是将多个输入流整合成一个输入流了而已。

总结

上面的虽然是一个简单的应用,不过也会出不少的细节上面的小问题。所以说,解决了这个问题,对我们对IO的理解还是有一定的帮助的。


一枚小菜鸡