【问题标题】:Custom HashMap Code Issue自定义 HashMap 代码问题
【发布时间】:2012-07-09 01:41:34
【问题描述】:

我有以下代码,其中我使用 HashMap(使用两个并行数组)来存储键值对(键可以有多个值)。现在,我必须存储和加载它以供将来使用,这就是我使用文件通道存储和加载它的原因。这段代码的问题是:我可以在我的 8 GB 服务器中存储近 1.2 亿个键值对(实际上,我可以为我的 JVM 分配 8 GB 中的近 5 GB,而这两个并行数组占用将近 2.5 GB,其他内存用于我的代码的各种处理)。但是,我必须存储近 600/7 亿个键值对。任何人都可以帮助我如何修改此代码,因此我可以存储近 600/7 亿个键值对。或者对此的任何评论对我来说都会很好。还有一点,我必须在内存中加载和存储哈希图。使用文件通道需要一点时间。根据 Stack Overflow 的各种建议,我没有找到更快的建议。我也使用过 ObjectOutputStream,压缩输出流,但是比下面的代码慢。无论如何以这种方式存储这两个并行数组,因此加载时间会快得多。我在下面的代码中给出了一个测试用例。对此的任何评论也将对我有所帮助。

import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Arrays;
import java.util.Random;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.io.RandomAccessFile;

public class Test {

    public static void main(String args[]) {


        try {

            Random randomGenerator = new Random();

            LongIntParallelHashMultimap lph = new LongIntParallelHashMultimap(220000000, "xx.dat", "yy.dat");

            for (int i = 0; i < 110000000; i++) {
                lph.put(i, randomGenerator.nextInt(200000000));
            }

            lph.save();

            LongIntParallelHashMultimap lphN = new LongIntParallelHashMultimap(220000000, "xx.dat", "yy.dat");
            lphN.load();

            int tt[] = lphN.get(1);

            System.out.println(tt[0]);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class LongIntParallelHashMultimap {

    private static final long NULL = -1L;
    private final long[] keys;
    private final int[] values;
    private int size;
    private int savenum = 0;
    private String str1 = "";
    private String str2 = "";

    public LongIntParallelHashMultimap(int capacity, String st1, String st2) {
        keys = new long[capacity];
        values = new int[capacity];
        Arrays.fill(keys, NULL);
        savenum = capacity;
        str1 = st1;
        str2 = st2;
    }

    public void put(long key, int value) {
        int index = indexFor(key);
        while (keys[index] != NULL) {
            index = successor(index);
        }
        keys[index] = key;
        values[index] = value;
        ++size;
    }

    public int[] get(long key) {
        int index = indexFor(key);
        int count = countHits(key, index);
        int[] hits = new int[count];
        int hitIndex = 0;

        while (keys[index] != NULL) {
            if (keys[index] == key) {
                hits[hitIndex] = values[index];
                ++hitIndex;
            }
            index = successor(index);
        }

        return hits;
    }

    private int countHits(long key, int index) {
        int numHits = 0;
        while (keys[index] != NULL) {
            if (keys[index] == key) {
                ++numHits;
            }
            index = successor(index);
        }
        return numHits;
    }

    private int indexFor(long key) {
        return Math.abs((int) ((key * 5700357409661598721L) % keys.length));
    }

    private int successor(int index) {
        return (index + 1) % keys.length;
    }

    public int size() {
        return size;
    }

    public void load() {
        try {
            FileChannel channel2 = new RandomAccessFile(str1, "r").getChannel();
            MappedByteBuffer mbb2 = channel2.map(FileChannel.MapMode.READ_ONLY, 0, channel2.size());
            mbb2.order(ByteOrder.nativeOrder());
            assert mbb2.remaining() == savenum * 8;
            for (int i = 0; i < savenum; i++) {
                long l = mbb2.getLong();
                keys[i] = l;
            }
            channel2.close();

            FileChannel channel3 = new RandomAccessFile(str2, "r").getChannel();
            MappedByteBuffer mbb3 = channel3.map(FileChannel.MapMode.READ_ONLY, 0, channel3.size());
            mbb3.order(ByteOrder.nativeOrder());
            assert mbb3.remaining() == savenum * 4;
            for (int i = 0; i < savenum; i++) {
                int l1 = mbb3.getInt();
                values[i] = l1;
            }
            channel3.close();
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public void save() {
        try {
            FileChannel channel = new RandomAccessFile(str1, "rw").getChannel();
            MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_WRITE, 0, savenum * 8);
            mbb.order(ByteOrder.nativeOrder());

            for (int i = 0; i < savenum; i++) {
                mbb.putLong(keys[i]);
            }
            channel.close();

            FileChannel channel1 = new RandomAccessFile(str2, "rw").getChannel();
            MappedByteBuffer mbb1 = channel1.map(FileChannel.MapMode.READ_WRITE, 0, savenum * 4);
            mbb1.order(ByteOrder.nativeOrder());

            for (int i = 0; i < savenum; i++) {
                mbb1.putInt(values[i]);
            }
            channel1.close();
        } catch (Exception e) {
            System.out.println("IOException : " + e);
        }
    }
}

【问题讨论】:

  • 你考虑过水平缩放吗?有很多快速键值 NoSQL 数据库可以在多个服务器上水平扩展。如您所见,在一台机器上存储这么多数据变得很痛苦......
  • 对于保存和加载,您是否比较过将 LongIntParallelHashMultimap 直接序列化到磁盘(而不是遍历键和值并存储在单独的文件中)?
  • @TomaszNurkiewicz,对不起。我不能使用分布式方法,我必须在本地进行。
  • 您是否考虑过使用现有的原始地图代码?只需谷歌java primitive map
  • @SamGoldberg,是的。我使用了 ObjectOutputStream,需要更多时间。

标签: java hashmap hashtable key-value


【解决方案1】:

鉴于您声明的数据类型,我怀疑这是可能的。只需将原始类型的大小相乘即可。

每行需要 4 个字节来存储一个 int 和 8 个字节来存储一个 long。 6 亿行 * 每行 12 字节 = 7200 MB = 7.03 GB。你说你可以分配 5 GB 给 JVM。所以即使全是堆,只存储这个自定义的HashMap,也放不下。考虑缩小所涉及数据类型的大小或将其存储在 RAM 以外的位置。

【讨论】:

  • 感谢您的回复。我实际上是在问,除了 RAM 意味着磁盘支持并使用页面交换,对吗?但是,如何做到这一点意味着更快?目前,我通过划分数据库来解决它。但是,加载-存储 hashmap 需要花费大量时间,并且需要通过搜索来解决这些问题。
  • 如果你想增加可以存储的数据量,你要么需要更多的 RAM,要么将一些数据放在磁盘上。如果您选择将多余的部分放在磁盘上,您可能希望使用某种数据库来管理它,是的。我推荐一个 SQL 数据库或一个键/值存储,就像这个问题的答案中提到的那样:stackoverflow.com/questions/2376846/…
  • John,大家都建议 Redis 不支持 key- 多值。我也用过东京内阁,但比上面的代码慢。
  • 是的,肯定会慢一些。但是您当前的系统可能无法满足您的要求。因此,要么您的硬件要求需要提高,要么您的性能要求需要降低。此外,您也许可以查看您使用数据的方式。例如,如果您经常查找它的某些子集,请存储这些子集以便快速检索。如果您经常以可预测的顺序迭代大多数值,请考虑从文件中流式传输它们,而不是将它们存储在地图中。
【解决方案2】:

将数据库放在磁盘上,而不是在内存中。重写您的操作,使它们不对数组进行操作,而是对缓冲区进行操作。然后您可以打开一个足够大的文件,并让操作使用映射缓冲区访问他们需要的部分。尝试在您实现少数最近映射的内存区域的缓存时,您的应用程序是否会更好地执行,这样您就不必过于频繁地映射和取消映射公共区域,而是可以将它们映射进去。

这应该为您提供两全其美的磁盘和内存:

  • 对数据结构任何部分的随机访问很容易实现
  • 对表中常用部分的访问将被缓存
  • 表中很少使用的部分不会占用任何内存

如您所见,这在很大程度上取决于局部性:如果某些键比其他键更常见,则性能会很好,而分布良好的键将导致每次访问都进行新的磁盘操作。因此,虽然大多数内存中的哈希映射都需要良好的分布,但将常用键映射到相似位置的其他结构在这里会表现得更好。不过,这些会干扰碰撞处理。

【讨论】:

    【解决方案3】:

    最好使用像sqlite这样的内存数据库,效果会很好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-01
      相关资源
      最近更新 更多