大数据量日期计算优化

在批量处理场景(如解析 100 万条日志的时间戳、计算 100 万订单的超时时间)中,低效的日期计算会导致:

  • CPU 密集型开销:频繁的日期加减、时区转换涉及复杂的历法逻辑(如月份天数、闰年),消耗大量 CPU。

  • 对象膨胀:每条数据创建一个LocalDateTime对象,导致内存占用激增,触发 GC。

下面给出了三种优化方案:

优化方案

预计算时间间隔常量

对于固定间隔(如 1 天、30 分钟),预定义毫秒数常量,避免重复计算:

public class TimeIntervalConstants {
    public static final long MILLIS_PER_SECOND = 1000L;
    public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
    public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
    public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;
    public static final long MILLIS_PER_WEEK = 7 * MILLIS_PER_DAY;
}

// 使用时直接引用常量
long expireTime = createTime + TimeIntervalConstants.MILLIS_PER_DAY;

示例代码:

package com.hxstrive.java_date_time.performance;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

public class BatchDateCalculation2 {
    private static final long ONE_DAY_MILLIS = 24 * 60 * 60 * 1000L; // 1天的毫秒数

    // 优化前
    public LocalDateTime[] calculateExpireTimes(LocalDateTime[] createTimes) {
        LocalDateTime[] result = new LocalDateTime[createTimes.length];
        for (int i = 0; i < createTimes.length; i++) {
            long createMillis = createTimes[i].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
            long expireMillis = createMillis + 24 * 60 * 60 * 1000L; // 看这里
            result[i] = LocalDateTime.ofInstant(Instant.ofEpochMilli(expireMillis), ZoneId.systemDefault());
        }
        return result;
    }
    
    // 优化后
    public LocalDateTime[] calculateExpireTimesOptimized(LocalDateTime[] createTimes) {
        LocalDateTime[] result = new LocalDateTime[createTimes.length];
        for (int i = 0; i < createTimes.length; i++) {
            long createMillis = createTimes[i].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
            long expireMillis = createMillis + ONE_DAY_MILLIS; // 看这里
            result[i] = LocalDateTime.ofInstant(Instant.ofEpochMilli(expireMillis), ZoneId.systemDefault());
        }
        return result;
    }

    public static void main(String[] args) {
        LocalDateTime[] localDateTimes1 = new LocalDateTime[1000000];
        LocalDateTime[] localDateTimes2 = new LocalDateTime[1000000];

        long oneDay = 24 * 60 * 60 * 1000L;
        for(int i = 0; i < localDateTimes1.length; i++) {
            localDateTimes1[i] = LocalDateTime.ofInstant(Instant.ofEpochMilli(oneDay * i), ZoneId.systemDefault());
            localDateTimes2[i] = LocalDateTime.ofInstant(Instant.ofEpochMilli(oneDay * i), ZoneId.systemDefault());
        }
        BatchDateCalculation2 demo = new BatchDateCalculation2();

        // 优化前
        long start = System.currentTimeMillis();
        demo.calculateExpireTimes(localDateTimes1);
        System.out.println("优化前耗时:" + (System.currentTimeMillis() - start));

        // 优化后 —— 更慢了
        long start2 = System.currentTimeMillis();
        demo.calculateExpireTimesOptimized(localDateTimes2);
        System.out.println("优化后耗时:" + (System.currentTimeMillis() - start2));
    }
}

运行结果:

优化前耗时:508
优化后耗时:409

  

批量解析:减少时区转换开销

解析大量字符串时,若所有字符串均为同一时区(如均为北京时间),可先将时区转换逻辑抽离,避免重复计算。例如:

package com.hxstrive.java_date_time.performance;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

public class BatchParsing {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final ZoneId ZONE = ZoneId.of("Asia/Shanghai");
    
    // 优化前:每条数据单独处理时区
    public List<Instant> parseToInstant(List<String> dateStrs) {
        List<Instant> result = new ArrayList<>(dateStrs.size());
        for (String str : dateStrs) {
            // 每次解析都包含时区转换(重复计算)
            LocalDateTime ldt = LocalDateTime.parse(str, FORMATTER);
            result.add(ldt.atZone(ZONE).toInstant());  // 看这里
        }
        return result;
    }
    
    // 优化后:提前绑定时区到Formatter,减少转换步骤
    public List<Instant> parseToInstantOptimized(List<String> dateStrs) {
        // 关键:创建带有时区的Formatter,解析时直接得到Instant
        DateTimeFormatter formatterWithZone = FORMATTER.withZone(ZONE);  // 看这里
        List<Instant> result = new ArrayList<>(dateStrs.size());
        for (String str : dateStrs) {
            // 直接解析为Instant,减少中间对象
            result.add(Instant.from(formatterWithZone.parse(str)));
        }
        return result;
    }

    public static void main(String[] args) {
        List<String> dateStrList = new ArrayList<>();
        for(int i = 0; i < 1000000; i++) {
            dateStrList.add(FORMATTER.format(LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())));
        }

        BatchParsing demo = new BatchParsing();
        // 优化前
        long start = System.currentTimeMillis();
        demo.parseToInstant(dateStrList);
        System.out.println("优化前耗时:" + (System.currentTimeMillis() - start));

        // 优化后
        long start2 = System.currentTimeMillis();
        demo.parseToInstantOptimized(dateStrList);
        System.out.println("优化后耗时:" + (System.currentTimeMillis() - start2));
    }
}

运行结果:

优化前耗时:805
优化后耗时:617

  

并行处理:利用多核 CPU

对于超大数据量(如 1000 万+),可使用并行流(parallelStream)或线程池拆分任务,利用多核 CPU 提升效率。例如:

package com.hxstrive.java_date_time.performance;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class ParallelDateProcessing {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
            .withZone(ZoneId.of("Asia/Shanghai"));

    // 普通解析大量时间字符串
    public List<Instant> parse(List<String> dateStrs) {
        List<Instant> retList = new ArrayList<>(dateStrs.size());
        for(String dateStr : dateStrs) {
            retList.add(Instant.from(FORMATTER.parse(dateStr)));
        }
        return retList;
    }

    // 并行解析大量时间字符串
    public List<Instant> parseInParallel(List<String> dateStrs) {
        // 使用并行流自动拆分任务到多个线程
        return dateStrs.parallelStream()
                .map(str -> Instant.from(FORMATTER.parse(str)))
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        List<String> dateStrList = new ArrayList<>();
        for(int i = 0; i < 1000000; i++) {
            dateStrList.add(FORMATTER.format(LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())));
        }

        ParallelDateProcessing demo = new ParallelDateProcessing();
        // 优化前
        long start = System.currentTimeMillis();
        demo.parse(dateStrList);
        System.out.println("优化前耗时:" + (System.currentTimeMillis() - start));

        // 优化后
        long start2 = System.currentTimeMillis();
        demo.parseInParallel(dateStrList);
        System.out.println("优化后耗时:" + (System.currentTimeMillis() - start2));
    }
}

运行结果:

优化前耗时:838
优化后耗时:177

  

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号