1. 程式人生 > >Java開發FTP功能的apache工具包,小心使用為妙

Java開發FTP功能的apache工具包,小心使用為妙

專案是一個報表系統,使用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.
	 * 
其它程式碼照搬吧。不管怎麼樣,最後的傳輸效率大大提高,而且是時刻重新整理,減輕了伺服器壓力。

可能對此還不是深入理解,遺漏出望指教。