用Java上传文件(带进度条)

用Java上传文件(带进度条),java,file-io,upload,progress-bar,Java,File Io,Upload,Progress Bar,我对Java非常陌生,大部分时间我只是自学,所以我开始构建一个小程序。我想制作一个可以从本地磁盘选择一个文件,并将其作为多部分/表单数据POST请求上传,但带有进度条的文件。显然,用户必须授予Java小程序访问硬盘的权限。现在我已经完成了第一部分的工作:用户可以使用JFileChooser对象选择一个文件,该对象方便地返回一个file对象。但我想知道接下来会发生什么。我知道File.length()将提供文件的总大小(以字节为单位),但如何将选定的文件发送到web,以及如何监控已发送的字节数?提

我对Java非常陌生,大部分时间我只是自学,所以我开始构建一个小程序。我想制作一个可以从本地磁盘选择一个文件,并将其作为多部分/表单数据POST请求上传,但带有进度条的文件。显然,用户必须授予Java小程序访问硬盘的权限。现在我已经完成了第一部分的工作:用户可以使用
JFileChooser
对象选择一个文件,该对象方便地返回一个
file
对象。但我想知道接下来会发生什么。我知道
File.length()
将提供文件的总大小(以字节为单位),但如何将选定的
文件发送到web,以及如何监控已发送的字节数?提前感谢。

查找以将文件上载到web。它应该能够做到这一点。我不确定如何获取进度条,但它会以某种方式查询该API。

正如Vincent发布的文章所指出的,您可以使用Apache commons来实现这一点

小剪


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();
    }
}

请记住,当网络中的中间组件(例如,ISP的HTTP代理或服务器前面的反向HTTP代理)比服务器更快地使用您的上载时,进度条可能会产生误导。

要使用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。

请记住,进度条更新不能在事件调度线程上运行。

Apache common是一个非常好的选择。 apachecommon允许您配置以下内容

  • 配置(xml文件)最大文件大小/上载文件大小
  • 目标路径(保存上载文件的位置)
  • 设置温度。文件夹来交换文件,这样文件上传会很快
    我最终无意中发现了一个开源Java uploader小程序,并在它的代码中找到了我需要知道的一切。以下是一篇博客文章的链接,介绍了该文章及其来源:


    只是我的2c价值:

    这是基于tuler的答案(在撰写本文时有一个bug)。我稍微修改了一下,所以这里是我对tuler和mmyers答案的版本(我似乎无法编辑他们的答案)。我想试着让它更干净更快。除了这个bug(我在他们回答的评论中讨论过),我对他们版本的一个大问题是,它在每次写入时都会创建一个新的
    CountingOutputStream
    。这在内存方面可能会非常昂贵—大量的分配和垃圾收集。较小的问题是,当is可以只扩展
    多端口时,它会使用委托。我不知道他们为什么选择这个,所以我用了一种我更熟悉的方式。如果有人知道这两种方法的利弊,那就太好了。最后,FilterOutputStream#write(byte[],int,int)方法只是在循环中调用FilterOutputStream#write(byte)。建议子类重写此行为并使其更有效。最好的方法是让底层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);
            }
        }
    }
    

    更简单的countingEntity不依赖于特定的实体类型,而是扩展
    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));
        }
    }
    

    侦听器返回的字节数与原始文件大小不同。因此,我没有使用
    transfer++
    ,而是对它进行了修改,使
    transfer=len
    ;这是写入输出流的实际字节数的长度。当我计算传输的总字节数之和时,它等于
    CountingMultiPartEntity返回的实际
    ContentLength


    从其他答案中,如果您不想创建类,您只需覆盖所使用的
    AbstractHttpEntity
    类子级或实现
    public void writeTo(OutputStream outstream)
    方法

    使用
    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]
                }
            });
        }
    
    };
    

    这只是一个旁白,我讨厌公司使用applet提供更好的上传界面和html格式的上传界面。我觉得这是一个网络浏览器的问题。你不能盲目地信任那些请求许可的小程序,但大多数用户都会这样做。我同意,但不幸的是,浏览器对上传文件的支持仍然很糟糕。如果您添加了对这些文件进行任何客户端处理的需要/愿望,那么除了使用小程序或flash之外,您没有其他选择。为什么要将CountingMultipartRequestEntity包装在MultipartRequestEntity周围?伙计们,您的答案不正确。对于许多情况,这将加倍计算所有发送的字节数。FilterOutputStream#write(byte[],int,int)只是在一个循环中调用FOS.write(byte),所以您要重复计算所有字节。此外,FOS文档建议更改此行为,因为这样做效率不高。请参阅我的答案,在这里我保存了底层流并调用它的write(byte[],int,int)方法。@Harny我回答了这个问题,我也修正了这个答案,以便做正确的事情。如果有人遇到同样的问题,您要使用这个特定示例的.jar是commons-httpclient-3.0.1,它作为存档副本存储在Apache HttpClient项目页面上。较新的版本似乎没有本例使用的界面。答案很棒。如果您正在寻找上述代码的调用方,我找到了本教程,它帮助我理解了这两个方面。这个问题比另一个小问题多得多。我将如何实现这个?如果您回答:)像这样ProgressListener=newprogressListener(){@Override public void transfer(long num){parentTask.publicPublishProgress(num);}};StringEntity se=新的StringEntity(XML);CountingHttpEntity ce=新的CountingHttpEntity(se,列表
    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]
                }
            });
        }
    
    };