合并流以及已知文件流长度和未知文件长度的文件流读取方法

  项目中有一个这样的需求,上传文件的时候需要多张文件一起上传,而且每张文件都有自己的文件信息,因为文件信息需要匹配验证,在处理过程中需要传输流的时候前半段固定长度为文件信息,后半段是文件流,而且还是多张批量的情况,经过不断摸索最终想出一个方案:那就是采用合并流,示意图如下:

批次信息[256]+文件信息流1[1024]+文件流1+文件信息流2[1024]+文件流2+文件信息流3[1024]+文件流3…….

  前面256是固定长度的一个流,里面是文件数量等信息,文件批量上传的时候就可以根据文件数量来循环文件的个数了。

当然,1024固定长度信息中必须包含每个文件的大小(字节数),如果不知道大小的话,下一个循环的时候就会出现错误。

合并流介绍

        合并流,顾名思义,就是将多个流合并到一起形成一个完整的流,然后再根据需要截取需要的信息,原理很简单,下面是API:

    private static SequenceInputStream getSequenceStream(String  fileInfoString,String filePath){

      FileInputStream fs=null;

        ByteArrayInputStream bais=null;

        SequenceInputStream sis=null;

        try {

              //文件信息流

              byte[] b = fileInfoString.getBytes();

              byte[] info = Arrays.copyOf(b,1024);

              bais=new ByteArrayInputStream(info);     

              //文件流

              File file=new File(filePath);

              fs=new FileInputStream(file);

              //合并流

              sis=new SequenceInputStream(bais,fs);

             } catch (FileNotFoundException e) {

                e.printStackTrace();

             }

             return sis;

        }    

  上面代码就是合并流的过程,非常简单,我们可以根据固定的1024长度得到文件信息,这个长度的设置可以根据实际需求来调整,或者是直接利用String类的length方法获取,没有固定的值。利用这个原理,我们就可以将多个文件合并成一个流传到我们需要的地方了,我们这个项目是吧文件写入大数据平台根据文件的具体大小存入Hbase或者HDFS。

合并流搞定之后,接下来我们就是截取合并流了,这里就出现了一个问题,文件信息我们可以根据前面的固定长度截取读出来进行解析,因为他是转化String类型很容易,但是文件流呢,虽然我们知道他的大小,但是如何快速的把他读出来并写入磁盘呢?

第一种方案:直接读写

        这种方案就是我们根据文件信息中告诉我们的流大小,然后开辟相应的缓冲区,把它一次性的写入我们的磁盘,下面是代码:

    private byte [] getBytes(BufferedInputStream bis,long fileSize){

                 byte[] buffer=null;

                 try {

                         ByteArrayOutputStream bos=new ByteArrayOutputStream();

                         int count=(int)fileSize;

                         int readCount=0;//已经成功读取的字节

                         int len=0;

                         byte []buf=new byte[count];

//                      buf=new byte[count];

                         logger.info("Hbase可读文件大小:"+count);

                         while(readCount<count){

                                  len=bis.read(buf, readCount, count-readCount);

                                  readCount+=len;

                         }

                         bos.write(buf);

                         bos.flush();

                         buffer = bos.toByteArray();

                         bos.close();

                 } catch (IOException e) {

                         e.fillInStackTrace();

                 }

                 return buffer;

        }

测试效果:

  经过测试,这种方案是可行的,但是我们做的是大数据的项目,客户对效率的要求非常高,从压力测试来看效率非常低,另外,如果文件很大的话,这样的buffer开辟的区域会占用很大的内存,所以这种方案,虽然功能上可行,但是不可取,没有效率。下面是测试效果:

这种方案功能可行,但是效率不行,问题的根源就在于读流的方法没有效率,没有发挥出read方法的功效,而且开辟的缓冲区根据文件的大小而不同,如果一个批次文件太多的话,很容易把内存占满,发生内存溢出。 

第二种方案:循环读写

        这种方案是前一种方案的改进,我们根据文件大小利用循环读完固定长度的流,首先解释一下为什么要这样写while循环,要从read这个方法说起,从源码分析来看,read这个方法一次性本来就不可已读完整个流的,所以为了保证所有的流都读完,只能这样写。下面是方法:

 

//--------------------------改进的方法-----------------------------

        private byte [] getBytess(BufferedInputStream bis,long fileSize){

                 byte[] buffer=null;

                 try {

                     ByteArrayOutputStream bos=new ByteArrayOutputStream();

                      int count=(int)fileSize;

                      int readCount=0;//已经成功读取的字节
                      int len=0;
                      byte []buf=new byte[4096];
while( count>0){ len=bis.read(buf,0,count); bos.write(buf); count-=len; } bos.flush(); buffer = bos.toByteArray(); bos.close(); return buffer; } catch (IOException e) { e.fillInStackTrace(); } return buffer; }

        这样的尝试貌似可行,但是在测试的过程中又出现问题了,这种方案流是能读完,但是偶尔会读多,为什么呢?问题出现在while循环里,因为最后一次的循环可能不是文件剩下的那么多,比如1028个字节的流,它第一次会读1024,第二次读的就不是剩下的8个字节了,而且还是1024,原因就在于read这个方法,除非到-1他能读完,但是我们又不能读到-1,所以这种方法还需要改进,其实很简单,加判断就可以了,下面是改进的方法:

private byte [] getBytes(BufferedInputStream bis,long fileSize) throws IOException{

                 byte[] buffer=null;

                 ByteArrayOutputStream bos=null;
                 bos=new ByteArrayOutputStream();
                 int count=(int)fileSize;
                 int len=0;
                 byte []buf=new byte[4096];

                 while(count>0){
                    if(count<buf.length){
                        len=bis.read(buf,0,count);
                    }else{
                        len=bis.read(buf);
                    }
                        bos.write(buf,0,len);
                         count-=len;
                 }
                 bos.flush();
                 buffer = bos.toByteArray();
                 bos.close();
                 return buffer;
        }

经过测试后这种方法不仅可行而且对效率也没有影响。

测试效果:

 

从效果来看,效率提升了不止一倍。最后附上一次性读完流的代码,这种方式可以把整个流读完。

 

private byte [] getBytes(BufferedInputStream bis) throws IOException{

                 byte[] buffer=null;

                 ByteArrayOutputStream bos=null;
                 bos=new ByteArrayOutputStream();
                 int len=0;

                 byte []buf=new byte[1024];

                 while((len=bis.read(buf))!=-1){

                         bos.write(buf, 0, len);

                 }

                 bos.flush();

                 buffer=bos.toByteArray();

                 bos.close();
                 return buffer;

        }

 

posted @ 2018-01-06 11:57  我心自在  阅读(915)  评论(0编辑  收藏  举报