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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章