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

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

批次信息[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;

        }

原文地址:https://www.cnblogs.com/10158wsj/p/8213810.html

时间: 2024-08-26 11:43:56

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

黑马程序员——Java基础---IO(三)--File类、Properties类、打印流、序列流(合并流)

------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- File类 一.概述 1.File类:文件和目录路径名的抽象表现形式 2.特点: 1)用来将文件或文件夹封装成对象 2)方便于对文件与文件夹的属性信息进行操作,因此是对流操作的一种补充 3)File类的实例是不可变的:也就是说,一旦创建,

JAVA之旅(三十)——打印流PrintWriter,合并流,切割文件并且合并,对象的序列化Serializable,管道流,RandomAccessFile,IO其他类,字符编码

JAVA之旅(三十)--打印流PrintWriter,合并流,切割文件并且合并,对象的序列化Serializable,管道流,RandomAccessFile,IO其他类,字符编码 三十篇了,又是一个阳光明媚的周末,一个又一个的周末,周而复始,不断学习,前方的路你可曾看见?随我一起走进技术的世界,流连忘返吧! 一.打印流PrintWriter 打印流有PrintWriter和PrintStream,他的特点可以直接操作输入流还有文件 该流提供了打印方法,可以将各种数据类型原样打印 file对象

Java基础---IO(二)--File类、Properties类、打印流、序列流(合并流)

第一讲     File类 一.概述 1.File类:文件和目录路径名的抽象表现形式 2.特点: 1)用来将文件或文件夹封装成对象 2)方便于对文件与文件夹的属性信息进行操作 3)File类的实例是不可变的:也就是说,一旦创建,File 对象表示的抽象路径名将永不改变 4)File对象可以作为参数传递给流的构造函数 二.File对象创建 方式一: File f =new File("a.txt"); 将a.txt封装成File对象.可以将已有的和未出现的文件或者文件夹封装成对象. 方式

黑马程序猿——25,打印流,合并流,对象序列化,管道流,RandomAccessFile

------<ahref="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- 黑马程序猿--25.打印流.合并流.对象序列化,管道流,RandomAccessFile /* IO流的打印流:专门用于打印的流 字节打印流PrintStream PrintStream的构造函数能够接收file对象,String型字符串路

Java API —— IO流(数据操作流 &amp; 内存操作流 &amp; 打印流 &amp; 标准输入输出流 &amp; 随机访问流 &amp; 合并流 &amp; 序列化流 &amp; Properties &amp; NIO)

1.操作基本数据类型的流 1) 操作基本数据类型 · DataInputStream:数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型.应用程序可以使用数据输出流写入稍后由数据输入流读取的数据. · DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中.然后,应用程序可以使用数据输入流将数据读入. package datastreamdemos; import java.io.*; /** * Created b

Java基础系列10:内存操作流,管道流,合并流,压缩流以及回退流

前言:这篇文章将对几个"非主流"的IO流进行简单的介绍 一 内存操作流 内存操作流的主要作用是完成内存的输入和输出.比如说在某些情况下需要生成一些临时信息,而将这些临时信息保存在文件中不仅要进行文件的读写而且在功能完成之后还需要删除这个临时文件,因此比较麻烦,这时或许就需要用到内存操作流了. 需要用到的API是:ByteArrayInputStream和ByteArrayOutputStream,分别表示输入流和输出流,示例代码如下: package javase.io; import

一、javaSE (二十二)登录注册IO版本案例、数据操作流、内存操作流、打印流、标准输入输出流、随机访问流、合并流、序列化流、Properties、NIO

1:登录注册Io版本案例(掌握) 要求,对着写一遍 cn.itcast.pojo User cn.itcast.dao UserDao cn.itcast.dao.impl UserDaoImp1(实现我不管) cn.itcast.game GuessNumber cn.itcast.test UserTest 2:数据操作流(操作基本类型数据的流)(理解) (1)可以操作基本类型的数据 (2)流对象名称 DataInputStream DataOutputStream 3:内存操作流(理解)

IO流:System.in、SequenceInputStream合并流、内存输入输出流、数据流

1.System.in   System.out 是常用的在控制台输出数据的    System.in 可以从控制台输入数据      InputStream is = System.in   while (true) {      // 敲入a,然后敲回车可以看到      // 97 13 10      // 97是a的ASCII码      // 13 10分别对应回车换行      int i = is.read();      System.out.println(i);     字

IO流 合并流

package com.yyq; /* * 功能流 (合并流) * SequenceInputStream 表示其他输入流的逻辑串联,他从输入流的有序集合开始, * 并从第一个输入流开始读取,直到文件末尾. * 把三个文件的数据变成一个文件 * * */ import java.io.*; import java.util.Enumeration; import java.util.Vector; public class SequenceInputStreamDemo { public sta