Java 存储在磁盘上的HashMap从磁盘读回的速度非常慢

Java 存储在磁盘上的HashMap从磁盘读回的速度非常慢,java,hashmap,writetofile,Java,Hashmap,Writetofile,我有一个存储外部uid的HashMap,然后它存储一个为给定uid设置的不同id(应用程序的内部id) e、 g: 123.345.432=00001 123.354.433=00002 uid检查映射以确保使用相同的内部id。如果应用程序重新发送了某些内容 DICOMUID2studyIdentifierMap定义如下: private static Map DICOMUID2StudyIdentiferMap = Collections.synchronizedMap(new HashM

我有一个存储外部uid的HashMap,然后它存储一个为给定uid设置的不同id(应用程序的内部id)

e、 g:

  • 123.345.432=00001
  • 123.354.433=00002
uid检查映射以确保使用相同的内部id。如果应用程序重新发送了某些内容

DICOMUID2studyIdentifierMap定义如下:

private static Map DICOMUID2StudyIdentiferMap = Collections.synchronizedMap(new HashMap());
但是,如果我们成功加载,加载将覆盖它,否则它将使用默认的空HashMap

通过执行以下操作将其从磁盘读回:

FileInputStream f = new FileInputStream( studyUIDFile );  
ObjectInputStream s = new ObjectInputStream( f );

Map loadedMap = ( Map )s.readObject();
DICOMUID2StudyIdentiferMap = Collections.synchronizedMap( loadedMap );
HashMap通过以下方式写入磁盘:

FileOutputStream f = new FileOutputStream( studyUIDFile );
ObjectOutputStream s = new ObjectOutputStream( f );

s.writeObject(DICOMUID2StudyIdentiferMap);
我遇到的问题是,在Eclipse中本地运行的性能很好,但是当应用程序在机器上正常运行时,从磁盘加载HashMap需要几分钟的时间。加载后,还需要很长时间来检查以前的值,例如查看DICOMUID2StudyIdentifierMap.put(…,…)是否将返回值

我在这两种情况下加载相同的映射对象,它是一个~400kb的文件。它包含的HashMap大约有3000个键值对

为什么它在一台机器上速度如此之慢,但在eclipse中却不是这样

这台机器是一台运行XP的虚拟机,只是最近才开始变慢来读取哈希图,所以它一定与它的大小有关,不过我认为400kb不是很大


欢迎任何建议,TIA不确定序列化地图是最佳选择。如果映射是基于磁盘的持久性,为什么不使用为磁盘设计的库呢?退房它实际上是用C++编写的,但是有一个java API。我已经使用过好几次了,它很容易使用,速度很快,可以扩展到很大的尺寸

这是我为东京内阁复制/粘贴的一个例子,东京内阁是京都的旧版本,但基本上是一样的:

import tokyocabinet.HDB;

....

String dir = "/path/to/my/dir/";
HDB hash = new HDB();

// open the hash for read/write, create if does not exist on disk
if (!hash.open(dir + "unigrams.tch", HDB.OWRITER | HDB.OCREAT)) {
    throw new IOException("Unable to open " + dir + "unigrams.tch: " + hash.errmsg());
}

// Add something to the hash
hash.put("blah", "my string");

// Close it
hash.close();

作为@biziclop的注释,您应该首先使用探查器查看您的应用程序在哪里花费了所有的时间

如果这不能给你任何结果,这里有一些理论

  • 可能是您的应用程序即将耗尽堆。当JVM即将耗尽堆时,它可能会花费几乎所有的时间进行垃圾收集,徒劳地继续。如果启用GC日志记录,则会显示此消息

  • 可能是ObjectInputStream和ObjectOutputStream正在执行大量的小型读取系统调用。尝试用缓冲流包装文件流,看看它是否显著加快了速度

为什么它在一台机器上速度如此之慢,但在eclipse中却不是这样


“满堆”理论可以解释这一点。Eclipse的默认堆大小比使用
java…
启动的应用程序大得多,没有堆大小选项。

也许你应该寻找类似于
地图的替代方案,例如SimpleDB、BerkeleyDB或Google BigTable。

是Linkedin流行的开源键值存储。我建议你看一下源代码,看看他们是怎么做的。现在,我正在查看的序列化部分位于。看看他们正在使用的代码,我认为这是一种更有效的读写光盘的方法

为什么它在一台机器上速度如此之慢,但在eclipse中却不是这样


您的问题并不是很清楚,但Eclipse是否在VM(VirtualBox?)中运行?因为如果是这样的话,可能会更快,因为完整的虚拟机存储在内存中,这比访问光盘快得多。

下面是122个NoSQL数据库的列表,您可以使用它们作为替代方案

这里有两个昂贵的操作,一个是对象序列化,另一个是磁盘访问。只需读取/写入所需数据,即可加快访问速度。您可以通过使用自定义格式来加速序列化

您还可以更改数据的结构以提高效率。如果每次都要重新加载/重写整个地图,我建议使用以下方法


改用TIntIntHashMap大约快10%

将地图的大小增加到100万个条目

Took 412.718 ms to save and 62.009 ms to load 1,000,000 entries.
Took 403.135 ms to save and 61.756 ms to load 1,000,000 entries.
Took 399.431 ms to save and 61.816 ms to load 1,000,000 entries.

我认为这可能是一个哈希问题。您在映射中使用的密钥类型是什么,它是否有一个有效的hashCode()方法可以很好地分布密钥?

我的建议是使用jvisualvm攻击应用程序,以找出所有时间都花在了哪里。另一个选择是删除同步包装器,看看情况是否有所改善。这非常有用,谢谢。然而,我把Richard H的解决方案标记为我的答案,因为我将使用他建议的方法。
Took 1.203 ms to save and 1.706 ms to load 3,000 entries.
Took 1.209 ms to save and 1.203 ms to load 3,000 entries.
Took 0.961 ms to save and 0.966 ms to load 3,000 entries.
Took 412.718 ms to save and 62.009 ms to load 1,000,000 entries.
Took 403.135 ms to save and 61.756 ms to load 1,000,000 entries.
Took 399.431 ms to save and 61.816 ms to load 1,000,000 entries.