由一个下载文件数据丢失问题分析BufferedOutputStream源码

今天突然遇到一个问题,通过下载文件的接口下载的文件比实际文件小了2kb,而且文件中的内容比实际内容少了很多,带着这个问题,我跟踪代码执行流程,我们来看一下核心代码:
        bis = new BufferedInputStream(new FileInputStream(targetFilePath));
        bos = new BufferedOutputStream(response.getOutputStream());
        byte[] buff = new byte[2048];
        int bytesRead;
        while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
            bos.write(buff, 0, bytesRead);
        }

核心代码非常简单,就是根据目标文件,通过FileInputStream流来读取目标文件流,写入到response的输出流中。中间通过BufferedInputStream和BufferedOutputStream缓冲流来提高性能。 根据上述代码,我大胆猜测可能出现问题的原因是BufferedInputStream或者BufferedOutputStream。进一步分析:

		 while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) 		

该语句是循环读取所有读缓冲区的内容,因此,该语句出现问题的几率不是很大,很大可能是因为写缓冲区的问题,下面我通过分析BufferedOutputStream的源码来看看能不能找出问题的原因:

	BufferedOutputStream位于 java.io包下
	
	/**
	* 继承自FilterOutputStream(FilterOutputStream有一个			OutputStream的属性,就是目标输出out对象流)
	*/
	public class BufferedOutputStream extends FilterOutputStream {
	    /**
		 * 用来存储数据的缓冲区(默认8912个字节)
		 */
		protected byte buf[];

		/**
		 * 当前已存储数据的字节数(个人理解为指向已存储数据末尾的一个指针)
		 */
		protected int count;
		
		/**
		* 构造方法1: 用设置目标输出流对象,同时默认buff缓冲区大小8912个字节
		*/
		 public BufferedOutputStream(OutputStream out) {
			this(out, 8192);
		}
		
		/**
		* 构造方法2:设置输出流对象,自定义缓冲区的大小,
		*/
		 public BufferedOutputStream(OutputStream out, int size) {
			super(out);
			if (size <= 0) {
				throw new IllegalArgumentException("Buffer size <= 0");
				}
			buf = new byte[size];
		}
		
		
			/**
			* 刷新缓冲区(将缓冲区内容写入到目标流对象中,同同时将count置为0)
			**/
		   private void flushBuffer() throws IOException {
   			 if (count > 0) {
        			out.write(buf, 0, count);
        			count = 0;
    			}
		}
		
		/**
		* 向缓冲区写一个字节数据
		**/
		public synchronized void write(int b) throws IOException {
			//先判断缓冲区是否已满,如果已满,清空缓冲区
			if (count >= buf.length) {
       			 	flushBuffer();
    			}
    			buf[count++] = (byte)b;
		}
		
		
		/**
		* 向缓冲区写入指定长度的数据
		**/
		    public synchronized void write(byte b[], int off, int len) throws IOException {
			//判断写入数据的长度是否超过缓冲区大小,如果超过,直接写入目标对象out流中,清空缓冲区
			if (len >= buf.length) {
				flushBuffer();
				out.write(b, off, len);
				return;
			}
			//如果长度大于缓冲区剩余空间,将缓冲区清空,写入数据
			if (len > buf.length - count) {
				flushBuffer();
			}
			System.arraycopy(b, off, buf, count, len);
			count += len;
		}
		
		/**
		*刷新缓冲区
		**/
		public synchronized void flush() throws IOException {
			flushBuffer();
			out.flush();
		}
	}

从上面的源码中可以发现 触发缓冲区刷新的时机是当写入数据大小大于缓冲区的可用大小;结合上面的业务代码可以发现问题: 假如最后一次(或者几次)写入缓冲区的数据小于缓冲区实际的大小,不足以出发清空缓冲区将数据写入实际目标流的out对象中,而且没有手动触发刷新,就会造成数据丢失的问题。

为了解决该问题,将核心操作放到try-catche-finally中,在finally中手动关闭BufferedInputStream和BufferedOutputStream流(BufferedOutputStream并没有close方法,调用父类FilterOutputStream的close方法),在关闭前会强制刷新缓冲区的数据到out写对象流中。该问题得到解决。

转载于:https://my.oschina.net/u/2477500/blog/3100857

你可能感兴趣的:(由一个下载文件数据丢失问题分析BufferedOutputStream源码)