【问题标题】:Updating Date & Time after every period of time在每个时间段后更新日期和时间
【发布时间】:2015-04-21 13:15:08
【问题描述】:

我想为我正在开发的一个小型 Java 实用程序创建自己的基本控制台日志。一切都基本实时运行良好;但是,有一小段代码困扰着我。我将简要介绍以下所有代码。

注意:这里的几乎所有代码都未经测试/未经优化。如果我做错了什么,告诉我,我很愿意接受批评。

用于记录目的的静态变量:

static ArrayList<String> logs = new ArrayList<>();
static int offset = 0;
static DateFormat dateFormat = new SimpleDateFormat("yyy/MM/dd HH:mm:ss");
static Date date = new Date();

填充JTextArea 的代码 - 这会附加日志消息并记住偏移量(因此我不会遍历整个 ArrayList 以获取已记录的内容)。

public static void populateTextArea(final JTextArea textArea) {
  for(int i = offset; i < logs.size()-1; i++) {
      textArea.append(dateFormat.format(date) + ":  " + logs.get(i)+"\n");
  }
      offset = logs.size()-1;
}

以下内容每 150 毫秒自动更新一次控制台:

public static void timerLogging(final JTextArea textArea) {
Timer timer = new Timer(150, new java.awt.event.ActionListener() {
    public void actionPerformed(java.awt.event.ActionEvent e)
       {
           updateDateAndTime();
           populateTextArea(textArea);
       }
    });
    timer.start();
}

现在困扰我的是以下部分:

public static void updateDateAndTime() {
    date = new Date();
}

有没有办法简单地做一些事情,比如date = GlobalDate.getCurrentDate();?以目前的设置方式,它会不会产生很小的开销(可能甚至不明显,但我不是那种“让它工作”的人)?

【问题讨论】:

  • 请记住,如果您花时间担心小额开销,那么您花在真正重要的事情上的时间就会少得多。
  • 请注意,如果您更改该 Date 实例的值,那么您将为引用同一实例的“每个人”更改它。
  • 好点卡亚曼。 @Tom - 它仅用于控制台,在这种情况下我认为它确实是安全的。
  • 作为旁注 - 您似乎正在使用全局 SimpleDateFormat 实例和 TimerTask。如果您碰巧在代码中使用了多个 TimerTask,请记住 SimpleDateFormat 不是线程安全的(您现在拥有的代码不是问题,但如果您使用多个 TimerTask 可能会出现问题)
  • @KresimirNesek 你能详细说明一下吗?我对你想说的话有一个简单的理解,但我没有完全理解。

标签: java


【解决方案1】:

没有“开销”可言:每秒七次创建一个额外的对象不算作开销。

但是,如果您想保留相同的Date 对象,您可以按照Date 的默认构造函数使用的相同方式重新初始化它:

public static void updateDateAndTime() {
    date.setTime(System.currentTimeMillis());
}

【讨论】:

  • 每次你对约会对象进行变异,一只小猫就会死去。我更喜欢您的第一个实现,每次都创建一个新日期。每个日志在不同的时间完成,它应该是不同的 Date 对象。
  • @Guillaume - 我几乎想问一个具体的问题,似乎有很多 cmets 认为java.lang.Date 有点问题。不过,我敢肯定已经有很多问题了。
  • @Juxhin 我希望java.util.Date 是不可变的,但是由于设计者已弃用除setTime 之外的所有变异方法,因此在我看来,他们故意使类保持可变。不过,我不赞同垂死的小猫学说:我认为使用 API 的所有功能是可以的,即使你在哲学上不同意它的设计者 :)
  • 嗯,对于我的预期用途,它似乎工作正常,所以我会保持这种方式。我确实理解他们的观点,并且在一定程度上同意它。再次感谢大家的意见。
【解决方案2】:

当你在同一个句子中看到 Java 和 Date 时,首先要检查的地方是 Joda Time

java.lang.Date 是可变的,因此您可以保留相同的日期对象并更改它所代表的日期:

date.setTime(System.currentTimeMillis());

java.lang.Date 是可变的这一事实通常被认为是 Java API 设计方式中的一个错误。今天和明天不是同一个日期,它们不应该是同一个对象。

Java 8 中的 Joda-Time 和新的 java.time 包都主要使用 immutable 对象。

【讨论】:

  • 我之前确实听说过并读过 Joda Time(实际上就像一年前一样使用它,但忘记了什么)。我读过某位 Jon(在 SO 上很有名)的评论,他说了同样的话,请问为什么这被认为是 Java API 中的一个缺陷?因为您实际上可以更改当前日期?
  • @Juxhin 不,问题不在于更改 JVM 或主机操作系统中时钟的当前时间,因为 java.util.Date 不能这样做。阅读 Wikipedia page 和 google 以了解有关不可变对象的更多信息。
  • 可变Dates 的问题是你倾向于传递Date 对象。如果它们是可变的,则意味着它们可能会在您使用它们时发生变化,这在大多数情况下是出乎意料的。
【解决方案3】:

避免过早的优化

正如其他两个答案所指出的,通常您不必担心在 Java 中创建对象的开销。至少 Oracle 提供的实现针对短期对象的创建和销毁进行了高度优化。每秒几个不是问题。

在编写长时间运行的紧密循环时,您可能会暂时考虑重用对象。但只有在证明存在性能问题时才致力于减少实例化。否则你会落入premature optimization的陷阱。

ISO 8601

ISO 8601 标准为日期时间值的文本表示定义了合理的格式。 YYYY-MM-DDTHH:MM:SS.SSSZ 格式适用于日志记录。

UTC

使用UTC 时区中的日期时间值几乎总是更好,然后调整到本地时区以呈现给用户。这意味着您的业务逻辑、数据库存储和日志记录应该使用 UTC。

Joda-Time 和 java.time

与 Java 捆绑在一起的 java.util.Date 和 .Calendar 类是出了名的麻烦、混乱和缺陷。避开他们。

改为使用 Joda-Time 库或 Java 8 中内置的新 java.time package(受 Joda-Time 启发)。

在生成或解析字符串时,Joda-Time 和 java.time 都使用 ISO 8601 作为默认值。 java.time 类通过附加时区名称来扩展 ISO 8601,除了通常的偏移量。

示例代码 - Joda-Time

使用 Joda-Time 2.7 的示例代码。

DateTime nowUtc = DateTime.now( DateTimeZone.UTC ) ;

示例输出。

2013-08-21T00:16:26.941+09:00

为了向用户展示,请指定另一个时区。

DateTime nowQuébec = nowUtc.withZone( DateTimeZone.forID( "America/Montreal" ) ) ;

示例代码 - java.time

在 Java 8 中使用 java.time 的示例代码。

ZonedDateTime nowUtc = ZonedDateTime.now( ZoneOffset.UTC );

示例输出。

2013-08-21T00:16:26.941+09:00[Asia/Tokyo]

调整时区。

ZoneId zone = ZoneId.of("America/Montreal"); 
ZonedDateTime nowQuébec = ZonedDateTime.of( nowUtc , zone ) ;

Joda-Time 表演

每秒创建多个DateTime 实例不是问题。

引用常见问题解答条目,How well does it perform?...

Joda-Time 专为性能而设计。与 java.util.Calendar、java.text.SimpleDateFormat 和 java.util.TimeZone 相比,Joda-Time 中几乎所有等效操作都更快。重要的例外是获取或设置单个字段的操作。

在 java.util.Calendar 上调用“get”非常快,因为它没有任何作用。日历会提前计算所有字段,即使其中许多字段您不需要。 Calendar 的 set 方法很快,因为它将计算推迟到以后。在调用 Calendar.set 之后调用 Calendar.get 会强制重新计算所有字段值。在调用 DateTime.set 之后调用 Joda 的 DateTime.get 方法只执行最小量的计算,并且比 Calendar 更快。

Joda-Time 在操作过程中也分配了很少的临时对象,并且几乎不执行线程同步。在高度多线程或使用大量内存的系统中,Calendar、SimpleDateFormat 和 TimeZone 可能会成为瓶颈。当使用 Joda-Time 类时,瓶颈就消失了。

执行者

您可能希望将您对Timer 的使用替换为Executor,特别是ScheduledExecutorService。 Timer 在很大程度上已被 Executor 淘汰。尤其是在 Servlet/Java EE Web 应用程序中,您永远不应该使用 Timer。搜索 StackOverflow 和 Google 以获取更多信息。

日志框架

如果您的问题确实与日志记录有关,我建议您学习使用 Java 中可用的几种优秀日志记录框架之一。

slf4j

第一步是获取slf4j。这个项目只是接口,一个API抽象。

因为Java中有这么多的日志框架,你可能会在不同的项目中遇到不同的,或者你可能想在自己的项目中切换框架。问题是日志记录意味着您在整个代码库中都有许多调用。所以学习或改变框架很麻烦。 slf4j 项目旨在通过提供一组您在整个代码中调用的接口来解决这个问题。在幕后,适配器介入以将这些 slf4j 调用转换为特定日志框架的调用。适配器适用于 log4j、Java 的 java.util.logging 等。

实际上,slf4j 还捆绑了一个非常简单的接口实现。此实现仅用于开发(帮助您入门),或用于非常简单的项目。默认情况下,该实现仅记录少数log levels,但您可以对其进行调整。你可以从这个简单的实现开始,然后切换到 Logback;这就是 slf4j 的全部意义所在,使您能够切换日志框架。

回退

此外,slf4j 的创建者还创建了Logback。 Logback 是一个全功能的日志框架,直接实现了 slf4j 的接口。作为接口的直接实现意味着不需要适配器。

顺便说一句,slf4j 和 Logback 的创建者 Ceki Gülcü 也是著名的 log4j 的创建者。他利用自己在日志记录方面的丰富经验创建了继任者 slf4j 和 Logback。

Logback 的一个显着缺陷是它不支持 ISO 8601 日期时间格式(如上所述)。您可以根据this StackOverflow Question 进行调整。

对于新项目,我建议从 slf4j + Logback 开始。对于使用另一个日志框架的现有项目,请在通过适配器通过 slf4j 进行调用时保留该日志框架。在未来的某一天,在所有的日志调用都被 slf4j 调用替换之后,如果需要,你可以选择用 Logback 替换你的日志框架。

【讨论】:

  • 非常感谢您的 inpit Basil 您提出了一些非常好的观点。我现在剥离了应用程序的整个日志记录方面,并正在考虑以 ypur 的方式重新实现它或简单地使用 log4j。
  • 不,不是log4j,那是上个千年。查看slf4j(API 抽象,只是接口)和logback(实现)。
  • 哈哈好吧,1993(或类似的)确实有点老了。我确实检查了SLF4J,所以我将使用logback 来实现它,或者再一次,只写我自己的非常基本的(因为我不需要任何详细的东西)。但是我确实希望学习在我未来的应用程序中正确实现 SLF4J。
  • 其实 slf4j 包含一个非常简单的实现,可能适合你的需要。默认情况下,它只记录一些优先级,但您可以调整它。另外,有趣的是,slf4j 和 logback 是由 log4j 的发明者构建的。
  • 请问,为什么log4j 还在被使用呢? - 关于 SLF4J,老实说,我在实现外国 API 方面非常薄弱。我一次又一次地抱怨它们的重要性,但我总是发现自己使用的是纯 Java API(我并不是很自豪地说)。所以我只需要强迫自己走出舒适区来使用 SLF4J。感谢您提供的众多提示和出色的帖子。我只需将其标记为正确答案。
猜你喜欢
  • 2018-02-09
  • 1970-01-01
  • 2021-10-04
  • 1970-01-01
  • 1970-01-01
  • 2011-09-06
  • 2013-12-12
  • 2021-08-21
  • 1970-01-01
相关资源
最近更新 更多