Java动态显示文件上传进度的简单实现

实现文件上传的进度显示,我们先看看都有哪些问题我们要解决。

1 上传数据的处理进度跟踪
2 进度数据在用户页面的显示
就这么2个问题,

第一个问题,主要是组件的选择
必须支持数据处理侦听或通知的组件。当然,我肯定只用我自己的组件啦。基本原理是

1 使用request.getContentLength() 读取到处理数据的总长度,注意这个长度不等于文件的长度,因为Base64等编码会增加数据量,如果超过了允许的长度,直接返回-1;

2 在每读取一部分数据时(比如一行,或者64K,或者你自定义的字节数),将读取的字节数通知我们的进度跟踪程序。我取名为 UploadListener代码如下

/** 
* 处理附件上传的通知。 

* 各位可以继承这个类,来实现自己的特殊处理。 
*  
@author 赵学庆 www.java2000.net 
*/
 
public   class  UploadListener 
  
// 调试模式将在控制台打印出一些数据 
  private boolean debug; 

  
// 总数据字节数 
  private int total; 

  
// 当前已经处理的数据字节数 
  private int totalCurrent = 0

  
// 延迟,用来调试用,免得速度太快,根本卡看不到进度 
  private int delay = 0

  
/** 
   * 处理数据通知的方法。 

   * 保存已经处理的数据。并且在一定的比例进行延迟。默认每1% 

   * 如果不需用延迟,可以删掉内部的代码,加快速度。 
   *  
   * 
@param size 增加的字节数 
   
*/
 
  
public void increaseTotalCurrent(long size) 
    
this.totalCurrent += size; 
    
try 
      currentRate 
= totalCurrent * 100 / total; 
      
if (currentRate > lastRate) 
        
if (delay > 0
          Thread.sleep(delay); 
        }
 
        
if (debug) 
          System.out.println(
"rate=" + totalCurrent + "/" + total + "/" + (totalCurrent * 100 / total)); 
        }
 
        lastRate 
= currentRate; 
      }
 
    }
 catch (Exception e) 
      e.printStackTrace(); 
    }
 
  }
 

  
/** 
   * 读取全部自己数 
   *  
   * 
@return 
   
*/
 
  
public int getTotal() 
    
return total; 
  }
 

  
/** 
   * 读取已经处理的字节数 
   *  
   * 
@return 
   
*/
 
  
public int getTotalCurrent() 
    
return totalCurrent; 
  }
 

  
private long lastRate = 0

  
private long currentRate = 0

  
public int getDelay() 
    
return delay; 
  }
 

  
public void setDelay(int delay) 
    
this.delay = delay; 
  }
 

  
public void setTotal(int total) 
    
this.total = total; 
  }
 

  
public boolean isDebug() 
    
return debug; 
  }
 

  
public void setDebug(boolean debug) 
    
this.debug = debug; 
  }
 
}

 3 下面我们来看上传的处理部分
  Upload upload  =   new  Upload(request); 
  
//  增加了侦听进度的代码 
  UploadListener uploadListener  =   new  UploadListener(); 
  
//  这句话我们后面再讨论,这个可是关键 
  session.setAttribute( " uploadListener " ,uploadListener); 
  uploadListener.setDelay(
0 ); 
  uploadListener.setDebug(
true ); 
  upload.setUploadListener(uploadListener); 
  upload.parse(); 
  
//  这句话同样重要,我们后面再讨论 
  session.setAttribute( " uploadListener " , null );
4 我们再看上传的表单部分
< script type = " text/javascript " >  
function checkForm()

  $(
"SHOW_FRAME").src="link.jsp"
  $(
'SUBMIT').disabled=true
  Ext.MessageBox.show(

    title: 
'Please wait...'
    msg: 
'Initializing...'
    width:
240
    progress:
true
    closable:
false 
  }
); 
  $(
"MAIN_FORM").submit(); 
  
return false
}
 
function setUploadProcess(total,current)

  var rate 
= Number(current)/Number(total); 
  Ext.MessageBox.updateProgress(rate,
'Uploading...'+current+"/"+total); 
  
if(Number(current)>=Number(total))
    closeUploadProcess(); 
  }
 
}
 
function closeUploadProcess()

  Ext.MessageBox.hide(); 
}
 
</ script >  
< iframe name = " ACTION_FRAME "  id = " ACTION_FRAME "  width = " 0 "  height = " 0 " ></ iframe >  
< iframe name = " SHOW_FRAME "  id = " SHOW_FRAME "  width = " 0 "  height = " 0 " ></ iframe >  
< form method = " OST "  id = " MAIN_FORM "  onsubmit = " return checkForm() "  enctype = " multipart/form-data "   
  action
= " uploadFileSave.jsp "  target = " ACTION_FRAME " >  
  
< input type = " file "  size = " 50 "  name = " file " >   
  
< input  type = " submit "  ID = " SUBMIT "  value = " Upload It " >  
</ form >
第一个iframe用于提交表单数据,第二个就是我们用来获取处理数据进度信息的。
提交表单很简单,target指向了我们的第一个iframe
我们看一下JS
checkForm 里面第一句就是关键的读取进度信息的页面,我们在第二个iframe里面获得。然后就是弹出进度的显示框,我使用了Ext. 然后提交上传表单
setUploadProcess 用来更新进度框上面的数据,第一个参数是数据总共的大小,第二个参数是已经处理的大小。
closeUploadProcess 关闭进度框

5 最后,我们来看读取进度信息的页面
<% @ page language = " java "  contentType = " text/html; charset=utf-8 "  pageEncoding = " utf-8 " %>  
<% @include file = " ../package.inc.jsp " %>  
<%  
  response.setHeader(
" ragma " " no-cache " ); 
  response.setHeader(
" Cache-Control " " no-cache " ); 
  response.setDateHeader(
" Expires " 0 ); 
  response.setBufferSize(
0 ); 
  UploadListener uploadListener 
=   null
  
while  (uploadListener  ==   null   ||  uploadListener.getTotalCurrent()  <=   0
    uploadListener 
= (UploadListener) session.getAttribute("uploadListener"); 
    out.print(
"."); 
    out.flush(); 
    Thread.sleep(
10); 
  }
 
  
long  total  =  uploadListener.getTotal(); 
  out.println(total); 
  
long  current; 
  out.flush(); 
  
while  ( true
    current 
= uploadListener.getTotalCurrent(); 
    
if (current >= total) 
      
break
    }
 
    out.println(
"<script type='text/javascript'>parent.setUploadProcess('" + total + "','" + current + "');</script>"); 
    out.flush(); 
    Thread.sleep(
10); 
  }
 
%>< script type = " text/javascript " > parent.closeUploadProcess(); </ script >

其中前面的循环,用来判断是否产生了上传的信息,如果没有则等待。
然后就是读取上传的信息,并计算后生成调用上级窗口的更新进度条的JS, 请注意out.print后面必须跟上out.flush,否则不会持续输出到客户端,也就不会看到连续的进度条变化。


总结:
上面的部分比较乱,我这里总结一下关键点。
1 在上传组件里面,把总大小和当前读取了的大小放到一个类里面,并持续更新,直到处理完毕
2 上传的进度类,放在session里面,供进度读取页面读取
3 进度读取页面,从session里面拿到数据,并返回结果。

有几个疑问解释一下。
1 由于Http协议决定了,必须等request处理完毕才会返回输出,所以不能在upload页面里进行处理进度的显示。我前面测试到1M左右的文件不成功,就是没有考虑到这个问题。所以必须单独用一个GET的程序进行读取
2 读取是一个持续不断的过程,因为上传大文件是很慢的!
3 如果你的应用服务器启用了GZIP压缩,是容器管理的,那么很不幸,因为容易必须拿到所有的数据,至少是一部分数据才会返回,所以造成我们返回的那些很少的字节经常会被截住,造成无法显示上传的连续过程。
  解决方法
  1) 关闭GZIP, 我想许多人不会这么做 
  2) 使用自定义的GZIP压缩,判断某些东西(比如URL),对他们不进行压缩处理

测试和下载地址: http://www.java2000.net/test/testUploadFile.jsp

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值