Ограничение максимального размера хэш-карты в Java

#java #hashmap

#java #Хэш-карта

Вопрос:

Я хочу ограничить максимальный размер HashMap , чтобы использовать показатели для различных алгоритмов хэширования, которые я внедряю. Я посмотрел на loadfactor в одном из HashMap перегруженных конструкторов.

 HashMap(int initialCapacity, float loadFactor) 
  

Я попытался установить loadFactor равным 0.0f в конструкторе (это означает, что я не хочу, чтобы размер HashMap когда-либо увеличивался), но javac называет это недопустимым:

 Exception in thread "main" java.lang.IllegalArgumentException: Illegal load factor: 0.0
        at java.util.HashMap.<init>(HashMap.java:177)
        at hashtables.CustomHash.<init>(Main.java:20)
        at hashtables.Main.main(Main.java:70) Java Result: 1
  

Есть ли другой способ ограничить размер HashMap , чтобы он никогда не увеличивался?

Комментарии:

1. Что должно произойти, когда карта заполнена и вы пытаетесь вставить другой элемент?

2. К вашему сведению, хэш-таблицам необходимо сжимать свое пространство ключей, потому что вы не можете зарезервировать 2 ^ 31 * 4 байта пространства памяти для хранения значения для каждого возможного ключа. Поэтому хэш-таблицы обычно усекают хэш и используют связанные списки для коллизий. loadFactor грубо указывает максимальный размер связанного файла, прежде чем таблица начнет использовать больше битов хэша. Следовательно, связанные списки длиной 0 не имеют смысла: вы не можете в них ничего хранить.

3. Коэффициент загрузки определяет, когда следует увеличить размер структуры данных. Начальный размер (i) и коэффициент загрузки (x) означают, что мы увеличиваем размер, когда у нас есть элементы i * x. если x = 0, это все равно что просить Java увеличивать размер структуры данных всякий раз, когда в ней 0 элементов.

Ответ №1:

Вы могли бы создать новый класс, подобный этому, чтобы ограничить размер хэш-карты:

 public class MaxSizeHashMap<K, V> extends LinkedHashMap<K, V> {
    private final int maxSize;

    public MaxSizeHashMap(int maxSize) {
        this.maxSize = maxSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxSize;
    }
}
  

Комментарии:

1. Просто для пояснения, если я правильно понимаю, таким образом, когда вы вставляете новый элемент, он просто удаляет самый старый элемент с карты и вставляет вместо него новый, таким образом ограничивая размер до maxSize . Дело не в том, что это не позволяет добавлять новые элементы.

Ответ №2:

Иногда чем проще, тем лучше.

 public class InstrumentedHashMap<K, V> implements Map<K, V> {

    private Map<K, V> map;

    public InstrumentedHashMap() {
        map = new HashMap<K, V>();
    }

    public boolean put(K key, V value) {
        if (map.size() >= MAX amp;amp; !map.containsKey(key)) {
             return false;
        } else {
             map.put(key, value);
             return true;
        }
    }

    ...
}
  

Комментарии:

1. Этот ответ ограничивает максимальный размер вашей карты. Смотрите ответ Маргуса о более простой карте, которая предотвращает ввод или удаление записей.

2. @mattburns, разве не в этом вопрос? Или вопрос был перефразирован после вашего комментария?

3. @sriman , ну да, это соответствует названию вопроса, но не подробному описанию вопроса. OP хотел, чтобы он никогда не рос (например, был неизменяемым). Но 10 лет спустя люди, читающие это, вероятно, будут здесь только потому, что они искали ограничение максимальной емкости хэш-карт … Meh

4. Вы можете расширить AbstractMap или HashMap, чтобы избежать необходимости переопределять весь интерфейс Map.

Ответ №3:

Обычно лучшим является простое решение, поэтому используйте неизменяемую или Unmutable хэш-карту.

Если вы не можете изменить количество элементов, то размер будет фиксированным — проблема решена.

Комментарии:

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

Ответ №4:

Я попытался установить loadFactor равным 0.0f в конструкторе (это означает, что я не хочу, чтобы размер HashMap когда-либо увеличивался), но javac называет это недопустимым

loadFactor Of 1.0f означает «не увеличиваться, пока хэш-карта не заполнится на 100%». loadFactor Of 0.0f означало бы «расти экспоненциально», если бы это было принято, вот почему это не так.

Из документов по хэш-карте:

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

Пример: Хэш-карта, инициализированная с настройками по умолчанию, имеет емкость 16 и коэффициент загрузки 0.75f . Capacity * load factor = 16 * 0.75 = 12 . Таким образом, добавление 13-го элемента в хэш-карту приведет к ее увеличению (приблизительно) до 32 сегментов.

Недопустимый пример: хэш-карта, инициализированная с емкостью 16 и коэффициентом загрузки 0.0f . Capacity * load factor = 16 * 0 = 0 . Таким образом, каждая попытка добавить элемент будет приводить к повторному хэшу и удвоению размера, пока у вас не закончится память.

То, что вы изначально хотели:

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

Если вы создадите хэш-карту с емкостью M > N, коэффициентом загрузки 1 и добавите N элементов, она не увеличится.

 Map<KeyType, ValueType> nonGrowingHashMap = new HashMap<>(MAXIMUM_MAP_SIZE, 1.0f);
  

Ответ №5:

 public class Cache {
    private LinkedHashMap<String, String> Cache = null;
    private final int cacheSize;  
    private ReadWriteLock readWriteLock=null;
    public Cache(LinkedHashMap<String, String> psCacheMap, int size) {
        this.Cache = psCacheMap;
        cacheSize = size;
        readWriteLock=new ReentrantReadWriteLock();
    }

    public void put(String sql, String pstmt) throws SQLException{
        if(Cache.size() >= cacheSize amp;amp; cacheSize > 0){
            String oldStmt=null;
            String oldSql = Cache.keySet().iterator().next();
            oldStmt = remove(oldSql);
            oldStmt.inCache(false);
            oldStmt.close();

        }
        Cache.put(sql, pstmt);
    }

    public String get(String sql){
        Lock readLock=readWriteLock.readLock();
        try{
            readLock.lock();
            return Cache.get(sql);
        }finally{
            readLock.unlock();
        }
    }

    public boolean containsKey(String sql){
        Lock readLock=readWriteLock.readLock();
        try{
            readLock.lock();
            return Cache.containsKey(sql);
        }finally{
            readLock.unlock();
        }
    }

    public String remove(String key){
        Lock writeLock=readWriteLock.writeLock();
        try{
            writeLock.lock();
            return Cache.remove(key);
        }finally{
            writeLock.unlock();
        }
    }

    public LinkedHashMap<String, String> getCache() {
        return Cache;
    }

    public void setCache(
            LinkedHashMap<String, String> Cache) {
        this.Cache = Cache;
    }


}
  

Ответ №6:

Метод put класса HashMap отвечает за добавление элементов в хэш-карту, и он делает это путем вызова метода с именем addEntry, код которого выглядит следующим образом:

    void addEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size   >= threshold)
            resize(2 * table.length);
    } 
  

Как вы можете видеть, в этом методе размер HashMap изменяется, если было превышено пороговое значение, поэтому я бы попробовал расширить класс HashMap и написать свои собственные методы для put и addEntry , чтобы удалить изменение размера. Что-то вроде:

 package java.util;

public class MyHashMap<K, V> extends HashMap {


    private V myPutForNullKey(V value) {
        for (Entry<K, V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount  ;
        myAddEntry(0, null, value, 0);
        return null;
    }

    public V myPut(K key, V value) {
        if (key == null)
            return myPutForNullKey(value);
        if (size < table.length) { 
            int hash = hash(key.hashCode());
            int i = indexFor(hash, table.length);
            for (Entry<K, V> e = table[i]; e != null; e = e.next) {
                Object k;
                if (e.hash == hash amp;amp; ((k = e.key) == key || key.equals(k))) {
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }

            modCount  ;
            myAddEntry(hash, key, value, i);
        }
        return null;
    }

    void myAddEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K, V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
        size  ;
    }
}
  

Вам нужно было бы написать свои собственные методы, поскольку put и addEntry не могут быть переопределяющими, и вам также нужно было бы сделать то же самое для putForNullKey , поскольку оно вызывается внутри put . Требуется проверка в put , чтобы убедиться, что мы не пытаемся поместить объект, если таблица заполнена.