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

时间:2011-07-13 11:34:16

标签: java hashmap writetofile

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

例如:

  • 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(...,...)是否将返回值来检查以前的值也需要很长时间。

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

为什么一台机器上这么慢,但不是在日食中?

这台机器是运行XP的VM,它最近才开始慢慢读取HashMap,所以它必须与它的大小有关,但400kb不是很大,我不认为。

欢迎任何建议,TIA

6 个答案:

答案 0 :(得分:4)

作为@biziclop的评论,您应该首先使用分析器查看您的应用程序在所有时间内花费的时间。

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

  • 可能是您的应用程序接近堆耗尽。随着JVM接近堆耗尽,它几乎可以花费大量时间进行垃圾收集,徒劳无功。如果您启用GC日志记录,则会显示此信息。

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

  

为什么一台机器上这么慢,但不是在日食中?

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

答案 1 :(得分:2)

不确定序列化地图是最佳选择。如果Map是基于磁盘的持久性,为什么不使用专为磁盘设计的lib?查看Kyoto Cabinet。它实际上是用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();

答案 2 :(得分:1)

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

答案 3 :(得分:1)

Voldemort是Linkedin广受欢迎的开源键值商店。我建议你看看源代码,看看他们是如何做的。现在我正在查看https://github.com/voldemort/voldemort/blob/master/src/java/voldemort/serialization/ObjectSerializer.java的序列化部分。查看他们使用的代码ByteArrayOutputStream,我认为这是更有效的方式来读/写光盘。

  

为什么一台机器上的速度如此之慢,而不是在日食中?

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

答案 4 :(得分:1)

以下是您可以使用的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%。

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

答案 5 :(得分:1)

我认为这可能是一个哈希问题。你在Map中使用的键的类型是什么,它是否有一个有效的hashCode()方法来扩展键?