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



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

e。g:

  • 123.345.432 = 00001
  • 123.354.433 = 00002

通过uid检查映射,以确保使用相同的内部id。

DICOMUID2StudyIdentiferMap定义如下

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需要几分钟才能从磁盘加载。一旦加载,它也需要很长时间来检查以前的值,例如查看DICOMUID2StudyIdentiferMap.put(…,…)将返回一个值。

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

为什么它在一台机器上这么慢,而在eclipse中却没有?

这台机器是一台运行XP的虚拟机,它最近才开始变得很慢,所以它一定和它的大小有关,但是400kb不是很大,我不认为。

欢迎任何建议,TIA

正如@biziclop所评论的那样,您应该首先使用一个分析器来查看应用程序在哪里花费了所有的时间。

如果这还没有给你任何结果,这里有几个理论。

  • 这可能是您的应用程序即将耗尽堆。当JVM接近耗尽堆时,它可能会花费几乎所有的时间进行垃圾收集,徒劳地试图继续运行。

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

为什么它在一台机器上这么慢,而在eclipse中却没有?

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

不确定序列化Map是否是最佳选择。如果Map是基于磁盘的持久性,为什么不使用专为磁盘设计的库呢?看看《京都内阁》。它实际上是用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();

这里是122个NoSQL数据库的列表,你可以使用作为替代。

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

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


private Map<Integer, Integer> mapping = new LinkedHashMap<Integer, Integer>();
public void saveTo(File file) throws IOException {
    DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
    dos.writeInt(mapping.size());
    for (Map.Entry<Integer, Integer> entry : mapping.entrySet()) {
        dos.writeInt(entry.getKey());
        dos.writeInt(entry.getValue());
    }
    dos.close();
}
public void loadFrom(File file) throws IOException {
    DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
    mapping.clear();
    int len = dis.readInt();
    for (int i = 0; i < len; i++)
        mapping.put(dis.readInt(), dis.readInt());
    dis.close();
}
public static void main(String[] args) throws IOException {
    Random rand = new Random();
    Main main = new Main();
    for (int i = 1; i <= 3000; i++) {
        // 100,000,000 to 999,999,999
        int uid = 100000000 + rand.nextInt(900000000); 
        main.mapping.put(uid, i);
    }
    final File file = File.createTempFile("deleteme", "data");
    file.deleteOnExit();
    for (int i = 0; i < 10; i++) {
        long start = System.nanoTime();
        main.saveTo(file);
        long mid = System.nanoTime();
        new Main().loadFrom(file);
        long end = System.nanoTime();
        System.out.printf("Took %.3f ms to save and %.3f ms to load %,d entries.%n",
                (end - mid) / 1e6, (mid - start) / 1e6, main.mapping.size());
    }
}

打印

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.

使用TIntIntHashMap可以快10%。

将Map的大小增加到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.

也许您应该寻找类似于Map的替代方案,例如SimpleDB, BerkeleyDB或Google BigTable。

Voldemort是Linkedin的一个流行的开源键值存储。我建议您看一下源代码,看看它们是如何工作的。现在我正在https://github.com/voldemort/voldemort/blob/master/src/java/voldemort/serialization/ObjectSerializer.java上查看序列化部分。看看他们使用ByteArrayOutputStream的代码,我认为这是更有效的方式来读取/写入磁盘。

为什么它在一台机器上这么慢,而在eclipse中却没有?

不是很清楚从你的问题,但Eclipse运行在VM(VirtualBox?)?因为如果是这样的话,可能会更快,因为整个VM存储在内存中,这比访问磁盘快得多。

我认为这可能是一个散列问题。您在Map中使用的键的类型是什么?它是否具有有效的hashCode()方法来很好地展开键?

相关内容

最新更新