我只是将模块从旧的Java日期迁移到新的java.time API,并注意到性能大幅下降。它归结为具有时区的日期解析(我一次解析数百万个日期)。
没有时区(yyyy/MM/dd HH:mm:ss
)的日期字符串的解析速度很快-比旧的Java日期快约2倍,在我的PC上每秒约有150万次操作。
但是,当模式包含时区(yyyy/MM/dd HH:mm:ss z
)时,使用新java.time
API 的性能下降约15倍,而使用旧API
的性能大约与没有时区的性能一样。请参阅下面的性能基准。
是否有人可以使用新的java.time
API
快速解析这些字符串?目前,作为一种解决方法,我正在使用旧的API进行解析,然后将转换Date
为Instant,这并不是特别好。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OperationsPerInvocation;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(1)
@Fork(1)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
@State(Scope.Thread)
public class DateParsingBenchmark {
private final int iterations = 100000;
@Benchmark
public void oldFormat_noZone(Blackhole bh, DateParsingBenchmark st) throws ParseException {
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
for(int i=0; i<iterations; i++) {
bh.consume(simpleDateFormat.parse("2000/12/12 12:12:12"));
}
}
@Benchmark
public void oldFormat_withZone(Blackhole bh, DateParsingBenchmark st) throws ParseException {
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z");
for(int i=0; i<iterations; i++) {
bh.consume(simpleDateFormat.parse("2000/12/12 12:12:12 CET"));
}
}
@Benchmark
public void newFormat_noZone(Blackhole bh, DateParsingBenchmark st) {
DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy/MM/dd HH:mm:ss").toFormatter();
for(int i=0; i<iterations; i++) {
bh.consume(dateTimeFormatter.parse("2000/12/12 12:12:12"));
}
}
@Benchmark
public void newFormat_withZone(Blackhole bh, DateParsingBenchmark st) {
DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy/MM/dd HH:mm:ss z").toFormatter();
for(int i=0; i<iterations; i++) {
bh.consume(dateTimeFormatter.parse("2000/12/12 12:12:12 CET"));
}
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(DateParsingBenchmark.class.getSimpleName()).build();
new Runner(opt).run();
}
}
10万次运算的结果:
Benchmark Mode Cnt Score Error Units
DateParsingBenchmark.newFormat_noZone avgt 5 61.165 ± 11.173 ms/op
DateParsingBenchmark.newFormat_withZone avgt 5 1662.370 ± 191.013 ms/op
DateParsingBenchmark.oldFormat_noZone avgt 5 93.317 ± 29.307 ms/op
DateParsingBenchmark.oldFormat_withZone avgt 5 107.247 ± 24.322 ms/op
更新:
我只是对java.time类进行了分析,实际上,时区解析器似乎效率很低。仅解析一个独立的时区是造成所有缓慢的原因。
@Benchmark
public void newFormat_zoneOnly(Blackhole bh, DateParsingBenchmark st) {
DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
.appendPattern("z").toFormatter();
for(int i=0; i<iterations; i++) {
bh.consume(dateTimeFormatter.parse("CET"));
}
}
有一个名为类ZoneTextPrinterParser
的java.time
捆绑,这是内部制作组在每一个所有可用时区的副本parse()
呼叫(通过ZoneRulesProvider.getAvailableZoneIds()
),这是在开发区解析花费的时间99%的责任。
那么,答案可能是编写我自己的区域解析器,也不会太好,因为那样我就无法构建DateTimeFormatter
via了appendPattern()
。
如您的问题和我的评论中所述,每次需要解析时区时,ZoneRulesProvider.getAvailableZoneIds()
都会创建一组新的所有可用时区的字符串表示形式(的键static final ConcurrentMap<String, ZoneRulesProvider> ZONES
)。1个
幸运的是,a ZoneRulesProvider
是一个abstract
旨在被子类化的类。该方法protected abstract Set<String> provideZoneIds()
负责填充ZONES
。因此,如果子类提前知道要使用的 所有
时区,则只能提供所需的时区。由于该类提供的条目要少于包含数百个条目的默认提供程序,因此它有可能显着减少的调用时间getAvailableZoneIds()
。
该ZoneRulesProvider
API提供了如何注册一个指令。请注意,不能取消注册提供程序,只能对其进行补充,因此,删除默认提供程序并添加您自己的提供程序不是一件简单的事情。系统属性java.time.zone.DefaultZoneRulesProvider
定义默认提供程序。如果返回null
(通过System.getProperty("..."
),则将加载JVM的臭名昭著的提供程序。使用System.setProperty("...", "fully-qualified name of a concrete ZoneRulesProvider class")
一个可以提供自己的提供程序,这就是第二段中讨论的提供程序。
最后,我建议:
abstract class ZoneRulesProvider
protected abstract Set<String> provideZoneIds()
只用所需的时区。我自己没有这样做,但是我
确信它会因为某种原因而失败,因为
它会起作用。
1在问题注释中建议,在1.8版本之间,调用的确切性质可能已更改。
编辑: 找到更多信息
上述默认ZoneRulesProvider
值final class TzdbZoneRulesProvider
位于中java.time.zone
。从路径中读取该类中的区域:(JAVA_HOME/lib/tzdb.dat
在我的情况下,它在JDK的JRE中)。该文件确实包含许多区域,下面是一个片段:
TZDB 2014cJ Africa/Abidjan Africa/Accra Africa/Addis_Ababa Africa/Algiers
Africa/Asmara
Africa/Asmera
Africa/Bamako
Africa/Bangui
Africa/Banjul
Africa/Bissau Africa/Blantyre Africa/Brazzaville Africa/Bujumbura Africa/Cairo Africa/Casablanca Africa/Ceuta Africa/Conakry Africa/Dakar Africa/Dar_es_Salaam Africa/Djibouti
Africa/Douala Africa/El_Aaiun Africa/Freetown Africa/Gaborone
Africa/Harare Africa/Johannesburg Africa/Juba Africa/Kampala Africa/Khartoum
Africa/Kigali Africa/Kinshasa Africa/Lagos Africa/Libreville Africa/Lome
Africa/Luanda Africa/Lubumbashi
Africa/Lusaka
Africa/Malabo
Africa/Maputo
Africa/Maseru Africa/Mbabane Africa/Mogadishu Africa/Monrovia Africa/Nairobi Africa/Ndjamena
Africa/Niamey Africa/Nouakchott Africa/Ouagadougou Africa/Porto-Novo Africa/Sao_Tome Africa/Timbuktu Africa/Tripoli Africa/Tunis Africa/Windhoek America/Adak America/Anchorage America/Anguilla America/Antigua America/Araguaina America/Argentina/Buenos_Aires America/Argentina/Catamarca America/Argentina/ComodRivadavia America/Argentina/Cordoba America/Argentina/Jujuy America/Argentina/La_Rioja America/Argentina/Mendoza America/Argentina/Rio_Gallegos America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis America/Argentina/Tucuman America/Argentina/Ushuaia
America/Aruba America/Asuncion America/Atikokan America/Atka
America/Bahia
然后,如果找到了一种仅用所需区域创建类似文件并加载该区域的方法,那么性能问题
可能
肯定无法解决。
当我尝试构建时,ide抛出错误:“未解析的引用:X” X是(公共)类Java上的任何Java常量(公共静态final) 无法使用: 我尝试过: 这是一个混合项目,Java 我可以使用静态方法,例如: 错误行: 我正在使用: Gradle配置 软件 堆栈跟踪:
问题内容: 我有解析日期的代码,如下所示: 一切正常,突然,这停止了。原来,管理员在服务器上进行了一些配置更改,并且当前返回的日期为“ 2010-12-27T10:50:44.000-08:00”,上述模式无法解析该日期。我有两个问题: 第一种是哪种模式将解析上述格式的JVM返回的日期(特别是时区为“ -08:00”)?其次,在Linux RHEL 5服务器上,究竟会在哪里更改此类设置,以便我们将
问题内容: 我有两个时间戳,它们以两种不同的格式描述同一时刻。 和。 我用Joda-Time用两个不同的日期格式解析时间戳。最后,我想有两个DateTime对象,它们在同一时刻相同。 DateFormatter提供了几种控制时区和语言环境的方法,但我无法使其正常工作。 这是我想工作的代码: 问题答案: 如果您的默认时间zome是Europe / Berlin,则2010-10-03 18:58:0
我不确定是否可以这样做,但我想将一个非常简单的JSON文件解析为一个字符串数组。 示例文件: 到目前为止,我认为我应该使用带有模式的扫描仪来获得我的输出,但没有做到这一点。 因为这个模式显然是错误的,因为它认为“,”是合适的,但我希望它不包括在内...: S 我也接受一些建议,这些建议可能会以任何其他方式被解析。也许是JSON解析器?但是因为文件太简单了,我认为没有必要。
问题内容: 我在解析具有以下格式的输入字符串中的日期时遇到问题: 引发异常: 我敢打赌它与GMT字符串有关。我想我已经有尝试过,,,和。有什么想法吗?输入是否是标准的有效输入格式? 问题答案: 问题是根据Java Timezone规范, 无效的GMT字符串。 解决方案 :操纵输入字符串的时区部分。==>
有一个简单的xml,格式如下 在启动应用程序时,我生成一个id,并在视频中进行设置。XML的appid属性。 我使用一个简单的DOM解析器,XML得到正确更新,但在重复启动过程中,注意到两个属性之间的空间正在增加。经过五次左右的修改,文件看起来像下面的那个。 我删除属性并将其重新添加到XML中,我想这是问题所在。在写回它时,有什么方法可以重新格式化整个文件吗?这样可以避免空行。 尝试在谷歌上搜索,