【问题标题】:Why parsing a String into Date in Java is slow? Can we accelerate it?为什么在 Java 中将字符串解析为日期很慢?我们可以加速吗?
【发布时间】:2023-03-30 07:35:01
【问题描述】:

我正在读取一个包含日期的文本文件,并且我想将表示日期的字符串解析为 java 中的 Date 对象。我注意到的是操作很慢。为什么?有什么方法可以加速吗? 我的文件看起来像:

2012-05-02 12:08:06:950, secondColumn, thirdColumn
2012-05-02 12:08:07:530, secondColumn, thirdColumn
2012-05-02 12:08:08:610, secondColumn, thirdColumn

我逐行读取文件,然后从每一行获取日期String,然后使用SimpleDateFormat 将其解析为Date 对象,如下所示:

DataInputStream in = new DataInputStream(myFileInputStream);
BufferedReader  br = new BufferedReader(new InputStreamReader(in));
String strLine;

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while ((strLine = br.readLine()) != null)
{
    ....Do things....
    Date myDateTime = (Date)formatter.parse(myDateString);
    ...Do things....
}

【问题讨论】:

  • 您是否尝试在整个文件解析操作中使用相同的 SimpleDateFormat 实例?
  • 你是如何确定它慢的?
  • 发布的代码不足以说明您是如何处理这种情况的。您的文件中有多少行,需要多长时间?
  • 看一下 SimpleDateFormat::parse(String) 的代码就知道这不是一件容易的事。特别是错误处理是相当多的东西。如果您的日期看起来总是相同,您可以自己从行中解析它们并相应地填充日期实例。如果这样更快,我也不敢事先回答。
  • 我真的希望人们停止将 DataInputStream 与 BufferedReader 混合使用。谁开始这个模因...... grrr。

标签: java date simpledateformat date-parsing


【解决方案1】:

日期和时区的转换成本很高。如果您可以假设您的日期/时间彼此相似,则您可以在分钟更改时转换日期和小时/分钟(如果您使用 GMT,则仅转换日期)并自行生成秒数。

这将每分钟调用一次parse。根据您的假设,您可以每小时或每天一次。

String pattern = "yyyy-MM-dd HH:mm";
SimpleDateFormat formatter = new SimpleDateFormat(pattern);
String lastTime = "";
long lastDate = 0;
while ((strLine = br.readLine()) != null) {
    String myDateString = strLine.split(", ")[0];
    if (!myDateString.startsWith(lastTime)) {
        lastTime = myDateString.substring(0, pattern.length());
        lastDate = formatter.parse(lastTime).getTime();
    }
    Date date = new Date(lastDate + Integer.parseInt(myDateString.substring(pattern.length() + 1).replace(":", "")));
}

【讨论】:

  • +1 获取代码示例。 OP - 我们可以假设文件按日期/时间顺序排列吗?
【解决方案2】:

tl;博士

  • 使用 java.time 而不是遗留类。
  • StringLocalDateTimeDateTimeFormatter 的每次解析需要不到 1,500 纳秒(0.0000015 秒)。

java.time

您正在使用麻烦的旧日期时间类,它们现在是遗留的,被 java.time 类所取代。

让我们做一些微基准测试,看看在 java.time 中解析日期时间字符串有多慢/快。

ISO 8601

ISO 8601 标准定义了合理的实用格式,用于以文本方式表示日期时间值。 java.time 类在解析/生成字符串时默认使用这些标准格式。

使用这些标准格式,而不是发明自己的格式,如问题所示。

DateTimeFormatter

定义格式模式以匹配您的输入。

DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd HH:mm:ss:SSS" );

我们会将每个此类输入解析为 LocalDateTime,因为您的输入缺少时区或与 UTC 的偏移量的指示符。请记住,这些值代表一个时刻,不是时间轴上的一个点。成为一个真实的时刻需要一个区域/偏移的上下文。

String inputInitial = "2012-05-02 12:08:06:950" ;
LocalDateTime ldtInitial = LocalDateTime.parse( inputInitial , f );

让我们做一堆这样的输入。

int count = 1_000_000;
List < String > inputs = new ArrayList <>( count );

for ( int i = 0 ; i < count ; i++ )
{
    String s = ldtInitial.plusSeconds( i ).format( f );
    inputs.add( s );
}

测试工具。

long start = System.nanoTime();
for ( String input : inputs )
{
    LocalDateTime ldt = LocalDateTime.parse( input , f );
}
long stop = System.nanoTime();
long elapsed = ( stop - start );
long nanosPerParse = (elapsed / count ) ;
Duration d = Duration.ofNanos( elapsed );

转储到控制台。

System.out.println( "Parsing " + count + " strings to LocalDateTime took: " + d  + ". About " + nanosPerParse + " nanos each.");

将 1000000 个字符串解析为 LocalDateTime 耗时:PT1.320778647S。每个大约 1320 纳秒。

太慢了?

因此,在配备四核 Intel i7 CPU 的 MacBook Pro 笔记本电脑上,解析一百万个这样的输入需要大约一秒半的时间。在我的测试运行中,每次解析大约需要 1,000 到 1,500 纳秒。

在我看来,这不是性能问题。


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

从哪里获得 java.time 类?

  • Java SE 8Java SE 9 及更高版本
    • 内置。
    • 标准 Java API 的一部分,带有捆绑实现。
    • Java 9 添加了一些小功能和修复。
  • Java SE 6Java SE 7
    • ThreeTen-Backport 中的大部分 java.time 功能都向后移植到 Java 6 和 7。
  • Android
    • java.time 类的 Android 捆绑包实现的更高版本。
    • 对于早期的 Android (ThreeTenABP 项目采用 ThreeTen-Backport(如上所述)。见How to use ThreeTenABP…

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

  • 迄今为止最好的答案!!太好了!!
  • 有趣的是,“LocalDateTime.parse(..).toEpochSecond(ZoneOffset.UTC)”实际上比使用相同模式(“yyyy- MM-dd'T'HH:mm:ss")。对我来说大约 10-15%。我没想到。
【解决方案3】:

我建议编写一个自定义解析器,它会更快。比如:

Date parseYYYYMMDDHHMM(String strDate) {
   String yearString = strDate.substring(0, 4);
   int year = Integer.parseInt(yearString);
   ...

另一种方法是使用预先计算的日期时间哈希图(无毫秒)到 unix-timestamp。如果没有太多不同的日期将起作用(或者您可以在日期翻转后重新计算它)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-11
    • 2013-06-05
    • 2020-02-24
    • 2012-10-21
    • 1970-01-01
    • 2017-11-04
    • 1970-01-01
    相关资源
    最近更新 更多