it.sauronsoftware.ftp4j.FTPListParseException异常及解决方案
最近在做一个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:
解决该异常啰嗦了一大堆主要是想告诉一下新手(包括自己),在解决问题时,要懂得跟踪寻找问题出现的根由,确定原因,再去找解决问题的方案,希望这个异常解决方案能帮到一些人