用Java进行file upload(带进度条)
我对Java 非常陌生,大部分时间都是在自学,所以我开始构build一个小程序。 我想创build一个可以从本地磁盘上select一个文件,并将其作为多部分/表单数据POST请求上传,但有一个进度条 。 显然,用户必须授予Java applet访问硬盘驱动器的权限。 现在我已经得到了第一部分的工作:用户可以使用JFileChooser
对象来select文件,该对象可以方便地返回一个File
对象。 但是我想知道接下来会发生什么。 我知道File.length()
会给我文件的总大小(以字节为单位),但是如何将选定的File
发送到Web,以及如何监视已发送的字节数? 提前致谢。
要使用HttpClient检查进度,请将MultipartRequestEntity包装在一个计数要发送的字节的位置。 包装如下:
import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import org.apache.commons.httpclient.methods.RequestEntity; public class CountingMultipartRequestEntity implements RequestEntity { private final RequestEntity delegate; private final ProgressListener listener; public CountingMultipartRequestEntity(final RequestEntity entity, final ProgressListener listener) { super(); this.delegate = entity; this.listener = listener; } public long getContentLength() { return this.delegate.getContentLength(); } public String getContentType() { return this.delegate.getContentType(); } public boolean isRepeatable() { return this.delegate.isRepeatable(); } public void writeRequest(final OutputStream out) throws IOException { this.delegate.writeRequest(new CountingOutputStream(out, this.listener)); } public static interface ProgressListener { void transferred(long num); } public static class CountingOutputStream extends FilterOutputStream { private final ProgressListener listener; private long transferred; public CountingOutputStream(final OutputStream out, final ProgressListener listener) { super(out); this.listener = listener; this.transferred = 0; } public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); this.transferred += len; this.listener.transferred(this.transferred); } public void write(int b) throws IOException { out.write(b); this.transferred++; this.listener.transferred(this.transferred); } } }
然后实现一个更新进度条的ProgressListener。
请记住,进度条更新不能在“事件派发线程”上运行。
简单的countingEntity不依赖于特定的实体types,而是扩展HttpEntityWrapped
:
package gr.phaistos.android.util; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import org.apache.http.HttpEntity; import org.apache.http.entity.HttpEntityWrapper; public class CountingHttpEntity extends HttpEntityWrapper { public static interface ProgressListener { void transferred(long transferedBytes); } static class CountingOutputStream extends FilterOutputStream { private final ProgressListener listener; private long transferred; CountingOutputStream(final OutputStream out, final ProgressListener listener) { super(out); this.listener = listener; this.transferred = 0; } @Override public void write(final byte[] b, final int off, final int len) throws IOException { //// NO, double-counting, as super.write(byte[], int, int) delegates to write(int). //super.write(b, off, len); out.write(b, off, len); this.transferred += len; this.listener.transferred(this.transferred); } @Override public void write(final int b) throws IOException { out.write(b); this.transferred++; this.listener.transferred(this.transferred); } } private final ProgressListener listener; public CountingHttpEntity(final HttpEntity entity, final ProgressListener listener) { super(entity); this.listener = listener; } @Override public void writeTo(final OutputStream out) throws IOException { this.wrappedEntity.writeTo(out instanceof CountingOutputStream? out: new CountingOutputStream(out, this.listener)); } }
我结束了一个开源的Java上传器小程序的绊脚石,并find了我需要知道的一切,在其代码。 这里有一个博客文章的链接以及源代码:
文章
源代码
侦听器返回的字节数量与原始文件大小不同。 所以,而不是transferred++
,我修改它,使transferred=len
; 即写入输出stream的实际字节数的长度。 而当我计算所添加的总字节数时,它等于由CountingMultiPartEntity.this.getContentLength();
返回的实际ContentLength
CountingMultiPartEntity.this.getContentLength();
public void write(byte[] b, int off, int len) throws IOException { wrappedOutputStream_.write(b,off,len); transferred=len; listener_.transferred(transferred); }
您可能会发现这篇文章有帮助。 它使用HttpClient和FileUpload来详细解释,两个apache项目都可以做你想做的事情。 它还包括代码示例。
请记住,当networking中的中间组件(例如,ISP的HTTP代理或服务器前的反向HTTP代理)比服务器更快地使用上传时,进度条可能会引起误解。
正如Vincent发表的文章所指出的那样,你可以使用Apache的commons来做到这一点。
less了剪切
DiskFileUpload upload = new DiskFileUpload(); upload.setHeaderEncoding(ConsoleConstants.UTF8_ENCODING); upload.setSizeMax(1000000); upload.setSizeThreshold(1000000); Iterator it = upload.parseRequest((HttpServletRequest) request).iterator(); FileItem item; while(it.hasNext()){ item = (FileItem) it.next(); if (item.getFieldName("UPLOAD FIELD"){ String fileName = item.getString(ConsoleConstants.UTF8_ENCODING); byte[] fileBytes = item.get(); } }
DiskFileUpload upload = new DiskFileUpload(); upload.setHeaderEncoding(ConsoleConstants.UTF8_ENCODING); upload.setSizeMax(1000000); upload.setSizeThreshold(1000000); Iterator it = upload.parseRequest((HttpServletRequest) request).iterator(); FileItem item; while(it.hasNext()){ item = (FileItem) it.next(); if (item.getFieldName("UPLOAD FIELD"){ String fileName = item.getString(ConsoleConstants.UTF8_ENCODING); byte[] fileBytes = item.get(); } }
只是我的2c值得:
这是基于tuler的答案(在写作时有错误)。 我稍微修改它,所以这里是我的版本的tuler和mmyers的答案(我似乎无法编辑他们的答案)。 我想试图使这一点更干净,更快。 除了这个bug(我在他们的回答中讨论的)之外,我在他们的版本中遇到的一个大问题就是每次写入都会创build一个新的CountingOutputStream
。 这在内存方面可能会非常昂贵 – 分配和垃圾收集量很大。 较小的问题是,它可以扩展MultipartEntity
时使用委托。 不知道为什么他们select这个,所以我以我更熟悉的方式做了。 如果有人知道这两种方法的优点/缺点,那就太棒了。 最后,FilterOutputStream#write(byte [],int,int)方法只是在循环中调用FilterOutputStream#write(byte)。 FOS文档build议使用子类覆盖此行为并使其更高效。 最好的方法是让底层的OutputStream处理写入请求。
import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; public class CountingMultiPartEntity extends MultipartEntity { private UploadProgressListener listener_; private CountingOutputStream outputStream_; private OutputStream lastOutputStream_; // the parameter is the same as the ProgressListener class in tuler's answer public CountingMultiPartEntity(UploadProgressListener listener) { super(HttpMultipartMode.BROWSER_COMPATIBLE); listener_ = listener; } @Override public void writeTo(OutputStream out) throws IOException { // If we have yet to create the CountingOutputStream, or the // OutputStream being passed in is different from the OutputStream used // to create the current CountingOutputStream if ((lastOutputStream_ == null) || (lastOutputStream_ != out)) { lastOutputStream_ = out; outputStream_ = new CountingOutputStream(out); } super.writeTo(outputStream_); } private class CountingOutputStream extends FilterOutputStream { private long transferred = 0; private OutputStream wrappedOutputStream_; public CountingOutputStream(final OutputStream out) { super(out); wrappedOutputStream_ = out; } public void write(byte[] b, int off, int len) throws IOException { wrappedOutputStream_.write(b,off,len); ++transferred; listener_.transferred(transferred); } public void write(int b) throws IOException { super.write(b); } } }
查看HTTP客户端将file upload到networking。 它应该能够做到这一点。 我不确定如何获取进度条,但是会涉及到以某种方式查询API。
Apache常见的是非常好的select。 Apache通用可以让你configuration下面的东西。
- configuration(xml文件)最大文件大小/上传文件大小
- 目标path(保存上传文件的位置)
- 设置温度。 文件夹交换文件,这样file upload会很快。
从其他答案,你可以重写AbstractHttpEntity
类子项或实现public void writeTo(OutputStream outstream)
方法,如果你不想创build一个类。
使用FileEntity
实例的示例:
FileEntity fileEntity = new FileEntity(new File("img.jpg")){ @Override public void writeTo(OutputStream outstream) throws IOException { super.writeTo(new BufferedOutputStream(outstream){ int writedBytes = 0; @Override public synchronized void write(byte[] b, int off, int len) throws IOException { super.write(b, off, len); writedBytes+=len; System.out.println("wrote: "+writedBytes+"/"+getContentLength()); //Or anything you want [using other threads] } }); } };