Вопрос по java, hashmap, hashtable, key-value – Выпуск пользовательского кода HashMap

2

У меня есть следующий код, где я использовал HashMap (используя два параллельных массива) для хранения пар ключ-значение (ключ может иметь несколько значений). Теперь я должен сохранить и загрузить его для будущего использования, поэтому я сохраняю и загружаю его с помощью File Channel. Проблема с этим кодом заключается в следующем: я могу хранить около 120 миллионов пар ключ-значение на моем 8 ГБ сервере (на самом деле, я могу выделить почти 5 ГБ из 8 ГБ для моей JVM, и эти два параллельных массива занимают почти 2,5 ГБ, другие память используется для различной обработки моего кода). Но мне нужно хранить около 600/700 миллионов пар ключ-значение. Может ли кто-нибудь помочь мне, как изменить этот код, таким образом, я могу хранить около 600/700 миллионов пар ключ-значение. Или любой комментарий по этому поводу будет приятным для меня. Еще один момент, я должен загрузить и сохранить хэш-карту в / из памяти. Использование файлового канала занимает немного много времени. Согласно различным предложениям по переполнению стека, я не нашел более быстрого. Я использовал ObjectOutputStream, поток вывода Zipped также, однако, медленнее, чем приведенный ниже код. Есть ли в любом случае хранить эти два параллельных массива таким образом, что время загрузки будет намного быстрее. Я дал ниже в моем коде тестовый пример. Любой комментарий по этому поводу также будет полезен для меня.

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);
        }
    }
}
Рассматривали ли вы использовать существующий примитивный код карты? Просто гуглjava primitive map Alexander Pogrebnyak
@SamGoldberg, да. Я использовал ObjectOutputStream, занимает больше времени. Arpssss
Вы думали о горизонтальном масштабировании? Существует множество быстрых баз данных NoSQL со значением ключа, которые масштабируются горизонтально на нескольких серверах. Хранение такого количества данных на одной машине становится болезненным, как вы можете видеть ... Tomasz Nurkiewicz
Для сохранения и загрузки вы сравнивали сериализацию LongIntParallelHashMultimap непосредственно на диск (вместо перебора ключей и значений и хранения в отдельных файлах)? Sam Goldberg
@ TomaszNurkiewicz, извините. Я не могу использовать распределенный подход, я должен сделать это локально. Arpssss

Ваш Ответ

3   ответа
0

а не в памяти. Перепишите ваши операции так, чтобы они не работали с массивами, а вместо этого работали с буферами. Затем вы можете открыть достаточно большой файл и получить от операций доступ к той части, которая им нужна, с помощью сопоставленного буфера. Попробуйте проверить, работает ли ваше приложение лучше, когда вы реализуете кэш нескольких недавно отображенных областей памяти, поэтому вам не придется слишком часто отображать и отображать общие области, а вместо этого можно сохранять их отображенными.

Это должно дать вам лучшее из обоих миров, диска и оперативной памяти:

Random access to any portion of the data structure is easy to implement Access to often used portions of the table will be cached Seldom used portions of the table will not occupy any memory

Как видите, это во многом зависит от локальности: если некоторые ключи более распространены, чем другие, все будет работать хорошо, тогда как правильно распределенные ключи будут вызывать новую операцию на диске для каждого доступа. Таким образом, хотя хорошие распределения желательны для большинства хэш-карт в памяти, другие структуры, которые отображают часто используемые ключи в подобные местоположения, будут работать лучше здесь. Тем не менее, они будут мешать обработке столкновений.

2

что это возможно, учитывая типы данных, которые вы объявили. Просто умножьте размеры примитивных типов.

Каждая строка требует 4 байта для хранения целого и 8 байтов для хранения длинного. 600 миллионов строк * 12 байт на строку = 7200 МБ = 7,03 ГБ. Вы говорите, что можете выделить 5 ГБ для JVM. Таким образом, даже если все это было в куче и хранилось только этот пользовательский HashMap, он не подходит. Подумайте об уменьшении размера используемых типов данных или хранении их где-то кроме ОЗУ.

Джон, там все говорят о Redis, который не поддерживает ключ с множественным значением. Я также использовал Tokyo Cabinet, но медленнее, чем приведенный выше код. Arpssss
Спасибо за ответ. То, что я на самом деле спрашиваю, кроме ОЗУ означает диск с резервной копией и использование подкачки страниц, верно? Но как это сделать значит быстрее? В настоящее время я решил это путем деления БД. Но hashmap в load-store занимает много времени, и поиск для решения этих проблем. Arpssss
Да, это определенно будет медленнее. Но ваша текущая система не может удовлетворить ваши требования. Таким образом, ваши требования к оборудованию должны возрасти или ваши требования к производительности должны снизиться. Кроме того, вы можете посмотреть, как вы используете данные. Например, если вы часто ищете определенные его подмножества, сохраните эти подмножества для быстрого поиска. Если вы часто просматриваете большинство значений в предсказуемом порядке, рассмотрите возможность их потоковой передачи из файла, а не сохранять их на карте.
Если вы хотите увеличить объем данных, которые вы можете хранить, вам нужно больше оперативной памяти или поместить некоторые данные на диск. Если вы решите поместить избыток на диск, вы, вероятно, захотите использовать какую-то базу данных для управления ею, да. Я рекомендую базу данных SQL или хранилище ключей / значений, подобные тем, которые упомянуты в ответах на этот вопрос:stackoverflow.com/questions/2376846/….
0

такую как sqlite, которая даст хороший результат.

Похожие вопросы