Tôi là cực kỳ mới đối với Java và hầu như chỉ dạy tôi khi tôi đi, vì vậy tôi đã bắt đầu xây dựng một applet. Tôi muốn tạo một tệp có thể chọn tệp từ đĩa cục bộ và tải tệp đó lên dưới dạng yêu cầu POST nhiều biểu mẫu/dữ liệu biểu mẫu nhưng với thanh tiến trình. Rõ ràng người dùng phải cấp quyền cho Java applet để truy cập vào ổ đĩa cứng. Bây giờ tôi đã có phần đầu tiên làm việc: người dùng có thể chọn một tập tin bằng cách sử dụng một đối tượng JFileChooser
, mà thuận tiện trả về một đối tượng File
. Nhưng tôi tự hỏi điều gì sẽ xảy ra tiếp theo. Tôi biết rằng File.length()
sẽ cho tôi tổng kích thước tính bằng byte của tệp, nhưng làm cách nào để gửi File
được chọn đến trang web và làm cách nào để theo dõi số lượng byte đã được gửi? Cảm ơn trước.Tải lên tệp bằng Java (có thanh tiến trình)
Trả lời
Tôi đã tình cờ gặp một ứng dụng trình tải lên Java nguồn mở và tìm thấy mọi thứ tôi cần biết trong mã của nó. Dưới đây là các liên kết đến một bài viết trên blog mô tả nó cũng như các nguồn:
Nhìn vào HTTP Client để tải lên tệp lên web. Nó sẽ có thể làm điều đó. Tôi không chắc chắn làm thế nào để có được thanh tiến trình, nhưng nó sẽ liên quan đến truy vấn API đó bằng cách nào đó.
Bạn có thể tìm thấy điều này article hữu ích. Nó giải thích chi tiết bằng cách sử dụng HttpClient và FileUpload, cả hai dự án apache để làm những gì bạn muốn. Nó cũng bao gồm các mẫu mã.
Như đã lưu ý trong bài viết mà Vincent đã đăng, bạn có thể sử dụng Apache commons để thực hiện việc này.
nhỏ snipped
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();
}
}
Hãy ghi nhớ rằng thanh tiến trình có thể là sai lầm khi một thành phần trung gian trong mạng (ví dụ, proxy HTTP của một ISP, hoặc một proxy HTTP ngược ở phía trước của máy chủ) tiêu thụ tải lên của bạn nhanh hơn máy chủ.
Để kiểm tra tiến độ bằng cách sử dụng HttpClient, hãy quấn MultipartRequestEntity xung quanh một bộ đếm byte được gửi. Wrapper dưới đây:
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);
}
}
}
Sau đó, triển khai ProgressListener cập nhật thanh tiến trình.
Hãy nhớ rằng bản cập nhật thanh tiến trình không được chạy trên Chủ đề công văn sự kiện.
Bạn có thể vui lòng đưa ra một ví dụ về ProgressListener sẽ cập nhật các thành phần Swing – kilonet
tại sao CountingMultipartRequestEntity phải được bao quanh MultipartRequestEntity? – kilonet
Các bạn, câu trả lời của bạn không chính xác. Đối với nhiều trường hợp, điều này sẽ nhân đôi số byte được gửi. FilterOutputStream # write (byte [], int, int) chỉ đơn giản gọi FOS.write (byte) trong một vòng lặp, vì vậy bạn đang đếm đôi tất cả các byte. Ngoài ra, tài liệu FOS khuyên bạn nên thay đổi hành vi này vì nó không hiệu quả. Xem câu trả lời của tôi, nơi tôi lưu dòng cơ bản và gọi nó là phương thức write (byte [], int, int). – Hamy
Apache chung là tùy chọn rất tốt. Apache phổ biến cho phép bạn định cấu hình các mục sau.
- Configure (xml file) kích thước tập tin/tải lên kích thước tập tin tối đa
- Đường dẫn đích (nơi để lưu các tập tin tải lên)
- Đặt temp. thư mục để trao đổi các tập tin, do đó, tập tin tải lên sẽ được nhanh chóng.
Thành phần nào của Apache Commons? –
Chỉ cần 2c tôi có giá trị:
này được dựa tắt của câu trả lời tuler của (có một lỗi tại thời điểm viết). Tôi sửa đổi nó một chút, vì vậy đây là phiên bản của tôi của tuler và mmyers trả lời (tôi dường như không thể chỉnh sửa câu trả lời của họ). Tôi muốn cố gắng làm cho nó sạch hơn và nhanh hơn một chút.Bên cạnh lỗi (mà tôi thảo luận trong các bình luận về câu trả lời của họ), vấn đề lớn mà tôi có với phiên bản của họ là nó tạo ra một CountingOutputStream
mới với mỗi lần viết. Điều này có thể rất tốn kém về bộ nhớ - tấn phân bổ và thu gom rác thải. Vấn đề nhỏ hơn là sử dụng một đại biểu khi nó chỉ có thể mở rộng MultipartEntity
. Không chắc tại sao họ lại chọn nó, vì thế tôi đã làm theo cách tôi quen thuộc hơn. Nếu bất cứ ai biết ưu/nhược điểm của hai cách tiếp cận đó sẽ là tuyệt vời. Cuối cùng, phương thức FilterOutputStream # write (byte [], int, int) chỉ cần gọi hàm FilterOutputStream # write (byte) trong một vòng lặp. FOS documentation đề xuất các lớp con ghi đè hành vi này và làm cho hiệu quả hơn. Cách tốt nhất để làm điều đó ở đây là để cho OutputStream bên dưới xử lý yêu cầu viết.
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);
}
}
}
Điều này có nhiều vấn đề hơn so với cái nhỏ kia. – ColinD
Một countingEntity đơn giản sẽ không phụ thuộc vào một loại thực thể cụ thể nhưng thay vì mở rộng 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));
}
}
Tôi sẽ triển khai điều này bằng cách nào? bạn có thể tag cho tôi nếu bạn trả lời :) –
như \t \t \t ProgressListener nghe này = ProgressListener mới() \t \t \t { \t \t \t \t @ Override \t \t \t \t public void chuyển (num dài) \t \t \t \t { \t \t \t \t \t parentTask.publicPublishProgress (num); \t \t \t \t} \t \t \t}; \t \t \t StringEntity se = new StringEntity (XML); \t \t \t CountingHttpEntity ce = new CountingHttpEntity (se, listener); \t \t \t httppost.setEntity (ce); –
Lượng byte trả về bởi người nghe là khác nhau từ kích thước tập tin gốc. Vì vậy, thay vì có transferred++
, tôi đã sửa đổi nó để transferred=len
; đó là độ dài của số byte thực tế được ghi vào luồng đầu ra. Và khi tôi tính toán việc bổ sung tổng byte chuyển nó bằng với thực tế ContentLength
trả về bởi CountingMultiPartEntity.this.getContentLength();
public void write(byte[] b, int off, int len) throws IOException {
wrappedOutputStream_.write(b,off,len);
transferred=len;
listener_.transferred(transferred);
}
Từ câu trả lời khác bạn chỉ có thể ghi đè lên AbstractHttpEntity
trẻ em lớp hoặc triển khai public void writeTo(OutputStream outstream)
phương pháp mà bạn đang sử dụng nếu không muốn tạo một lớp học.
Một ví dụ sử dụng một trường hợp 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]
}
});
}
};
Đây chỉ là một bình luận bên, tôi ghét nó khi các công ty sử dụng các applet để cung cấp một giao diện tải lên tốt hơn và một trong html. Tôi cảm thấy đây là sự cố trình duyệt web. Bạn không thể tin tưởng một cách mù quáng vào các applet yêu cầu sự cho phép, nhưng hầu hết người dùng đều cho phép nó. Đáng buồn nhưng là sự thật. – Pyrolistical
@Pyrolistical Tôi đồng ý, nhưng rất tiếc, trình duyệt hỗ trợ tải lên tệp là _still_ horrible. Nếu bạn thêm vào nhu cầu/mong muốn thực hiện bất kỳ xử lý phía máy khách nào trên các tệp đó, bạn không có bất kỳ tùy chọn nào khác ngoài việc sử dụng một applet hoặc flash. – cdeszaq