1. 程式人生 > >java實現FTP上傳(檔案)、下載(檔案、資料夾、資料夾遞迴)、刪除(檔案、資料夾遞迴)

java實現FTP上傳(檔案)、下載(檔案、資料夾、資料夾遞迴)、刪除(檔案、資料夾遞迴)

提示:必須先保證有在FTP上上傳、下載、刪除的許可權!

本文結構 ---- 先給出測試樣例(圖片版),再給出工具類程式碼(文字版)!

上傳測試

注意:.uploadFile(String remoteDir, String remoteFileName, File file)中,remoteDir統一寫(以“/”分割的)絕對路徑;         remoteDir可以是不存在的目錄(會自動建立);更多詳情見程式碼註釋!

執行主函式:可看到上傳成功(ie瀏覽器開啟ftp://10.2.6.16:22/test/):

多次上傳,得到現有目錄(ie瀏覽器開啟ftp://10.2.6.16:22/test/):

可以看見:/test/下有a、b、c這三個目錄(每個目錄下都有各自的檔案);還有SQL語句.txt、                justry_deng.htmlJustryDeng.shuaige這三個檔案了。

由此可見:上傳成功!

單檔案下載測試

注:.downloadFile(String remoteDirOrRemoteFile, String localDir)中,remoteDirOrRemoteFile統一      寫(以“/”分割的)絕對路徑;localDir可以是不存在的目錄(會自動建立);更多詳情見程式碼註釋!

注:如果remoteDirOrRemoteFile

不存在,那麼不會下載下來任何東西。

執行主函式,可看到下載成功:

注:下載下來的檔案的內容也是正確無亂碼的。

:.recursiveDownloadFile(String remoteDirOrRemoteFile, String localDir)方法也支援單檔案下載

由此可見:檔案下載成功!

指定目錄下所有檔案下載(不包括:下載該目錄下的資料夾及其內部內容)測試

注:.downloadFile(String remoteDirOrRemoteFile, String localDir)中,remoteDirOrRemoteFile統一      寫(以“/”分割的)絕對路徑

;localDir可以是不存在的目錄(會自動建立);更多詳情見程式碼註釋!

執行主函式,可看到下載成功:

注:可以看到,不僅把FTP 的/test/目錄下的檔案下載下來了,並沒有下載/test/目錄下的a、b、c資料夾。

由此可見:指定目錄下所有檔案下載成功!

指定目錄下所有檔案下載(還包括:下載該目錄下的資料夾及其內部內容)測試

注:.recursiveDownloadFile(String remoteDirOrRemoteFile, String localDir)中,remoteDirOrRemoteFile統一      寫(以“/”分割的)絕對路徑;localDir可以是不存在的目錄(會自動建立);更多詳情見程式碼註釋!

執行主函式,可看到下載成功:

注:可以看到,不僅把FTP 的/test/目錄下的有SQL語句.txtjustry_deng.htmlJustryDeng.shuaige這三個檔案      下載下來了,還把/test/目錄下的abc資料夾(以及每個資料夾裡面的檔案、子資料夾)都下載下來了。

由此可見:指定目錄下所有檔案、資料夾(遞迴)下載成功!

單檔案刪除測試

注:.deleteBlankDirOrFile(String deletedBlankDirOrFile)中,deletedBlankDirOrFile統一寫(以“/”分割的)絕對路徑,      可以是一個明確的要刪除的檔案全路徑;也可以是一個空的資料夾路徑;更多詳情見程式碼註釋!

執行主函式,可看到下載成功(ie瀏覽器開啟ftp://10.2.6.16:22/):

注:可以看到,FTP 的/test/目錄下的JustryDeng.shuaige檔案已經被刪除了。

由此可見:單檔案刪除成功!

指定目錄刪除(包括:刪除該目錄下的資料夾及其內部內容)測試

刪除前,FTP是有/test/目錄的,且其內容為:

呼叫FTP工具類刪除:

執行主函式後,ie瀏覽器開啟ftp://10.2.6.16:22/,可以看見:

FTP目錄下找不到test目錄了,即:刪除test資料夾(即裡面的內容)成功!

由此可見:資料夾(含不含內容都可以)刪除成功!

>>> 進入正題

本人測試時,軟硬體環境: JDK1.8、Windows、Eclipse、SpringBoot、FTP伺服器(windows下的)

準備工作:在pom.xml中引入依賴

<!-- 
    Apache Commons Net library contains a collection of network utilities 
	and protocol implementations. Supported protocols include: Echo, Finger, 
	FTP, NNTP, NTP, POP3(S), SMTP(S), Telnet, Whois 
-->
<dependency>
	<groupId>commons-net</groupId>
	<artifactId>commons-net</artifactId>
	<version>3.6</version>
</dependency>

FTP工具類

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

/**
 * FTP上傳、下載、刪除 工具類
 * 約定:統一使用絕對路徑
 *
 * @author JustryDeng
 * @DATE 2018年9月26日 上午10:38:46
 */
public class FTPUtils {
	
    /** ftp伺服器地址 */
    private String hostname;

    /** 埠號 */
    private Integer port;

    /** ftp登入賬號 */
    private String username;

    /** ftp登入密碼 */
    private String password;
    
    /**
     * 命令 語句 編碼(控制發出去的命令的編碼) 
     * 如:在刪除時,明明提示刪除成功了,但是FTP上的檔案依然存在
     *   這很可能就是因為:發出去的指令由於此處的編碼的源因,亂碼了;導
     *                致刪除了一個不存在的檔案,所以返回的提示為成功,
     *                但是FTP上的要刪除的檔案依然存在
     *                
     * 注:根據不同的(Server/Client)情況,這裡靈活設定
     */
    private String sendCommandStringEncoding = "ISO-8859-1";
    
    /**
     * 下載檔案,檔名encode編碼
     * 
     * 注:根據不同的(Server/Client)情況,這裡靈活設定
     */
    private String downfileNameEncodingParam1 = "ISO-8859-1";
    
    /**
     * 下載檔案,檔名decode編碼
     * 
     * 注:根據不同的(Server/Client)情況,這裡靈活設定
     */
    private String downfileNameDecodingParam2 = "GBK";
    
    /**
     * 設定檔案傳輸形式(使用FTP類靜態常量賦值即可)
     * 
     * 注:根據要下載上傳的檔案情況,這裡靈活設定
     */
    private Integer transportFileType = FTP.BINARY_FILE_TYPE;
    
    /** FTP客戶端引用 */
    private FTPClient ftpClient = null;
    
    public FTPClient getFtpClient() {
		return ftpClient;
	}

	private FTPUtils(String hostname, Integer port, String username, String password) {
		super();
		this.hostname = hostname;
		this.port = port;
		this.username = username;
		this.password = password;
	}
	

	/**
	 * 設定下載時,檔名的編碼
	 * 即:new String(file.getName().getBytes(param1), param2) 中的param1
	 * 注:根據不同的(Server/Client)情況,這裡靈活設定
	 *
	 * @DATE 2018年9月26日 下午7:34:26
	 */
    public void setDownfileNameEncodingParam1(String downfileNameEncodingParam1) {
		this.downfileNameEncodingParam1 = downfileNameEncodingParam1;
	}

	/**
	 * 設定下載時,檔名的編碼
	 * 即:new String(file.getName().getBytes(param1), param2) 中的param2
	 * 注:根據不同的(Server/Client)情況,這裡靈活設定
     *
	 * @DATE 2018年9月26日 下午7:34:26
	 */
	public void setDownfileNameDecodingParam2(String downfileNameDecodingParam2) {
		this.downfileNameDecodingParam2 = downfileNameDecodingParam2;
	}
	

	/**
	 * 設定檔案傳輸形式 -> 二進位制
	 * 根據自己的時機情況,選擇FTP.BINARY_FILE_TYPE或FTP.ASCII_FILE_TYPE等即可
	 * 注:根據不同的檔案情況,這裡靈活設定
	 *
	 * @DATE 2018年9月27日 上午9:48:51
	 */
	public void setTransportFileType(Integer transportFileType) {
		if( transportFileType != null) {
		    this.transportFileType = transportFileType;
		}
	}

	/**
     * @param hostname
     *            FTPServer ip
     * @param port
     *            FTPServer 埠
     * @return FTPUtils例項
     * @DATE 2018年9月26日 下午4:35:40
     */
    public static FTPUtils getFTPUtilsInstance(String hostname, Integer port) {
    	return getFTPUtilsInstance(hostname, port, null, null);
    }
    

   /** 
    * FTP的上傳、下載、刪除,底層還是 傳送得命令語句; 這裡就設定傳送的命令語句的編碼
    * 
    *  Saves the character encoding to be used by the FTP control connection.
    *  Some FTP servers require that commands be issued in a non-ASCII
    *  encoding like UTF-8 so that filenames with multi-byte character
    *  representations (e.g, Big 8) can be specified.
    */
	public void setSendCommandStringEncoding(String sendCommandStringEncoding) {
		this.sendCommandStringEncoding = sendCommandStringEncoding;
	}

	/**
     * @param hostname
     *            FTPServer ip
     * @param port
     *            FTPServer 埠
     * @param username
     *            使用者名稱
     * @param password
     *            密碼
     * @return FTPUtils例項
     * @DATE 2018年9月26日 下午4:39:02
     */
    public static FTPUtils getFTPUtilsInstance(String hostname, Integer port, String username, String password) {
    	return new FTPUtils(hostname, port, username, password);
    }

    /**
     * 初始化FTP伺服器
     * 注:連線FTP伺服器後,當前目錄(即:session)預設處於根目錄“/”下;
     *    所以如果一開始就是相對路徑的話,那麼是相對根目錄的
     *
     * @throws IOException
     * @DATE 2018年9月26日 下午1:37:14
     */
    private void initFtpClient() throws IOException {
        ftpClient = new FTPClient();
        ftpClient.setControlEncoding(sendCommandStringEncoding);
        System.out.println(" FTPUtils -> connecting FTPServer -> " + this.hostname + ":" + this.port); 
        // 連線ftp伺服器
        ftpClient.connect(hostname, port);
        if(username != null && password != null) {
        	//登入ftp伺服器
            ftpClient.login(username, password); 
        }
        // 設定檔案傳輸形式
        ftpClient.setFileType(transportFileType);
        // Returns the integer value of the reply code of the last FTP reply.
        int replyCode = ftpClient.getReplyCode(); 
        // Determine if a reply code is a positive completion response.
        if(FTPReply.isPositiveCompletion(replyCode)){
        	System.out.println(" FTPUtils -> connect FTPServer success!"); 
        } else {
        	System.err.println(" FTPUtils -> connect FTPServer fail!"); 
        }
    }

    /**
     * 上傳檔案至FTP
     * 注:若有同名檔案,那麼原檔案會被覆蓋
     *
     * @param remoteDir
     *            上傳到指定目錄(絕對路徑)  如果寫的相對路徑,那麼會預設在其前面加一個"/"
     *         統一:路徑分割符 用“/”,而不用“\”;
     *         
     * @param remoteFileName
     *            上傳到FTP,該檔案的檔名
     * @param file
     *            要上傳的本地檔案
     * @return 上傳結果
     * @throws IOException
     * @DATE 2018年9月26日 下午1:35:27
     */
    public boolean uploadFile(String remoteDir, String remoteFileName, File file) throws IOException{
        boolean result = false;
        InputStream inputStream = null;
        try{
            inputStream = new FileInputStream(file);
            // 初始化
            initFtpClient();
            CreateDirecroty(remoteDir);
            ftpClient.makeDirectory(remoteDir);
            ftpClient.changeWorkingDirectory(remoteDir);
            result = ftpClient.storeFile(remoteFileName, inputStream);
        }finally{
        	// 先登出、再關閉連線
        	ftpClient.logout();
            if(ftpClient.isConnected()){ 
                ftpClient.disconnect();
            } 
            if(null != inputStream){
                inputStream.close();
            } 
        }
        System.out.println(" FTPUtils -> uploadFile boolean result is ---> " + result);
        return result;
    }

    
    /**
     * 從FTP下載檔案
     * 注:如果remoteDirOrRemoteFile不存在,再不會下載下來任何東西
     *
     * @param remoteDirOrRemoteFile
     *            FTP中的某一個目錄(此時下載該目錄下的所有檔案,該目錄下的資料夾不會被下載); 
     *            或  FTP中的某一個檔案全路徑名(此時下載該檔案)
     *        統一:路徑分割符 用“/”,而不用“\”;
     *        
     * @param localDir
     *            本地用於儲存下載下來的檔案的資料夾
     *        統一:路徑分割符 用“/”,而不用“\”;
     * @return 下載了的檔案個數
     * @throws IOException
     * @DATE 2018年9月26日 下午7:24:11
     */
    public int downloadFile(String remoteDirOrRemoteFile, String localDir) throws IOException{ 
    	//如果資料夾不存在則建立    
    	File localfile = new File(localDir);
    	if (!localfile.exists()) {
    		System.out.println(" " + localDir + "is not exist, create this Dir!");
			localfile.mkdir();
    	}
        int successSum = 0; 
        int failSum = 0; 
        OutputStream os=null;
        try { 
            initFtpClient();
            // 根據remoteDirOrRemoteFile是檔案還是目錄,來切換changeWorkingDirectory
            if(remoteDirOrRemoteFile.lastIndexOf(".") < 0) {
	            // 切換至要下載的檔案所在的目錄,否者下載下來的檔案大小為0
	            ftpClient.changeWorkingDirectory(remoteDirOrRemoteFile);
            }else {
            	String tempWorkingDirectory = "";
            	int index = remoteDirOrRemoteFile.lastIndexOf("/");
            	if (index > 0) {
            		tempWorkingDirectory = remoteDirOrRemoteFile.substring(0, index);
            	}else {
            		tempWorkingDirectory = "/";
            	}
            	// 切換至要下載的檔案所在的目錄,否者下載下來的檔案大小為0
	            ftpClient.changeWorkingDirectory(tempWorkingDirectory);
            }
            // 獲取remoteDirOrRemoteFile目錄下所有 檔案以及資料夾   或  獲取指定的檔案
            FTPFile[] ftpFiles = ftpClient.listFiles(remoteDirOrRemoteFile);
            for(FTPFile file : ftpFiles){ 
            	// 如果是資料夾,那麼不下載 (因為:直接下載資料夾的話,是無效檔案)
            	if(file.isDirectory()) {
            		continue;
            	}
            	String name = new String(file.getName().getBytes(this.downfileNameEncodingParam1), 
            			                 this.downfileNameDecodingParam2);
                File localFile = new File(localDir + "/" + name); 
                os = new FileOutputStream(localFile); 
                boolean result = ftpClient.retrieveFile(file.getName(), os); 
                if (result) {
                    successSum++;
                } else {
                	failSum++;
                }
                System.out.println(" already success download item count ---> " + successSum);
            } 
        } finally{ 
        	// 先登出、再關閉連線
        	ftpClient.logout(); 
            if(ftpClient.isConnected()){ 
                ftpClient.disconnect();
            } 
            if(os != null){
                os.close();
            } 
        } 
        System.out.println(" FTPUtils -> downloadFile success download file total ---> " + successSum);
        System.out.println(" FTPUtils -> downloadFile fail download file total ---> " + failSum);
        return successSum; 
    }

    /**
     * downloadFile的升級版 -> 其功能如下:
     *     1.remoteDirOrRemoteFile可為FTP上某一個檔案的全路徑名
     *       ---> 下載該檔案,此處與downloadFile功能一致
     *     
     *     2.remoteDirOrRemoteFile可為FTP上某一個檔案目錄名
     *       ---> 下載該目錄下的所有檔案、資料夾(包括該資料夾中的所有檔案資料夾並以此類推) 
     *           注:對比downloadFile方法可知,downloadFile只能下載該目錄下的所有檔案,不能遞迴下載
     *
     * @DATE 2018年9月26日 下午7:26:22
     */
    public int recursiveDownloadFile(String remoteDirOrRemoteFile, String localDir) throws IOException{ 
    	int successSum = 0; 
    	// remoteDirOrRemoteFile是一個明確的檔案  還是  一個目錄
    	if(remoteDirOrRemoteFile.indexOf(".") >= 0) {
    		successSum = downloadFile(remoteDirOrRemoteFile, localDir);
    	}else {
	        /// 初步組裝資料,呼叫遞迴方法;查詢給定FTP目錄以及其所有子孫目錄,進而得到FTP目錄與本地目錄的對應關係Map
	        // 有序存放FTP remote資料夾路徑
    		// 其實邏輯是:先往alreadyQueriedDirList裡面存,再進行的查詢。此處可以這麼處理。
	        List<String> alreadyQueryDirList = new ArrayList<>(16); 
	        alreadyQueryDirList.add(remoteDirOrRemoteFile);
	        // 有序存放FTP remote資料夾路徑
	        List<String> requiredQueryDirList = new ArrayList<>(16); 
	        requiredQueryDirList.add(remoteDirOrRemoteFile);
	        // 記錄FTP目錄與 本地目錄對應關係
	        Map<String, String> storeDataMap = new HashMap<>();
	        storeDataMap.put(remoteDirOrRemoteFile, localDir);
	        queryFTPAllChildrenDirectory(storeDataMap, alreadyQueryDirList, requiredQueryDirList);
	        
	        // 迴圈呼叫downloadFile()方法,進行巢狀下載
	        for (int i = 0; i < alreadyQueryDirList.size(); i++) {
	        	int thiscount = downloadFile(alreadyQueryDirList.get(i), 
	        			storeDataMap.get(alreadyQueryDirList.get(i)));
	        	successSum += thiscount;
			}
    	}
        System.out.println(" FTPUtils -> recursiveDownloadFile(excluded created directories) "
        		               + " success download file total ---> " + successSum);
        return successSum; 
    }
 

    /**
     * 刪除檔案 或 刪除空的資料夾
     *
     * @param deletedDirOrFile
     *            要刪除的檔案的全路徑名  或  要刪除的空資料夾全路徑名
     *        統一:路徑分割符 用“/”,而不用“\”;
     *        
     * @return 刪除成功與否
     * @throws IOException
     * @DATE 2018年9月26日 下午9:12:07
     */
    public boolean deleteBlankDirOrFile(String deletedBlankDirOrFile) throws IOException{ 
        boolean flag = false; 
        try { 
            initFtpClient();
            // 根據remoteDirOrRemoteFile是檔案還是目錄,來切換changeWorkingDirectory
            if(deletedBlankDirOrFile.lastIndexOf(".") < 0) {
	            // 出於保護機制:如果當前資料夾中是空的,那麼才能刪除成功
            	flag = ftpClient.removeDirectory(deletedBlankDirOrFile);
            	// 不排除哪些 沒有後綴名的檔案 存在的可能;
            	// 如果刪除空資料夾失敗,那麼其可能是沒有後綴名的檔案,那麼嘗試著刪除檔案
            	if (!flag) {
            		flag = ftpClient.deleteFile(deletedBlankDirOrFile);
            	}
            }else {/// 如果是檔案,那麼直接刪除該檔案
            	String tempWorkingDirectory = "";
            	int index = deletedBlankDirOrFile.lastIndexOf("/");
            	if (index > 0) {
            		tempWorkingDirectory = deletedBlankDirOrFile.substring(0, index);
            	}else {
            		tempWorkingDirectory = "/";
            	}
            	// 切換至要下載的檔案所在的目錄,否者下載下來的檔案大小為0
	            ftpClient.changeWorkingDirectory(tempWorkingDirectory);
	            flag = ftpClient.deleteFile(deletedBlankDirOrFile.substring(index + 1));
            }
        } finally {
        	ftpClient.logout();
            if(ftpClient.isConnected()){ 
                ftpClient.disconnect();
            } 
        }
        if (flag == false) {
        	System.out.println(" FTPUtils -> deleteBlankDirOrFile Maybe [" + deletedBlankDirOrFile 
		               				+ "]  doesn't exist !");
        }
        System.out.println(" FTPUtils -> deleteBlankDirOrFile [" + deletedBlankDirOrFile 
        		               + "] boolean result is ---> " + flag);
        return flag; 
    }
    
    
    /**
     * deleteBlankDirOrFile的加強版 -> 可刪除檔案、空資料夾、非空資料夾
     *
     * @param deletedBlankDirOrFile
     * @return
     * @throws IOException
     * @DATE 2018年9月27日 上午1:25:16
     */
    public boolean recursiveDeleteBlankDirOrFile(String deletedBlankDirOrFile) throws IOException{ 
    	boolean result = true; 
    	if(!destDirExist(deletedBlankDirOrFile)) {
    		System.out.println(" " + deletedBlankDirOrFile + " maybe is a  non-suffix file!, try delete!");
    		boolean flag = deleteBlankDirOrFile(deletedBlankDirOrFile);
    		String flagIsTrue = " FTPUtils -> recursiveDeleteBlankDirOrFile " 
    		                        + deletedBlankDirOrFile + "---> success!";
    		String flagIsFalse = " FTPUtils -> recursiveDeleteBlankDirOrFile " 
                    + deletedBlankDirOrFile + "---> target file is not exist!";
    		System.out.println(flag == true ? flagIsTrue : flagIsFalse);
    		return true;
    	}
    	// remoteDirOrRemoteFile是一個明確的檔案  還是  一個目錄
    	if (deletedBlankDirOrFile.indexOf(".") >= 0 || !ftputilsChangeWorkingDirectory(deletedBlankDirOrFile)) {
    		result = deleteBlankDirOrFile(deletedBlankDirOrFile);
    	} else {
	        /// 初步組裝資料,呼叫遞迴方法;查詢給定FTP目錄以及其所有子孫目錄、子孫檔案        (含其自身)
	        // 存放  資料夾路徑
    		// 其實邏輯是:先往alreadyQueriedDirList裡面存,再進行的查詢。此處可以這麼處理。
	        List<String> alreadyQueriedDirList = new ArrayList<>(16); 
	        alreadyQueriedDirList.add(deletedBlankDirOrFile);
	        // 存放  檔案路徑
	        List<String> alreadyQueriedFileList = new ArrayList<>(16); 
	        // 存放 資料夾路徑
	        List<String> requiredQueryDirList = new ArrayList<>(16); 
	        requiredQueryDirList.add(deletedBlankDirOrFile);
	        queryAllChildrenDirAndChildrenFile(alreadyQueriedDirList, 
								        		alreadyQueriedFileList, 
								        		requiredQueryDirList);
	        
	        // 迴圈呼叫deleteBlankDirOrFile()方法,刪除檔案
	        for (int i = 0; i < alreadyQueriedFileList.size(); i++) {
	        	boolean isSuccess = deleteBlankDirOrFile(alreadyQueriedFileList.get(i));
	        	if (!isSuccess) {
	        		result = false;
	        	}
			}
	        
	        // 對alreadyQueriedDirList進行排序,以保證等下刪除時,先刪除的空資料夾是 最下面的
	        String[] alreadyQueriedDirArray = new String[alreadyQueriedDirList.size()];
	        alreadyQueriedDirArray = alreadyQueriedDirList.toArray(alreadyQueriedDirArray);
	        sortArray(alreadyQueriedDirArray);
	        
	        // 迴圈呼叫deleteBlankDirOrFile()方法,刪除空的資料夾
	        for (int i = 0; i < alreadyQueriedDirArray.length; i++) {
	        	boolean isSuccess = deleteBlankDirOrFile(alreadyQueriedDirArray[i]);
	        	if (!isSuccess) {
	        		result = false;
	        	}
			}
    	}
        System.out.println(" FTPUtils -> recursiveDeleteBlankDirOrFile "
        		               + " boolean result is---> " + result);
        return result;
    }
    
    
    
    /*  -------------JustryDeng-------------以下為輔助方法-------------JustryDeng------------- */
    
    /**
     * 根據陣列元素的長度,來進行排序(字串長的,排在前面)
     * 陣列元素不能為null
     *
     * @DATE 2018年9月27日 上午12:54:03
     */
    private void sortArray(String[] array) {
    	for (int i = 0; i < array.length - 1; i++) {
    	    for(int j = 0; j < array.length - 1 - i; j++) {
	            if (array[j].length() - array[j+1].length() < 0) {
	             String flag=array[j];
	             array[j] = array[j+1];
	             array[j+1] = flag;
	            }
    	    }
    	}
    }
    
    /**
     * 根據給出的FTP目錄、對應本地目錄; 查詢該FTP目錄的所有子目錄 , 以及獲得與每一個子目錄對應的本地目錄(含其自身以及與其自身對應的本地目錄)
     *
     * @param storeDataMap
     *            儲存FTP目錄與本地目錄的對應關係;key -> FTP目錄, value -> 與key對應的本地目錄
     * @param alreadyQueryDirList
     *            所有已經查詢過了的FTP目錄,即:key集合
     * @param requiredQueryDirList
     *            還需要查詢的FTP目錄
     * @throws IOException
     * @DATE 2018年9月26日 下午7:17:52
     */
    private void queryFTPAllChildrenDirectory(Map<String, String> storeDataMap, 
									    		List<String> alreadyQueriedDirList, 
									    		List<String> requiredQueryDirList) throws IOException {
    	List<String> newRequiredQueryDirList = new ArrayList<>(16); 
    	initFtpClient();
    	try { 
    		if(requiredQueryDirList.size() == 0) {
    			return;
    		}
    		for (int i = 0; i < requiredQueryDirList.size(); i++) {
    			String rootRemoteDir = requiredQueryDirList.get(i);
    			String rootLocalDir = storeDataMap.get(requiredQueryDirList.get(i));
	            // 獲取rootRemoteDir目錄下所有 檔案以及資料夾(或  獲取指定的檔案)
	            FTPFile[] ftpFiles = ftpClient.listFiles(rootRemoteDir);
	            for(FTPFile file : ftpFiles){ 
	             	if (file.isDirectory()) {
	             		String tempName = file.getName();
	             		String ftpChildrenDir = "";
         				ftpChildrenDir = rootRemoteDir + "/" + tempName  ;
	             		String localChildrenDir = "";
             			localChildrenDir = rootLocalDir + "/" + tempName  ;
             			alreadyQueriedDirList.add(ftpChildrenDir);
	             		newRequiredQueryDirList.add(ftpChildrenDir);
	             		storeDataMap.put(ftpChildrenDir, localChildrenDir);
	             	}
	            } 
    		}
    	} finally{ 
    		// 先登出、再關閉連線
    		ftpClient.logout(); 
    		if (ftpClient.isConnected()){ 
    			ftpClient.disconnect();
    		} 
    	} 
    	this.queryFTPAllChildrenDirectory(storeDataMap, alreadyQueriedDirList, newRequiredQueryDirList);
    }
    
    /**
     * 根據給出的FTP目錄,查詢其所有子目錄以及子檔案(含其自身)
     *
     * @param alreadyQueriedDirList
     *            所有已經查詢出來了的目錄
     * @param alreadyQueriedFileList
     *            所有已經查詢出來了的檔案
     * @param requiredQueryDirList
     *            還需要查詢的FTP目錄
     * @throws IOException
     * @DATE 2018年9月27日 上午12:12:53
     */
    private void queryAllChildrenDirAndChildrenFile(List<String> alreadyQueriedDirList, 
									    		List<String> alreadyQueriedFileList, 
									    		List<String> requiredQueryDirList) throws IOException {
    	List<String> newRequiredQueryDirList = new ArrayList<>(16); 
    	initFtpClient();
    	try { 
    		if(requiredQueryDirList.size() == 0) {
    			return;
    		}
    		for (int i = 0; i < requiredQueryDirList.size(); i++) {
    			String dirPath = requiredQueryDirList.get(i);
	            // 獲取dirPath目錄下所有 檔案以及資料夾(或  獲取指定的檔案)
	            FTPFile[] ftpFiles = ftpClient.listFiles(dirPath);
	            for(FTPFile file : ftpFiles){ 
	             	if (file.isDirectory()) {
	             		String tempName = file.getName();
	             		String ftpChildrenDir = dirPath + "/" + tempName;
	             		alreadyQueriedDirList.add(ftpChildrenDir);
	             		newRequiredQueryDirList.add(ftpChildrenDir);
	             	} else {
	             		String tempName = file.getName();
	             		String ftpChildrenFile = dirPath + "/" + tempName;
	             		alreadyQueriedFileList.add(ftpChildrenFile);
	             	}
	            } 
    	 
    		}
    	} finally{ 
    		// 先登出、再關閉連線
    		ftpClient.logout(); 
    		if (ftpClient.isConnected()){ 
    			ftpClient.disconnect();
    		} 
    	} 
    	this.queryAllChildrenDirAndChildrenFile(alreadyQueriedDirList, alreadyQueriedFileList, newRequiredQueryDirList);
    }

    
    /**
     * 建立指定目錄(注:如果要建立的目錄已經存在,那麼返回false)
     *
     * @param dir
     *            目錄路徑,絕對路徑,如: /abc 或  /abc/ 可以
     *                   相對路徑,如:  sss 或    sss/ 也可以
     *                  注:相對路徑建立的資料夾所在位置時,相對於當前session所處目錄位置。
     *                  提示: .changeWorkingDirectory() 可切換當前session所處目錄位置
     * @return 建立成功與否
     * @throws IOException 
     * @DATE 2018年9月26日 下午3:42:20
     */
    private boolean makeDirectory(String dir) throws IOException {
        boolean flag = false;
        flag = ftpClient.makeDirectory(dir);
        if (flag) {
            System.out.println(" FTPUtils -> makeDirectory -> create Dir [" + dir + "] success!");
        } else {
            System.err.println(" FTPUtils -> makeDirectory -> create Dir [" + dir + "] fail!");
        }
        return flag;
    }
    
    /**
     * 在FTP伺服器上建立remoteDir目錄(不存在,則建立;存在,則不建立)
     *
     * @param remoteDir
     *            要建立的目錄   為null或為"" 則視為  根目錄 
     * @return 結果
     * @throws IOException
     * @DATE 2018年9月26日 下午2:19:37
     */
    private boolean CreateDirecroty(String remoteDir) throws IOException {
        boolean success = true;
        String directory = null;
        if(remoteDir == null || remoteDir.trim().equals("")) {
        	directory = "/";
        }else if(remoteDir.endsWith("/")) {
        	directory = remoteDir;
        }else {
        	directory = remoteDir + "/";
        }
        // directory不為根目錄 且 切換至該目錄失敗 -> 說明FTPServer中不存在該目錄,那麼進行建立
        /*
         * .changeWorkingDirectory(directory)中的directory為 要切換到的目錄
         *   可為 -> 絕對路徑; 可為 -> 相對路徑(如果為相對路徑,那麼相對於當前session所處目錄)
         */
        if (!directory.equals("/") && !ftpClient.changeWorkingDirectory(directory)) {
        	// 獲得每一個節點目錄的起始位置
            int start = 0;
            int end = 0;
            if (directory.startsWith("/")) {
                start = 1;
            } else {
                start = 0;
            }
            end = directory.indexOf("/", start);
            // 迴圈建立目錄
            String dirPath = "";
            String paths = "";
            while (true) {
                String subDirectory = directory.substring(start, end);
                dirPath = dirPath + "/" + subDirectory;
                if (!ftpClient.changeWorkingDirectory(dirPath)) {
                    makeDirectory(dirPath);
                    ftpClient.changeWorkingDirectory(dirPath);
                    // 當前session所處FTP目錄位置
                    String currentDirPath = ftpClient.printWorkingDirectory();
                    System.out.println(" FTPUtils -> current position dirPath ---> " + currentDirPath);
                } 
                // 根性子節點目錄名 index起始位置
                paths = paths + "/" + subDirectory;
                start = end + 1;
                end = directory.indexOf("/", start);
                // 檢查所有目錄是否建立完畢
                if (end < 0) {
                    break;
                }
            }
        }
        return success;
    }
    
    
    /**
     * 避免在程式碼中頻繁 initFtpClient、logout、disconnect;
     * 這裡包裝一下FTPClient的.changeWorkingDirectory(String pathname)方法
     *
     * @param directory
     *            要切換(session)到FTP的哪一個目錄下
     * @DATE 2018年9月27日 上午11:24:25
     */
    private boolean ftputilsChangeWorkingDirectory(String pathname) throws IOException{
    	boolean result = true;
	    try {
	    	initFtpClient();
	    	result = ftpClient.changeWorkingDirectory(pathname);
	    }finally{
	    	// 先登出、再關閉連線
	    	ftpClient.logout();
	        if(ftpClient.isConnected()){ 
	            ftpClient.disconnect();
	        }
	    }
	        return result;
	}
    
    /**
     * 判斷FTP上某目錄是否存在
     *
     * @param pathname
     *            要判斷的路徑(檔名全路徑、資料夾全路徑都可以)
     *            注:此路徑應從根目錄開始
     * @DATE 2018年9月27日 上午11:24:25
     */
    private boolean destDirExist(String pathname) throws IOException{
    	boolean result = true;
	    try {
	    	// 初始化時,當前session位置即為 “/”
	    	initFtpClient();
	    	if (!pathname.startsWith("/")) {
	    		pathname = "/" + pathname;
	    	}
	    	if (pathname.lastIndexOf(".") >= 0) {
	    		int index = pathname.lastIndexOf("/");
	    		if (index != 0) {
	    			pathname = pathname.substring(0, index);
	    		} else {
	    			return true;
	    		}
	    	}
	    	result = ftpClient.changeWorkingDirectory(pathname);
	    }finally{
	    	// 先登出、再關閉連線
	    	ftpClient.logout();
	        if(ftpClient.isConnected()){ 
	            ftpClient.disconnect();
	        }
	    }
	    return result;
	}
}

微笑如有不當之處,歡迎指正

微笑本文已經被收錄進《程式設計師成長筆記(三)》,筆者JustryDeng