您的位置:首页 > 其它

commons-lang(time应用),解决simpleDateFormat性能和安全问题

2016-07-22 11:56 453 查看


JAVA的时间日期处理一直是一个比较复杂的问题,可以说大多数程序员都不能得心应手的处理这些问题,就算通过commons.lang.time包简化了,使用起来仍然有些麻烦,个人认为这可能是最复杂的一个子包。不过相对原始的JDK API,还是简化了不少,因此此子包也相当值得关注。 

首先讨论一下关于时间的类,从 JDK 1.1 开始,Date的作用很有限,相应的功能已由Calendar与DateFormat代替。使用 Calendar 类实现日期和时间字段之间转换,使用 DateFormat 类来格式化和分析日期字符串。Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。TimeZone 表示时区偏移量。Locale 对象表示了特定的地理、政治和文化地区。需要
Locale 来执行其任务的操作称为语言环境敏感的操作,它使用 Locale 为用户量身定制信息。SimpleDateFormat的作用主要是用来格式化Date,用过之后就会发现,它其实很不完善,对Calendar提供的支持很少. 

关于时间的类放在org.apache.commons.lang.time 包下面,它包括以下几个类: 

DateUtils 包括一组围绕Calendar与Date的实用方法. 

DateFormatUtils 

DurationFormatUtils 

FastDateFormat 一个快速线程安全的SimpleDateFormat版本 

StopWatch 提供一个方便的定时的API 

DateFormatUtils相对来说比较简单,它的方法全部都是static的,所以不需要用构造器创建新的实例,但它构造器却是public的,这并不是说我们应该在程序中使用它,官方文档已说明,它是为了与其它工具的集成的准备的。它包括的方法主要就是format。主要用途就是根据传入的pattern格式化Date或Calendar。也可以有一些附加的参数,如Locale,TimeZone。 

比如这样: 

Java代码  


format(java.util.Calendar calendar, java.lang.String pattern, java.util.TimeZone timeZone, java.util.Locale locale)   

当然DateFormatUtils还有一些预定义字段。对应的则是相应的FastDateFormat。 

可以这样用: 

Java代码  


DateFormatUtils.ISO_DATETIME_FORMAT.format(date)  

通过公有的静态final字段来暴露这些常量简化了类的使用,但是却有一些地方是需要注意的,在这方面FastDateFormat做得还是相当不错的。要使final字段真正的不可变,要么这些字段是基本类型的值(int,String等),要么是指向一个不可变对象的引用。同时需要注意的特殊情况是,长度为零的数组总是可变的,要么通过不可变的Collection来包装,要么使数组变成私有的,并添加一个公有的方法,返回一个数组的备份,这在effective java上的Item 15上有详细的介绍。回到原来的话题上,DateFormatUtils内部定义了一些常量,如: 

Java代码  


public static final FastDateFormat ISO_DATE_FORMAT  

           = FastDateFormat.getInstance("yyyy-MM-dd");  

上面的final 字段代表一个不可变的FastDateFormat,然而要让FastDateFormat字段真正的不可变,FastDateFormat内部必须遵循相应的规则才可以。 

不要提供能修改对象状态的方法 

确保类不会被继承 

让所有字段都成为static final字段 

确保所有可变的组件不能被访问 

详细介绍可参考Effective java第二版的Item 15。仔细查看,会发现FastDateFormat都遵循这些规则,而Effective java 2rd 的出版也在commons-lang 2.4 开发之后,这说明 

commons-lang的代码质量还是相当值得肯定的。 

其实DateFormateUtils内部细节实现完全依靠FastDateFormat,DateFormateUtils只是把一些常用的格式化功能单独组织起来,让日期时间的使用变得简单,毕竟大多数时候用户查看API时,如果有太多的方法,会给他们纷繁复杂的感觉。如果需要更加强大灵活的日期格式化功能,可以直接使用FastDateFormat,FastDateFormat这个类编写比较复杂,它有自己的一套解析规则,同时又使用了DateFormat类的一些规则,如Pattern。与SimpleDateFormat不同,FastDateFormat是线程安全,所以这个类在多线程的服务环境中特别有用。虽然它们都继承自java.text.Format,其实FastDateFormat相当于DateFormat与SimpleDateFormat的合并,只是功能更强大而已。如果是从DateFormat迁移到FastDateFormat的话,还是有一些地方需要注意的,比如,Date(String
date)被DateFormat.parse(String s) 取代了,仔细查看,FastDateFormat并没有类似的方法,其实准确的说,把日期解析放在DateFormat本身就不太合理,不看文档的话,大多数人都会认为Date应该提供该功能。所以commons-lang把它放在了DateUtils 类中,对应方法则更加的强大: 

Java代码  


parseDate(java.lang.String str, java.lang.String[] parsePatterns)   

因parsePatterns可以包括多种pattern,只要满足其中的一种即可。如果使用SimpleDateFormat,则先要通过SimpleDateFormat(String str)创建实例,然后通过applyPattern(String pattern)来变换不同的pattern。DateUtils提供了很多很方便的功能,减轻了使用Date的复杂性。把原来需用Calendar才能完成的功能统一集中了起来,也就是说没有对应的CalendarUtils类。在JDK中,Date与Calendar概念本身就有些混淆,只是为了保持兼容性才引入的Calendar。相对于Calendar提供的方法,DateUtils提供了更加合理的方法,对时间的单个字段操作变得更加的容易。如需要修改时间Date的某个字段,必须先获得Date对象实例,再传入Calendar,才能修改,如: 

Java代码  


public static Date add(Date date, int calendarField, int amount) {  

       if (date == null) {  

           throw new IllegalArgumentException("The date must not be null");  

       }  

       Calendar c = Calendar.getInstance();  

       c.setTime(date);  

       c.add(calendarField, amount);  

       return c.getTime();  

   }  

在这方面commons-lang的确做得很完善,如: 

Java代码  


public static Date addMinutes(Date date, int amount) {  

       return add(date, Calendar.MINUTE, amount);  

   }  

方法名也非常的直观,使用也更加方便了。 

但有一些方法不是很好理解,如: 

Java代码  


public static long getFragmentInSeconds(Date date,int fragment)  

这个方法的作用是:返回一个指定时间的秒数。关键的是参数fragment,它的作用非常重要。它的值必须是Calendar的时间常量字段。如Calendar.MONTH ,需要注意的是,小时必须用24小时制的,即Calendar.HOUR_OF_DAY ,而不能用Calendar.HOUR字段。如果使用 

Calendar.HOUR_OF_DAY 则时间2009-09-29 17:02:37 会返回157 (2*60+37),即所有大于等于fragment单位的字段将被忽略。 

相对于这些增强已有功能的类,还有一些对常用功能进行补充的类,如DurationFormatUtils ,这个类主要的作用就是处理时间的片断,主要包括两种方法: 

formatDuration和formatPeriod。如: 

Java代码  


formatDuration(long durationMillis, java.lang.String format)   

通过传入一个毫秒数与日期格式(如:yyyy-MM-dd HH:mm:ss),它会返回一个对应日期的字符串形式。当然Date类本身有一个与这类似的方法,即Date(String s)方法,用于创建一个Date实例,但它只是创建一个Date实例,如果要转换成相应的String,还要经过一些步骤才行。需要注意的是,此日期片断能表示的最大单位为天,用法也很简单: 

Java代码  


String pattern = "yyyy-MM-dd HH:mm:ss";  

long durationMillis = (10+20*60+13*3600+4*24*3600) * 1000;  

String formatDate = DurationFormatUtils.formatDuration(durationMillis,  

                pattern);  

System.out.println(formatDate);  

需要注意的是日期格式的小时必须用HH,而不能用hh 。 

下面介绍一下formatPeriod方法: 

Java代码  


public static java.lang.String formatPeriod(long startMillis,  

                                            long endMillis,  

                                            java.lang.String format)  

用于计算两个时间之间的片断,然后转化成相应的日期字符串类型,即能表示的最大单位,如下例所示: 

Java代码  


String[] parsePatterns = {"yyyy-MM-dd HH:mm:ss"};  

String str = "2009-09-29 15:30:12";  

String str2 = "2010-09-30 15:40:18";  

Date date = DateUtils.parseDate(str, parsePatterns);  

Date date2 = DateUtils.parseDate(str2, parsePatterns);  

long durationMillis = DateUtils.getFragmentInMilliseconds(date, Calendar.YEAR);  

long durationMillis2 =DateUtils.getFragmentInMilliseconds(date2,Calendar.YEAR);  

  

String s =  DurationFormatUtils.formatPeriod(durationMillis, durationMillis2,                                       "yyyy-MM-dd HH:mm:ss")  

其中s的值为:0000-00-01 00:10:06 

上述两种方法功能有些相似,本人一直以为底层用了同样的实现方法,因为看上去只要把两个时间点计算成毫秒,相后相减就与第一个方法一样了,实际不然。它们的底层完全不一样,各用各的实现方式,没有任何交差,根本原来就在于它们能表示的最大单位不一样,如下例如示: 

Java代码  


String[] parsePatterns = {"yyyy-MM-dd HH:mm:ss"};  

        String str = "2009-05-29 15:30:12";  

        String str2 = "2011-09-30 14:40:18";  

        Date date = DateUtils.parseDate(str, parsePatterns);  

        Date date2 = DateUtils.parseDate(str2, parsePatterns);  

long durationMillis = DateUtils.getFragmentInMilliseconds(date, Calendar.YEAR);  

long durationMillis2 =DateUtils.getFragmentInMilliseconds(date2,Calendar.YEAR);  

        System.out.println(DurationFormatUtils.formatPeriod(durationMillis,   

                                            durationMillis2, "yyyy-MM-dd HH:mm:ss"));  

        System.out.println(DurationFormatUtils.formatDuration(durationMillis2-                                          durationMillis,"yyyy-MM-dd HH:mm:ss"));  

结果为: 

0000-04-01 23:10:06 

0000-00-123 23:10:06 

time包还有一个StopWatch的类,此类主要作用是用来记时,有start,split,suspend之类的方法,平时能用到的地方不多。 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息