Загрузка файла с Java (с индикатором выполнения)
Я чрезвычайно новичок в Java, и в основном я учил себя на ходу, поэтому я начал создавать апплет. Я хотел бы сделать тот, который может выбрать файл с локального диска и загрузить его как POST-запрос multipart / form-data, но с индикатором выполнения. Очевидно, что пользователь должен предоставить разрешение апплету Java для доступа к жесткому диску. Теперь у меня уже работает первая часть: пользователь может выбрать файл, используя JFileChooser
объект, который удобно возвращает File
объект. Но мне интересно, что будет дальше. я знаю это File.length()
даст мне общий размер в байтах файла, но как мне отправить выбранный File
в Интернет, и как я могу контролировать, сколько байтов было отправлено? Заранее спасибо.
11 ответов
Я наткнулся на апплет Java-загрузчика с открытым исходным кодом и нашел все, что мне нужно было знать в его коде. Вот ссылки на сообщение в блоге, описывающее его, а также источник:
Чтобы проверить прогресс с помощью 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 не будет зависеть от конкретного типа объекта, а скорее будет расширяться 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));
}
}
Количество байтов, возвращаемых слушателем, отличается от исходного размера файла. Таким образом, вместо того, чтобы иметь transferred++
Я изменил это так, чтобы transferred=len
; это длина фактического количества байтов, записываемых в выходной поток. И когда я вычисляю сложение всех переданных байтов, оно равно фактическому 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);
}
Имейте в виду, что индикатор выполнения может вводить в заблуждение, когда промежуточный компонент в сети (например, HTTP-прокси интернет-провайдера или обратный HTTP-прокси перед сервером) потребляет загрузку быстрее, чем сервер.
Вы можете найти эту статью полезной. Он подробно объясняет, как HttpClient и FileUpload, оба проекта apache, делают то, что вы хотите. Он также включает примеры кода.
Как отмечается в статье, опубликованной Винсентом, вы можете использовать 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();
}
}
Просто мой 2с стоит:
Это основано на ответе Тулера (есть ошибка во время написания). Я немного изменил его, так что вот моя версия ответа tuler и mmyers (кажется, я не могу отредактировать их ответ). Я хотел попытаться сделать это немного чище и быстрее. Помимо ошибки (которую я обсуждаю в комментариях к их ответу), у меня большая проблема с их версией в том, что она создает новую CountingOutputStream
с каждой записью. Это может стать очень дорогим с точки зрения памяти - тонны выделений и сборщиков мусора. Меньшая проблема заключается в том, что используется делегат, когда он может просто расширить MultipartEntity
, Не уверен, почему они выбрали это, поэтому я сделал это так, как мне было лучше знакомо. Если кто-нибудь знает плюсы / минусы двух подходов, это было бы здорово. Наконец, метод FilterOutputStream#write(byte[], int,int) просто вызывает в цикле запись FilterOutputStream#write(byte). Документация FOS рекомендует подклассам переопределять это поведение и делать его более эффективным. Лучший способ сделать это здесь - позволить исходному 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-клиент для загрузки файла в Интернет. Это должно быть в состоянии сделать это. Я не уверен, как получить индикатор выполнения, но это будет связано с тем, чтобы запросить этот API как-то.
Из других ответов вы можете просто переопределить 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]
}
});
}
};
Apache common - очень хороший вариант. Apache common позволяет настроить следующие вещи.
- Настройте (файл xml) максимальный размер файла / размер загружаемого файла
- Путь к месту назначения (где сохранить загруженный файл)
- Установите темп. папка, чтобы поменять файл, чтобы загрузка файла была быстрой.