最近在做一個FTP數據采集功能,在使用ftp4j組件(官網下載地址:http://www.sauronsoftware.it/projects/ftp4j/download.php?PHPSESSID=7ugub8n90o29g1u64muqlss2c3)做FTP目錄文件掃描時,遇到了一個糾結的問題。
模擬問題場景:
FTP服務器目錄如下:
掃描FTP目錄文件代碼片段如下:
FTPClient ftpClient = new FTPClient(); try { ftpClient.connect("192.168.10.145", 21); System.out.println("連接成功"); ftpClient.login("monkey1992", "123456"); System.out.println("登錄成功"); ftpClient.changeDirectory("data"); String[] files = ftpClient.listNames(); System.out.println(Arrays.toString(files)); ftpClient.disconnect(true); } catch (Exception e) { e.printStackTrace(); }
使用listNames()方法,可以正常掃描出指定目錄文件的文件名,輸出結果如下:
連接成功
登錄成功
[data.txt, data.xls, test.doc]
ftp4j組件提供的掃描目錄文件的方法除了listNames()外,還提供了list(), list(String fileSpec)方法,返回值都是FTPFile[]
現在想使用的是使用list()方法掃描FTP服務器中指定的目錄,然而今天所要解決的問題出現了,調用list()方法拋出了異常,修改代碼如下:
FTPClient ftpClient = new FTPClient(); try { ftpClient.connect("192.168.0.132", 21); System.out.println("連接成功"); ftpClient.login("monkey1992", "106"); System.out.println("登錄成功"); ftpClient.changeDirectory("data"); FTPFile[] files = ftpClient.list(); //listNames改成list() System.out.println(Arrays.toString(files)); ftpClient.disconnect(true); } catch (Exception e) { e.printStackTrace(); }
結果如下:
連接成功 登錄成功 it.sauronsoftware.ftp4j.FTPListParseException at it.sauronsoftware.ftp4j.FTPClient.list(FTPClient.java:2131) at it.sauronsoftware.ftp4j.FTPClient.list(FTPClient.java:2182) at accel.component.datacollect.ftp.FtpMainTest.test(FtpMainTest.java:51) at accel.component.datacollect.ftp.FtpMainTest.main(FtpMainTest.java:30) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
通過斷點調試,跟蹤該異常是在it.sauronsoftware.ftp4j.listparsers.DOSListParser列表解析類中,在對列表文件的更新時間進行解析時出現了異常
DOSListParser類的源代碼如下:
package it.sauronsoftware.ftp4j.listparsers; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import it.sauronsoftware.ftp4j.FTPFile; import it.sauronsoftware.ftp4j.FTPListParseException; import it.sauronsoftware.ftp4j.FTPListParser; /** * This parser can handle the MSDOS-style LIST responses. * * @author Carlo Pelliccia */ public class DOSListParser implements FTPListParser { private static final Pattern PATTERN = Pattern .compile("^(\\d{2})-(\\d{2})-(\\d{2})\\s+(\\d{2}):(\\d{2})(AM|PM)\\s+" + "(<DIR>|\\d+)\\s+([^\\\\/*?\"<>|]+)$"); private static final DateFormat DATE_FORMAT = new SimpleDateFormat( "MM/dd/yy hh:mm a"); public FTPFile[] parse(String[] lines) throws FTPListParseException { int size = lines.length; FTPFile[] ret = new FTPFile[size]; for (int i = 0; i < size; i++) { Matcher m = PATTERN.matcher(lines[i]); if (m.matches()) { String month = m.group(1); String day = m.group(2); String year = m.group(3); String hour = m.group(4); String minute = m.group(5); String ampm = m.group(6); String dirOrSize = m.group(7); String name = m.group(8); ret[i] = new FTPFile(); ret[i].setName(name); if (dirOrSize.equalsIgnoreCase("<DIR>")) { ret[i].setType(FTPFile.TYPE_DIRECTORY); ret[i].setSize(0); } else { long fileSize; try { fileSize = Long.parseLong(dirOrSize); } catch (Throwable t) { throw new FTPListParseException(); } ret[i].setType(FTPFile.TYPE_FILE); ret[i].setSize(fileSize); } String mdString = month + "/" + day + "/" + year + " " + hour + ":" + minute + " " + ampm; Date md; try { md = DATE_FORMAT.parse(mdString); //解析字符串轉為日期類型時出異常 } catch (ParseException e) { throw new FTPListParseException(); } ret[i].setModifiedDate(md); } else { throw new FTPListParseException(); } } return ret; } }
異常信息如下:
所以it.sauronsoftware.ftp4j.FTPListParseException異常出現的原因是因為SimpleDateFormat的parse()解析方法上
為什么字符串解析成日期會拋異常,下面做個小實驗
SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yy hh:mm a"); try { Date date = dateFormat.parse("03/05/13 12:00 AM"); System.out.println(date); } catch (ParseException e) { e.printStackTrace(); }
輸出結果:
java.text.ParseException: Unparseable date: "03/05/13 12:00 AM" at java.text.DateFormat.parse(DateFormat.java:337) at accel.component.datacollect.ftp.FtpMainTest.main(FtpMainTest.java:34) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
異常原因分析:
其實之所以出現日期解析異常,關鍵是在系統時間的語言環境設置上。由於我的機器系統時間采用的我們國家的,AM或PM這種日期描述我們是沒有的。所以自然解析不了,下面是我機器的時間語言環境設置:
現在,我把機器語言改成美國的,然后再運行之前的日期解析實驗程序
程序輸出結果:
Tue Mar 05 00:00:00 CST 2013
現在你可能提出疑問,那解決該日期解析異常一定要改系統的時間語言環境?
答案肯定是否定的。在SimpleDateFormat中提供了下面這種構造方法
SimpleDateFormat(String pattern, Locale locale)
---> 用給定的模式和給定語言環境的默認日期格式符號構造 SimpleDateFormat
。
所以上述的實驗代碼,可以修改成下面這種方式,就無需修改系統的時間語言環境,代碼如下:
SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yy hh:mm a", Locale.ENGLISH); try { Date date = dateFormat.parse("03/05/13 12:00 AM"); System.out.println(date); } catch (ParseException e) { e.printStackTrace(); }
所以解決FTPListParseException異常,可以修改it.sauronsoftware.ftp4j.listparsers.DOSListParser類中的SimpleDateFormat的解析模式
現在你可能會再次發出疑問,解決該異常要修改ftp4j源代碼太不靠譜了。
下面還有一種解決該異常的方法,就是修改FTP服務器中的目錄列表樣式,默認是MS-DOS(M),現在改成UNIX(U)如下圖:
修改好后,調用FTPClient的list()方法就可以正常返回FTPFile數組了
總結:
解決it.sauronsoftware.ftp4j.FTPListParseException異常的方案主要有以下3個:
(1) 修改系統時間語言環境 (不靠譜,不推薦使用)
(2)修改it.sauronsoftware.ftp4j.listparsers.DOSListParser類的SimpleDateFormat的解析模式 (直接使用官網Jar時不推薦使用,需要修改代碼--編譯--再打包,但如果你是直接提取ftp4j組件的源代碼,自己拿來做二次封裝,簡單的說就是直接拿該組件的源碼在導到項目中使用,這種情況下就可以使用這種方式)
(3)修改FTP服務器中的目錄列表樣式(推薦使用)
PS:
解決該異常啰嗦了一大堆主要是想告訴一下新手(包括自己),在解決問題時,要懂得跟蹤尋找問題出現的根由,確定原因,再去找解決問題的方案,希望這個異常解決方案能幫到一些人