1. 概述

在处理包含日期时间字符串的数据集时,对这些字符串进行排序是许多Java应用程序中的常见任务。

在这个教程中,我们将探讨在Java中有效地对日期字符串进行排序的不同方法。

2. 问题介绍

我们可以直接按照特定的日期格式(如ISO日期时间格式 YYYY-MM-dd'T' HH:mm:ss)对字符串进行字典序排序。然而,这不是一种通用的日期字符串排序解决方案

我们不能对所有日期时间格式的字符串都进行字典序排序。例如,假设我们有一个这样的字符串列表:

List<String> dtStrings = Lists.newArrayList(
  "01/21/2013 10:41",
  "01/20/2013 10:48",
  "01/22/2013 15:13",
  "01/21/2013 16:37",
  "01/21/2013 17:16",
  "01/21/2013 17:19",
  "01/20/2013 06:16",
  "01/22/2013 06:19"
);

如果列表中的字符串按正确顺序排列,结果应如下所示:

List<String> EXPECTED = Lists.newArrayList(
  "01/20/2013 06:16",
  "01/20/2013 10:48",
  "01/21/2013 10:41",
  "01/21/2013 16:37",
  "01/21/2013 17:16",
  "01/21/2013 17:19",
  "01/22/2013 06:19",
  "01/22/2013 15:13"
);

我们将探讨解决排序问题的不同方法。同时,为了简化,我们将使用单元测试断言来验证每个解决方案是否产生预期的结果。

接下来,让我们看看它们的实际应用。

3. 使用自定义 Comparator

Java标准库提供了Collections.sort()方法来对集合中的元素进行排序。如果我们想按字典顺序对字符串列表进行排序,可以直接将列表传递给Collections.sort()方法。此外,该方法也接受一个Comparator对象作为第二个参数。

现在,让我们看看如何使用自定义Comparator对日期时间字符串进行排序:

DateFormat dfm = new SimpleDateFormat("MM/dd/yyyy HH:mm");
Collections.sort(dtStrings, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        try {
            return dfm.parse(o1).compareTo(dfm.parse(o2));
        } catch (ParseException e) {
            throw new IllegalArgumentException(e);
        }
    }
});
assertEquals(EXPECTED, dtStrings);

如代码所示,首先,我们根据日期字符串的格式创建了一个*SimpleDateFormat对象。然后,在调用Collections.sort()方法时,我们将dtStrings列表与一个匿名的Comparator*对象一起传递。

compare()方法实现中,我们首先将两个日期时间字符串解析为Date对象,然后比较两个Date对象

如果我们使用的Java版本为8或更高,我们可以利用强大的lambda表达式比较功能,使我们的代码更紧凑、易读:

final DateTimeFormatter dfm = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm");
dtStrings.sort(Comparator.comparing(s -> LocalDateTime.parse(s, dfm)));
assertEquals(EXPECTED, dtStrings);

需要注意的是,**Collections.sort()list.sort()方法都支持原地排序,这意味着原始列表会直接修改,而不需要创建一个新的已排序副本**。这在内存效率和性能方面具有显著优势。

4. 使用Stream API

另外,要对日期时间字符串列表进行排序,可以采取三个步骤:

  1. 将字符串元素转换为LocalDateTime实例
  2. 对这些LocalDateTime对象进行排序
  3. LocalDateTime对象转换回字符串

Stream API使我们能够方便地处理集合。如果我们使用Stream API实现这个想法,*map()*方法可以帮助我们执行转换:

DateTimeFormatter dfm = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm");
List<String> sortedList = dtStrings.stream()
  .map(s -> LocalDateTime.parse(s, dfm))
  .sorted()
  .map(dfm::format)
  .collect(Collectors.toList());
assertEquals(EXPECTED, sortedList);

Collections.sort()list.sort()解决方案不同,这种方法不会更改原始列表。相反,它返回一个新的列表来存放排序后的字符串

5. 使用TreeMap

Java中的TreeMap类提供了基于键自动排序条目的功能。通过利用这一特性,我们可以轻松地根据LocalDateTime和字符串的键值对对日期时间字符串进行排序。

然后,如果我们使用treeMap.values()方法获取TreeMap中的所有值,就能得到排序结果。

接下来,让我们在一个测试中实现这一点:

DateTimeFormatter dfm = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm");
Map<LocalDateTime, String> dateFormatMap = new TreeMap<>();
dtStrings.forEach(s -> dateFormatMap.put(LocalDateTime.parse(s, dfm), s));
List<String> result = new ArrayList<>(dateFormatMap.values());
assertEquals(EXPECTED, result);

这个解决方案很简单。但是,它有一个缺点。*标准Java Map不允许有重复的键,因此在使用TreeMap排序后,重复的日期时间字符串会被丢失**。因此,在应用TreeMap*方法排序之前,最好确保列表中没有重复的值。

6. 总结

在这篇文章中,我们探讨了对日期字符串进行排序的不同通用解决方案:

  • 使用自定义ComparatorCollections.sort()list.sort()(原地排序)
  • 将字符串转换为日期对象,对对象进行排序,再转换回字符串
  • TreeMap方法

如往常一样,这里展示的所有代码片段都可以在GitHub上找到。