• 填坑:Java 中的日期转换


    我们之前讨论过时间,在Java 中有一些方法会出现横线?比如Date 过期方法。

     

    参考文章:知识点:java一些方法会有横线?以Date 过期方法为例

    Java中的日期和时间处理方法

    • Date类(官方不再推荐使用,官方解释Date类不利于国际化,推荐使用Calendar类)
    • Calendar类
    • DateFormat类 使用此类来时间初始化

    我们发现,时间toLocalString 会有横线:

    vo.setSubmitDate(new Date().toLocaleString());

    可以改为:

    vo.setSubmitDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance(TimeZone.getTimeZone("GMT+08:00")).getTime()));

    在最近的项目中,用SimpleDateFormat出现一些奇奇怪怪的BUG:

    • 1.结果值不对:转换的结果值经常和预期不同。
    • 2.内存泄漏: 由于转换的结果值不对,后续的一些操作会导致系统内存泄漏,频繁触发GC(垃圾回收),造成系统不可用。

    1 什么是SimpleDateFormat

    在java doc对SimpleDateFormat的解释如下:

    SimpleDateFormatis a concrete class for formatting and parsing dates in a locale-sensitive manner. It allows for formatting(date → text), parsing (text → date), and normalization.

    SimpleDateFormat是一个用来对位置敏感的格式化和解析日期的实体类。他允许把日期格式化成text,把text解析成日期和规范化。

    1.1 使用SimpleDateFormat

    simpleDateFormat的使用方法比较简单:

    public static void main(String[] args) throws Exception { 
    
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd  HH:mm:ss");
    
        System.out.println(simpleDateFormat.format(new Date())); 
    
        System.out.println(simpleDateFormat.parse("2018-10-24  12:10:24")); 
        
    }
    • 1.定义一个日期"yyyy-mm-dd HH:mm:ss"的pattern, 也就是我们这个simpleDateFormat不管是格式化还是解析都需要按照这个pattern。
    • 2.对于format需要传递Date的对象,会返回一个String类型,这个String会按照我们上面的格式生成。
    • 3.对于parse需要传递一个按照上面pattern的字符串,如果传递错误的pattern会抛出java.text.ParseException异常,如果传递正确的会生成一个Date对象。

    附:格式占位符 G 年代标志符 y 年 M 月 d 日 h 时 在上午或下午 (1~12) H 时 在一天中 (0~23) m 分 s 秒 S 毫秒 E 星期 D 一年中的第几天 F 一月中第几个星期几 w 一年中第几个星期 W 一月中第几个星期 a 上午 / 下午 标记符 k 时 在一天中 (1~24) K 时 在上午或下午 (0~11) z 时区复制代码

    回到我们遇到的坑:

    2 为什么SimpleDateFormat会线程不安全呢?

    在SimpleDateFormat源码中,所有的格式化和解析都需要通过一个中间对象Calendar进行转换,而这将会出现线程不安全的操作

    比如当多个线程操作同一个Calendar的时候后来的线程会覆盖先来线程的数据,那最后其实返回的是后来线程的数据,这样就导致我们上面所述的BUG的产生

    为什么会出现这么多问题呢?

    因为SimpleDateFormat线程不安全,很多人都会写个Util类,然后把SimpleDateFormat定义成全局的一个常量,所有线程都共享这个常量:

    protected static final SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
    
    public static Date formatDate(String date) throws ParseException {
    
        returndayFormat.parse(date);
        
    }

    3.如何避坑

    对于SimpleDateFormat的解决方法有下面几种:

    3.1 新建SimpleDateFormat

    原因:所有线程都共用一个SimpleDateFormat,

    解决办法:每次使用的时候都创建一个新的SimpleDateFormat,我们可以在DateUtils中将创建SimpleDateFormat放在方法内部:

    public static Date formatDate(String date) throws ParseException { 
    
        SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
    
        return dayFormat.parse(date);
        
    }

    上面这个方法虽然能解决我们的问题但是引入了另外一个问题就是,如果这个方法使用量比较大,有可能会频繁造成Young gc,整个系统还是会受一定的影响。

    3.2 使用ThreadLocal

    使用ThreadLocal能避免造成Young gc,我们对每个线程都使用ThreadLocal进行保存

    由于ThreadLocal是线程之间隔离开的,所以不会出现线程安全问题:

    private static ThreadLocal simpleDateFormatThreadLocal = new ThreadLocal<>();
    
    public static Date formatDate(String date) throws ParseException { 
    
        SimpleDateFormat dayFormat = getSimpleDateFormat();
        returndayFormat.parse(date); 
        
    }
    
    private static SimpleDateFormatgetSimpleDateFormat() {     
        SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();
    
        if(simpleDateFormat == null){ 
        
        simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd  HH:mm:ss") simpleDateFormatThreadLocal.set(simpleDateFormat); 
            
        }
        
        returnsimpleDateFormat; 
        
    }

    3.3使用第三方工具包

    虽然上面的ThreadLocal能解决我们出现的问题,但是第三方工具包提供的功能更加强大,在java中有两个类库比较出名一个是Joda-Time,一个是Apache common包

    3.3.1 Joda-Time(推荐)

    Joda-Time 令时间和日期值变得易于管理、操作和理解。对于我们复杂的操作都可以使用Joda-Time操作,下面我列举两个例子,对于把日期加上90天,如果使用原生的Jdk我们需要这样写:

    Calendar calendar = Calendar.getInstance();calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);SimpleDateFormat sdf = new SimpleDateFormat("E MM/dd/yyyy HH:mm:ss.SSS");
    
    calendar.add(Calendar.DAY\_OF\_MONTH, 90);
    
    System.out.println(sdf.format(calendar.getTime()));

    但是在我们的joda-time中只需要两句话,并且api也比较通俗易懂,所以你为什么不用Joda-Time呢?

    DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
    
    System.out.println(dateTime.plusDays(90).toString("E MM/dd/yyyy HH:mm:ss.SSS");
    3.3.2 common-lang包

    在common-lang包中有个类叫FastDateFormat,由于common-lang这个包基本被很多Java项目都会引用,所以你可以不用专门去引用处理时间包,即可处理时间,在FastDateFormat中每次处理时间的时候会创建一个calendar,使用方法比较简单代码如下所示:

    FastDateFormat.getInstance().format(new Date());

    3.4升级jdk8(推荐)

    在java8中Date这个类中的很多方法包括构造方法都被打上了@Deprecated废弃的注解,取而代之的是LocalDateTime,

    LocalDate LocalTime这三个类:

    • LocalDate无法包含时间;
    • LocalTime无法包含日期;
    • LocalDateTime才能同时包含日期和时间。

    知识点:Java8 在日期的格式化和解析方面不用考虑线程安全性

    代码如下:

    public static String formatTime(LocalDateTime time,String pattern) {
    
    returntime.format(DateTimeFormatter.ofPattern(pattern)); 
        
    }

    localDateTime不仅解决了线程安全的问题,同样也提供了一些其他的运算比如加减天数:

    //日期加上一个数,根据field不同加不同值,field为ChronoUnit.* 
    
    public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) {
    
        return time.plus(number, field); 
        
    }
    
    //日期减去一个数,根据field不同减不同值,field参数为ChronoUnit.* 
    
    public static LocalDateTime minu(LocalDateTime time, long number, TemporalUnit field){
    
        return time.minus(number,field); 
        
    }
    延伸

    使用LocalDateTime 会改变现有的代码,我们可以将他们两进行互转:

    //Date转换为LocalDateTime 
    
    public static LocalDateTime convertDateToLDT(Date date) {
    
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); 
        
    }
    
    //LocalDateTime转换为Date 
    
    public static Date convertLDTToDate(LocalDateTime time) {
        
        return Date.from(time.atZone(ZoneId.systemDefault()).toInstant()); 
        
    }

    【公众号】:一只阿木木

  • 相关阅读:
    马云:员工的离职原因--转载
    zookeeper源码分析之五服务端(集群leader)处理请求流程
    技术高手如何炼成?--转自知乎
    一次上线事故经验
    zookeeper源码分析之四服务端(单机)处理请求流程
    AngularJS2.0 quick start——其和typescript结合需要额外依赖
    typescript 入门例子 Hello world——ts就是一个宿主机语言
    Kubernetes——自动扩展容器!假设你突然需要增加你的应用;你只需要告诉deployment一个新的 pod 副本总数即可
    Kubernetes——基于容器技术的分布式架构领先方案,它的目标是管理跨多个主机的容器,提供基本的部署,维护以及运用伸缩
    华为FusionSphere概述——计算资源、存储资源、网络资源的虚拟化,同时对这些虚拟资源进行集中调度和管理
  • 原文地址:https://www.cnblogs.com/yizhiamumu/p/9846609.html
Copyright © 2020-2023  润新知