Java開發FTP功能的apache工具包,小心使用為妙
阿新 • • 發佈:2019-02-13
專案是一個報表系統,使用apache-commons-net網路工具包實現檔案上傳與下載。實際測試中報表數量比較小,沒有發現大問題,並且開發時也是本地開發,本地FTP伺服器處理。後來測試發現,該處理使用的是單位元組不重新整理讀取模式,一個檔案如果幾百M,讀入緩衝位元組流的資料也會在記憶體中佔用幾百個M,這一點實際開發測試可以斷點除錯,使用VisualVM監控即可。
檢視FTP上傳下載原始碼,發現可以再FTPClient注入緩衝位元組大小,實際測試8*1024效能比較好:
ftpClient.changeWorkingDirectory(new String(path.getBytes("utf-8"), "ISO8859-1")); ftpClient.setFileType(FTP.BINARY_FILE_TYPE); // long start = System.currentTimeMillis(); ftpClient.setBufferSize(1024 * 8); ftpClient.storeFile(new String(fileName.getBytes("utf-8"), "ISO8859-1"), input); // System.out.println(System.currentTimeMillis()-start);
設定後,發現效率並沒有顯著提高,檢視storeFile檔案上傳原始碼了,裡面的核心處理還記基於流的讀寫,如下所示:
其中的protected boolean _storeFile(String command, String remote, InputStream local) throws IOException { Socket socket = _openDataConnection_(command, remote); if (socket == null) { return false; } OutputStream output = getBufferedOutputStream(socket.getOutputStream()); if (__fileType == ASCII_FILE_TYPE) { output = new ToNetASCIIOutputStream(output); } CSL csl = null; if (__controlKeepAliveTimeout > 0) { csl = new CSL(this, __controlKeepAliveTimeout, __controlKeepAliveReplyTimeout); } // Treat everything else as binary for now try { Util.copyStream(local, output, getBufferSize(), CopyStreamEvent.UNKNOWN_STREAM_SIZE, __mergeListeners(csl), false); } catch (IOException e) { Util.closeQuietly(socket); // ignore close errors here if (csl != null) { csl.cleanUp(); // fetch any outstanding keepalive replies } throw e; } output.close(); // ensure the file is fully written socket.close(); // done writing the file if (csl != null) { csl.cleanUp(); // fetch any outstanding keepalive replies } // Get the transfer response boolean ok = completePendingCommand(); return ok; }
Util.copyStream
是主要的處理方法,這裡的最後一個引數為false,表示是否實時flush,但是這個方法的屬性設定 包中沒有外部介面,坑爹了。進去看一看這個上傳的原始碼:
public static final long copyStream(InputStream source, OutputStream dest, int bufferSize, long streamSize, CopyStreamListener listener, boolean flush) throws CopyStreamException { int bytes; long total = 0; <strong>byte[] buffer = new byte[bufferSize >= 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE];</strong> try { while ((bytes = source.read(buffer)) != -1) { // Technically, some read(byte[]) methods may return 0 and we cannot // accept that as an indication of EOF. if (bytes == 0) { bytes = source.read(); if (bytes < 0) { break; } dest.write(bytes); <strong>if(flush) { dest.flush(); }</strong> ++total; if (listener != null) { listener.bytesTransferred(total, 1, streamSize); } continue; } dest.write(buffer, 0, bytes); <strong>if(flush) { dest.flush(); }</strong> total += bytes; if (listener != null) { listener.bytesTransferred(total, bytes, streamSize); } } } catch (IOException e) { throw new CopyStreamException("IOException caught while copying.", total, e); } return total; }
看看加粗的程式碼,一個是緩衝流大小,一個是是否重新整理流。找了半天flush沒有注入介面,沒辦法,最後想通過繼承的方式,在應用中實現這個類,並重寫方法,最後發現,難度有點大,依賴屬性太多。最好把FTPClient直接拉到自己應用中,全部複製吧,如下:
public class<strong> FTPClient extends org.apache.commons.net.ftp.FTPClient</strong> {
/**
* The system property ({@value} ) which can be used to override the system type.<br/>
* If defined, the value will be used to create any automatically created parsers.
*
* @since 3.0
*/
public static final String FTP_SYSTEM_TYPE = "org.apache.commons.net.ftp.systemType";
/**
* The system property ({@value} ) which can be used as the default system type.<br/>
* If defined, the value will be used if the SYST command fails.
*
其它程式碼照搬吧。不管怎麼樣,最後的傳輸效率大大提高,而且是時刻重新整理,減輕了伺服器壓力。
可能對此還不是深入理解,遺漏出望指教。