我想知道的是在更新对象在游戏中的位置时应该使用System.currentTimeMillis()还是System.nanoTime()吗?它们的运动变化与自上次调用以来经过的时间成正比,我想尽可能地精确。
我读到不同操作系统之间存在一些严重的时间分辨率问题(即Mac / Linux的分辨率几乎为1毫秒,而Windows的分辨率为50毫秒?)。我主要是在Windows上运行我的应用,而50ms分辨率似乎很不准确。
有没有比我列出的两个更好的选择?
有什么建议/评论吗?
#1 楼
如果您只是想对经过的时间进行非常精确的测量,请使用System.nanoTime()
。 System.currentTimeMillis()
将为您提供自该纪元以来最准确的经过时间(以毫秒为单位),但是System.nanoTime()
为您提供相对于某个任意点的纳秒级精确时间。来自Java文档:
public static long nanoTime()
返回最精确的可用系统计时器的当前值,以纳秒为单位。
此方法只能用于
测量经过时间,并且
与系统的任何其他概念或钟表时间都不相关。返回的值
表示纳秒,因为某些
是固定的但任意的起始时间(可能在
以后,所以值可能为
负)。此方法提供
纳秒精度,但不一定提供纳秒精度。没有
保证
值如何频繁变化。连续呼叫中的差异
大于大约292年(263
纳秒),将无法准确地
由于数值
溢出而计算经过时间。
例如,测量执行某些代码所需的时间:
long startTime = System.nanoTime();
// ... the code being measured ...
long estimatedTime = System.nanoTime() - startTime;
另请参阅:JavaDoc System.nanoTime()和JavaDoc System .currentTimeMillis()了解更多信息。
评论
您确定知道准确性和准确性之间的区别吗?它不可能精确到纳秒级的精度。
– mmcdole
08年12月9日在2:09
对不起,我是说精确。我使用的术语比较松散,但我同意它会造成混淆(以及该词的使用不当)。
– dancavallaro
08年12月9日在2:10
@dancavallaro,感谢您的信息。如果您不介意,我会编辑您的答案以包含文档中的引号,并修复链接
– mmcdole
08年12月9日在2:28
在选择nanoTime()时,此答案在技术上是正确的,但在一个非常重要的方面完全没有意义。正如文档所说,nanoTime()是一个精密计时器。 currentTimeMillis()不是计时器,它是“挂钟”。 nanoTime()总是会产生正的经过时间,currentTimeMillis不会(例如,如果您更改日期,hit秒等),这对于某些类型的系统来说是非常重要的区别。
– charstar
2011年4月10日上午10:32
用户更改时间和NTP同步可以确定,但是为什么currentTimeMillis由于DST而更改? DST开关不会更改经过纪元的秒数。它可能是“挂钟”,但它是基于UTC的时钟。您必须根据时区和DST设置确定本地时间的含义(或使用其他Java实用程序为您完成此操作)。
–影人
2014年1月24日21:43
#2 楼
由于没有人提到过这个问题……比较不同线程之间的
System.nanoTime()
调用的结果并不安全。即使线程的事件以可预测的顺序发生,纳秒级的差异也可以是正数或负数。System.currentTimeMillis()
可在线程之间安全使用。评论
在仅保留至SP2的Windows上,根据:stackoverflow.com/questions/510462/…
– Peter Schmitz
2012-2-22在11:25
好吧,您每天都会学到一些新东西。但是我怀疑,由于过去它是不安全的(肯定会在线程间返回无意义的读数),所以这种用法可能仍超出规范范围,因此应避免使用。
– GUB
2012年3月4日14:55
@jgubby:非常有趣...对支持的引用,在不同线程之间比较System.nanoTime()调用的结果并不安全?以下链接值得一看:bugs.sun.com/bugdatabase/view_bug.do?bug_id=6519418docs.oracle.com/javase/7/docs/api/java/lang/…
–user454322
2012年10月9日在10:45
看一下这里提到的描述:docs.oracle.com/javase/7/docs/api/java/lang/…,看起来只要从相同的时间从nanoTime返回的值之间的差异就可以比较有效JVM-仅当计算在Java虚拟机的同一实例中获得的两个此类值之间的差时,此方法返回的值才有意义。
–Tuxdude
16 Jan 20'23:01
用于nanoTime()的JavaDoc说:在Java虚拟机的实例中,此方法的所有调用都使用相同的来源。其他虚拟机实例可能使用其他来源。这意味着它确实跨线程返回相同的结果。
–西蒙·福斯伯格
18年2月1日在16:02
#3 楼
由Arkadiy更新:在Oracle Java 8中,我已经观察到Windows 7上System.currentTimeMillis()
的更正确的行为。该时间以1毫秒的精度返回。 OpenJDK中的源代码没有更改,因此我不知道是什么导致了更好的行为。Sun的David Holmes在几年前发布了一篇博客文章,其外观非常详细。在Java计时API(尤其是
System.currentTimeMillis()
和System.nanoTime()
)上,何时使用它们以及它们如何在内部工作。在Hotspot VM内部:时钟,计时器和调度事件-第一部分- Windows
Windows上Java为具有定时等待参数的API使用的计时器的一个非常有趣的方面是,计时器的分辨率可以根据可能进行的其他API调用而改变-系统(不仅在特定过程中)。他显示了一个示例,其中使用
Thread.sleep()
会导致此分辨率更改。评论
@Arkadiy:您在更新中是否有声明的来源?
– Lii
2015年12月15日在17:40
@Lii-不幸的是,没有。它来自于我在以下问题中运行代码:stackoverflow.com/questions/34090914/…。该代码在Java 7中产生15毫秒的精度,在Java 8中产生1毫秒的精度。
–user3458
15年12月15日在18:01
我在OpenJDK的hotspot / src / os / windows / vm / os_windows.cpp中将currentTimeMillis跟踪到os :: javaTimeMillis(hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/os / ...) 。看起来它仍然是GetSystemTimeAsFileTime,所以我不知道更改来自何处。或者,即使它是有效的。使用前进行测试。
–user3458
15年12月16日在17:44
行为上的变化是更改GetSystemTimeAsFileTime在XP vs 7中的工作方式的结果。有关更多详细信息,请参见此处。(tl; dr变得更加精确,因为整个系统引入了一些更精确的计时方法)。
–user719662
17年7月18日在22:08
#4 楼
旧版JVM不支持System.nanoTime()
。如果这是一个问题,请坚持使用currentTimeMillis
关于精度,您几乎是正确的。在某些Windows机器上,currentTimeMillis()
的分辨率约为10毫秒(而非50毫秒)。我不确定为什么,但是某些Windows机器和Linux机器一样准确。我过去使用GAGETimer取得了一定的成功。
评论
“旧版JVM”和哪个? Java 1.2之类的?
–西蒙·福斯伯格
17-10-20在15:07
System.nanoTime()于2004年在Java 1.5中引入。2013年Java 1.4扩展了对它的支持,因此可以肯定地说System.nanoTime()现在可以依靠,并且这个答案现在已经过时了。
–戴夫L。
1月22日下午16:45
#5 楼
就像其他人所说的那样,currentTimeMillis是时钟时间,它是由于夏令时而改变的(不是:夏令时和时区与currentTimeMillis无关,其余的都是事实),用户更改时间设置,leap秒和Internet时间同步。如果您的应用依赖于单调增加的经过时间值,那么您可能更喜欢nanoTime。您可能会认为玩家不会在游戏过程中摆弄时间设置,也许您是对的。但是,请不要低估由于Internet时间同步或远程桌面用户造成的中断。 nanoTime API不受这种干扰的影响。
如果您想使用时钟时间,但要避免由于Internet时间同步而导致中断,则可以考虑使用诸如Meinberg之类的NTP客户端,它可以将时钟速率“调整”到将其归零,而不仅仅是定期重置时钟。
我是根据个人经验讲的。在我开发的天气应用程序中,我得到了随机出现的风速峰值。我花了一段时间才意识到典型的PC上的时钟时间行为干扰了我的时基。当我开始使用nanoTime时,我所有的问题都消失了。一致性(单调性)对我的应用程序比原始精度或绝对精度更重要。
评论
“ currentTimeMillis是时钟时间,它会由于夏时制而改变”。 System.currentTimeMillis()报告自Unix / Posix纪元(即UTC 1970年1月1日午夜)以来的经过时间(以毫秒为单位)。因为从不为夏令时调整UTC,所以当当地时区增加时或以夏时制为当地时间设置偏移时,该值将不会被抵消。此外,Java Time Scale消除了leap秒,因此日期似乎继续具有86,400秒。
–斯科特
16年5月23日在5:27
#6 楼
是的,如果需要这种精度,请使用System.nanoTime()
,但是请注意,您那时需要Java 5+ JVM。在我的XP系统上,我看到系统时间至少报告为100微秒278纳秒。以下代码:
private void test() {
System.out.println("currentTimeMillis: "+System.currentTimeMillis());
System.out.println("nanoTime : "+System.nanoTime());
System.out.println();
testNano(false); // to sync with currentTimeMillis() timer tick
for(int xa=0; xa<10; xa++) {
testNano(true);
}
}
private void testNano(boolean shw) {
long strMS=System.currentTimeMillis();
long strNS=System.nanoTime();
long curMS;
while((curMS=System.currentTimeMillis()) == strMS) {
if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)); }
}
if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)+", Milli: "+(curMS-strMS)); }
}
#7 楼
对于游戏图形和平滑的位置更新,请使用System.nanoTime()
而不是System.currentTimeMillis()
。我在游戏中从currentTimeMillis()切换为nanoTime(),并在视觉上改善了运动的平滑度。虽然一毫秒似乎应该已经很精确了,但在视觉上却不是。
nanoTime()
可以改善的因素包括:在壁钟分辨率以下的精确像素定位
如果需要,像素之间抗锯齿的能力
Windows壁钟不准确
时钟抖动(实际时钟向前滴答的时间不一致)
其他答案表明,如果反复调用,nanoTime确实会降低性能,因此最好将其称为每帧一次,并使用相同的值来计算整个帧。
#8 楼
我在nanotime方面有很好的经验。使用JNI库,它可以将挂钟时间提供为两个很长的时间(从纪元开始的秒数,在该秒数秒内为纳秒)。它与针对Windows和Linux预先编译的JNI部件一起提供。#9 楼
System.currentTimeMillis()
对于经过的时间并不安全,因为此方法对系统的系统实时时钟更改敏感。 应使用
System.nanoTime
。请参考Java系统帮助:
关于nanoTime方法:
..此方法提供纳秒级精度,但不一定是
纳秒分辨率(即值更改的频率)-没有保证,除非分辨率至少等于currentTimeMillis()的分辨率。 。
如果使用
System.currentTimeMillis()
,则经过时间可能为负(返回<-返回到未来)#10 楼
这里的一件事是nanoTime方法的不一致。对于相同的输入,它不会给出非常一致的值。currentTimeMillis在性能和一致性方面要好得多,并且尽管不如nanoTime精确,但误差范围较小,因此其值的准确性更高。因此,我建议您使用currentTimeMillis评论
如其他答案和注释中所述,currentTimeMillis会随系统时钟的变化而变化,因此自JVM中发生某个较早的事件以来,计算经过的时间的选择就很差。
–都灵标记
13-10-14在19:00
评论
nanoTime通常通常比currentTimeMillis更为准确,但它也是一个相对昂贵的调用。currentTimeMillis()运行在几个(5-6)cpu时钟中,nanoTime取决于基础架构,并且可以是100+ cpu时钟。大家都知道Windows通常具有1000ms / 64的时间片粒度,对吗?这是15.625毫秒,即15625000纳秒!
我认为,额外的100个时钟周期不会影响您的游戏,因此折衷可能值得。您每次游戏更新仅应调用一次该方法,然后将值保存在mem中,这样就不会增加太多开销。至于不同平台的粒度,我不知道。
Windows的默认时间片粒度为1000ms / 64。您可以通过本地的timeBeginPeriod API来增加它。除了基本计时器外,现代PC还具有高分辨率计时器。可通过QueryPerformanceCounter调用访问高分辨率计时器。
@Gohan-本文详细介绍了System.currentTimeMillis()的内部工作原理:pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html