• Java日期时间API系列38-----一种高效的工作日计算方法


      

      如果没有节日放假调休的话,工作日很好计算,周一到周五就是工作日,但因为有节日放假调休,使得这个计算需要外部放假安排数据来支持。计算原理: 先按照放假安排数据计算,再按照周一周五计算。

    下面以LocalDateTime 为例。

    1.第一版,没有使用缓存

        /**
         * 判断是否中国工作日,包含法定节假日调整日期,节假日数据holidayData,如果节假日数据不支持年份,将使用周一到周五为工作日来判断。
         * @param localDateTime LocalDateTime
         * @param holidayData 放假信息0表示放假,1表示工作日,如:2021-01-01:0,2021-02-07:1
         * @return boolean
         */
        public static boolean isChineseWorkDay(LocalDateTime localDateTime, String holidayData){
            Objects.requireNonNull(holidayData, "holidayData");
            Map<String, Integer> dateTypeMap = StringUtil.convertHolidayDataToMap(holidayData);
            Integer dateType = dateTypeMap.get(DateTimeFormatterUtil.formatToDateStr(localDateTime));
            if(dateType != null){
                return dateType == 1 ? true : false;
            }
            return isWorkDay(localDateTime);
        }
    
    
    // StringUtil.convertHolidayDataToMap
    
        /**
         * 转换节日数据为map
         * @param holidayData 节日map
         * @return 返回节日map
         */
        public static Map<String, Integer> convertHolidayDataToMap(String holidayData){
            Map<String, Integer> dateTypeMap = new HashMap<>();
            if(isEmpty(holidayData)){
                return dateTypeMap;
            }
            
            String[] dateTypeArr = holidayData.replace(" ", "").split(",");
            for(String dateType : dateTypeArr){
                String[] arr = dateType.split(":");
                dateTypeMap.put(arr[0], Integer.valueOf(arr[1]));
            }
            return dateTypeMap;
        }
    
        /**
         * 判断是否工作日 (周一到周五)
         * @param localDateTime LocalDateTime
         * @return boolean
         */
        public static boolean isWorkDay(LocalDateTime localDateTime){
            int dayOfWeek = getDayOfWeek(localDateTime);
            if(dayOfWeek == 6 || dayOfWeek == 7){
                return false;
            }else{
                return true;
            }
        }

    这个方法,先将放假安排数据解析成Map,然后对比,最后使用周一到周五判断。

    2.第二版,使用缓存优化

    第一版中,每次调用都先将放假安排数据解析成Map,但其实是不需要的,因为放假安排数据每年只发布一次(特殊情况除外),一年都不需要变化,这些数据第一次调用时放进缓存,后面直接使用,有变化时再更新缓存。

    缓存使用本地缓存和Redis缓存都可以,本地缓存速度更快一些,下面使用本地缓存。

        public static boolean isChineseWorkDay2(LocalDateTime localDateTime, String holidayData){
            Objects.requireNonNull(holidayData, "holidayData");
            Map<String, Integer> dateTypeMap = StringUtil.convertHolidayDataToMapUseCache(holidayData);
            Integer dateType = dateTypeMap.get(DateTimeFormatterUtil.formatToDateStr(localDateTime));
            if(dateType != null){
                return dateType == 1 ? true : false;
            }
            return isWorkDay(localDateTime);
        }
    
    //StringUtil.convertHolidayDataToMapUseCache
    
        /**
         * 转换节日数据为map,使用缓存提高性能
         * @param holidayData 节日map
         * @return 返回节日map
         */
        @SuppressWarnings("unchecked")
        public static Map<String, Integer> convertHolidayDataToMapUseCache(String holidayData){
            Map<String, Integer> dateTypeMap = new HashMap<>();
            //参数为空,直接返回
            if(isEmpty(holidayData)){
                return dateTypeMap;
            }
            
            //查询缓存
            dateTypeMap = (Map<String, Integer>)CommonCache.get(holidayData);
            
            //缓存存在,返回缓存
            if(CollectionUtil.isNotEmpty(dateTypeMap)){
                return dateTypeMap;
            }
            
            //缓存不存在,先设置缓存然后返回
            Supplier<Object> supplier = new Supplier<Object>() {
                @Override
                public Object get() {
                    Map<String, Integer> dateTypeMap = new HashMap<>();
                    String[] dateTypeArr = holidayData.replace(" ", "").split(",");
                    for(String dateType : dateTypeArr){
                        String[] arr = dateType.split(":");
                        dateTypeMap.put(arr[0], Integer.valueOf(arr[1]));
                    }
                    return dateTypeMap;
                }
            };
            return (Map<String, Integer>)CommonCache.get(holidayData, supplier);
        }
    
        /**
         * 判断是否工作日 (周一到周五)
         * @param localDateTime LocalDateTime
         * @return boolean
         */
        public static boolean isWorkDay(LocalDateTime localDateTime){
            int dayOfWeek = getDayOfWeek(localDateTime);
            if(dayOfWeek == 6 || dayOfWeek == 7){
                return false;
            }else{
                return true;
            }
        }

    缓存使用了WeakHashMap实现缓存自动清理,使用ReentrantReadWriteLock实现读写线程安全。详细代码见com.xkzhangsan.time.utils.CommonCache,核心代码片段如下:

        /**
         * 从缓存池中查找值
         *
         * @param key
         *            键
         * @return*/
        public V get(K key) {
            lock.readLock().lock();
            try {
                return cache.get(key);
            } finally {
                lock.readLock().unlock();
            }
        }
    
        /**
         * 从缓存池中查找值,没有时尝试生成
         *
         * @param key
         *            键
         * @param supplier 提供者
         * @return*/
        public V get(K key, Supplier<V> supplier) {
            V value = get(key);
            if (value == null && supplier != null) {
                lock.writeLock().lock();
                try {
                    value = cache.get(key);
                    // 双重检查,防止在竞争锁的过程中已经有其它线程写入
                    if (null == value) {
                        try {
                            value = supplier.get();
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                        cache.put(key, value);
                    }
                } finally {
                    lock.writeLock().unlock();
                }
            }
            return value;
        }


     3. 二种实现性能对比


    这里以2021年放假信息为例,分别调用100万次。忽略第一次创建缓存的时间,从第二次开始,测试数据如下:

     2021-01-01:0,2021-02-07:1,2021-02-11:0,2021-02-12:0,2021-02-15:0,2021-02-16:0,2021-02-17:0,2021-02-20:1,2021-04-05:0,2021-04-25:1,2021-05-03:0,2021-05-04:0,2021-05-05:0,2021-05-08:1,2021-06-14:0,2021-09-18:1,2021-09-20:0,2021-09-21:0,2021-09-26:1,2021-10-01:0,2021-10-04:0,2021-10-05:0,2021-10-06:0,2021-10-07:0,2021-10-09:1

        @Test
        public void isChineseWorkDay1(){
            //2021年放假信息
            String holidayData = "2021-01-01:0,2021-02-07:1,2021-02-11:0,2021-02-12:0,2021-02-15:0,2021-02-16:0,2021-02-17:0,2021-02-20:1,2021-04-05:0,2021-04-25:1,2021-05-03:0,2021-05-04:0,2021-05-05:0,2021-05-08:1,2021-06-14:0,2021-09-18:1,2021-09-20:0,2021-09-21:0,2021-09-26:1,2021-10-01:0,2021-10-04:0,2021-10-05:0,2021-10-06:0,2021-10-07:0,2021-10-09:1";
            //指定日期是否是工作日
            long s = 0;
            for (int i = 0; i < 1000001; i++) {
                if(i==1){
                    s = System.currentTimeMillis();
                }
                DateTimeCalculatorUtil.isChineseWorkDay(LocalDateTime.now(), holidayData);
            }
            System.out.println("isChineseWorkDay1 cost1:"+(System.currentTimeMillis()-s));
        }
        
        @Test
        public void isChineseWorkDay2(){
            //2021年放假信息
            String holidayData = "2021-01-01:0,2021-02-07:1,2021-02-11:0,2021-02-12:0,2021-02-15:0,2021-02-16:0,2021-02-17:0,2021-02-20:1,2021-04-05:0,2021-04-25:1,2021-05-03:0,2021-05-04:0,2021-05-05:0,2021-05-08:1,2021-06-14:0,2021-09-18:1,2021-09-20:0,2021-09-21:0,2021-09-26:1,2021-10-01:0,2021-10-04:0,2021-10-05:0,2021-10-06:0,2021-10-07:0,2021-10-09:1";
            //指定日期是否是工作日
            long s = 0;
            for (int i = 0; i < 1000001; i++) {
                if(i==1){
                    s = System.currentTimeMillis();
                }
                DateTimeCalculatorUtil.isChineseWorkDay2(LocalDateTime.now(), holidayData);
            }
            System.out.println("isChineseWorkDay2 cost2:"+(System.currentTimeMillis()-s));
        }

    结果(单位:ms):

    isChineseWorkDay1 cost1:5589
    isChineseWorkDay2 cost2:366

    可以看到,使用缓存后性能对比 5589/366=15.27 , 速度提高15倍多,代码性能的小优化,大量调用后会被累加放大,优化非常值得!

    源代码地址:https://github.com/xkzhangsan/xk-time

    寻找撬动地球的支点(解决问题的方案),杠杆(Java等编程语言)已经有了。xkzhangsan
  • 相关阅读:
    poj 2388
    BUAA 1489
    poj 2524
    poj 2109
    poj 2503 Babelfish
    poj All in All
    poj 1611 The Suspects
    poj 2299
    poj 1328
    hdu 1008 Elevator
  • 原文地址:https://www.cnblogs.com/xkzhangsanx/p/14853917.html
Copyright © 2020-2023  润新知