【问题标题】:File Upload with Java (with progress bar)使用 Java 上传文件(带进度条)
【发布时间】:2010-09-20 06:17:55
【问题描述】:

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

【问题讨论】:

  • 这只是一个旁注,我讨厌公司使用小程序来提供更好的上传界面和 html 中的界面。我觉得这是一个网络浏览器问题。您不能盲目相信请求许可的小程序,但大多数用户还是会给予许可。悲伤但真实。
  • @Pyrolistical 我同意,但不幸的是,浏览器对上传文件的支持仍然很糟糕。如果您需要/希望对这些文件进行任何客户端处理,除了使用小程序或闪存之外,您别无选择。

标签: java file-io upload progress-bar


【解决方案1】:

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

public void write(byte[] b, int off, int len) throws IOException {
    wrappedOutputStream_.write(b,off,len);
    transferred=len;
    listener_.transferred(transferred);
}

【讨论】:

    【解决方案2】:

    如果不想创建类,您可以从其他答案中重写 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]
                }
            });
        }
    
    };
    

    【讨论】:

      【解决方案3】:

      更简单的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));
          }
      }
      

      【讨论】:

      • 我将如何实现这个?如果你回答可以标记我吗:)
      • 喜欢这个 ProgressListener listener = new ProgressListener() { @Override public void transfer(long num) { parentTask.publicPublishProgress(num); } }; StringEntity se = new StringEntity(XML); CountingHttpEntity ce = new CountingHttpEntity(se, listener); httppost.setEntity(ce);
      【解决方案4】:

      要使用 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。
      请记住,进度条更新不能在 Event Dispatch Thread 上运行。

      【讨论】:

      • 为什么 CountingMultipartRequestEntity 应该包裹在 MultipartRequestEntity 周围?
      • 各位,你的答案不正确。在许多情况下,这将重复计算所有发送的字节。 FilterOutputStream#write(byte[],int,int) 只是在循环中调用 FOS.write(byte) ,因此您对所有字节进行了重复计算。此外,FOS 文档建议更改此行为,因为它效率不高。请参阅我的答案,我在其中保存基础流并将其称为 write(byte[],int,int) 方法。
      • @Harny 我回答了这个问题,我也修复了这个答案以做正确的事情。
      • 如果有人遇到同样的问题,您要使用此特定示例的 .jar 是 commons-httpclient-3.0.1 ,它作为存档副本存储在 Apache HttpClient 项目页面上。较新的版本似乎没有此示例使用的界面。
      • 很棒的答案。如果您正在寻找上述代码的调用方,我发现本教程可以帮助我理解双方。 toolongdidntread.com/android/…
      【解决方案5】:

      只是我的 2c 价值:

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

      【讨论】:

      • 这比另一个小问题要多得多。
      【解决方案6】:

      我最终偶然发现了一个开源 Java 上传器小程序,并在其代码中找到了我需要知道的一切。以下是描述它的博客文章的链接以及来源:

      Article
      Source Code

      【讨论】:

      • 源代码链接已失效。
      • 不足为奇,因为我的问题是 12 岁。从那时起,情况发生了巨大变化,所以如果我是你,我绝对不会在 2020 年使用 Java 小程序来处理文件上传。首先,我认为这些现在通常被主流浏览器阻止,其次,我当时所追求的功能现在在 HTML5 中得到了完全支持。根本不需要涉及Java。 2008 年的必要做法是今天的糟糕做法。
      • 我不是这个意思,哈哈。这只是对使用没有 S.O. 内容的链接的评论。因为死链接,但我没有在第一条评论中说清楚。但实际上我正在研究上传反馈的方法,而不是使用某种 gui,如 applet、swing、javafx 甚至 HTML。问候!
      【解决方案7】:

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

      1. 配置(xml文件)最大文件大小/上传文件大小
      2. 目标路径(上传文件的保存位置)
      3. 设置温度。文件夹来交换文件,这样文件上传会很快。

      【讨论】:

      • Apache Commons 组件中的哪一个?
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

        正如文森特发布的文章所述,您可以使用 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(); } }

        【讨论】:

          【解决方案10】:

          查看HTTP Client 以将文件上传到网络。它应该能够做到这一点。我不确定如何获取进度条,但它会涉及以某种方式查询该 API。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-05-21
            • 2011-09-11
            • 2011-06-01
            • 1970-01-01
            • 2012-07-20
            • 2012-09-24
            • 2015-10-19
            • 1970-01-01
            相关资源
            最近更新 更多