Линейно изследване на реализация на Java HashTable

Така че тук имам реализация на HashTable, която написах, използвайки само масиви и имах малко помощ с кода. За съжаление, не разбирам напълно един от редовете, които някой е добавил, докато изпълнява метода "get" или "put". Какво точно се случва в цикъла while по-долу? Дали това е метод за линейно сондиране? Освен това защо цикълът проверява условията, които проверява?

по-конкретно,

int hash = hashThis(key);

    while(data[hash] != AVAILABLE && data[hash].key() != key) {

        hash = (hash + 1) % capacity;
    }

Ето целия Java клас по-долу за пълна справка.

public class Hashtable2 {

private Node[] data;
private int capacity;
private static final Node AVAILABLE = new Node("Available", null);

public Hashtable2(int capacity) {

    this.capacity = capacity; 
    data = new Node[capacity];
    for(int i = 0; i < data.length; i++) {

        data[i] = AVAILABLE;
    }
}

public int hashThis(String key) {

    return key.hashCode() % capacity; 
}

public Object get(String key) {

    int hash = hashThis(key);

    while(data[hash] != AVAILABLE && data[hash].key() != key) {

        hash = (hash + 1) % capacity;
    }
    return data[hash].element();
}

public void put(String key, Object element) {

    if(key != null) {
        int hash = hashThis(key);
        while(data[hash] != AVAILABLE && data[hash].key() != key) {

            hash = (hash + 1) % capacity;
        }

        data[hash] = new Node(key, element);

    }

}



public String toString(){

    String s="<";

    for (int i=0;i<this.capacity;i++)
    {
        s+=data[i]+", ";    

    }

    s+=">";

    return s;
    }

Благодаря ти.


person user1493543    schedule 11.02.2013    source източник


Отговори (2)


Току-що пренаписах част от кода и добавих метода findHash - опитайте се да избегнете дублиране на код!

private int findHash(String key) {
    int hash = hashThis(key);

    // search for the next available element or for the next matching key
    while(data[hash] != AVAILABLE && data[hash].key() != key) {

        hash = (hash + 1) % capacity;
    }
    return hash;
}

public Object get(String key) {

    return data[findHash(key)].element();
}

public void put(String key, Object element) {

    data[findHash(key)] = new Node(key, element); 
}

Това, което попитахте, е - какво точно означава този findHash-цикъл? Данните бяха инициализирани с AVAILABLE - което означава: данните (все още) не съдържат никакви действителни данни. Сега - когато добавим елемент с put - първо се изчислява hashValue, което е просто индекс в масива data, където да поставим данните. Сега - ако срещнем, че позицията вече е заета от друг елемент със същата хеш стойност, но различен ключ, ние се опитваме да намерим следващата AVAILABLE позиция. И методът get по същество работи по същия начин - ако бъде открит елемент от данни с различен ключ, следващият елемент се изследва и така нататък. Самият data е така нареченият ring-buffer. Тоест, търси се до края на масива и следващото търсене е отново в началото, започвайки с индекс 0. Това се прави с оператора по модул %.

Добре?

person michael_s    schedule 11.02.2013
comment
P.S. В изпълнението липсват някои важни методи: направете хеша по-голям, когато е пълен. В момента ще имате безкраен цикъл между другото, когато поставите елемент към вече пълен хеш. Също така не е възможно премахване. Опитайте се да напишете някои единици тестове за това. - person michael_s; 11.02.2013
comment
Идеално благодаря много! Да, определено ще помисля за добавянето им, просто навлизам в тези неща, но се вълнувам да науча! - person user1493543; 11.02.2013

Примерно внедряване на Hashtable с използване на Generics и Linear Probing за разрешаване на сблъсъци. Има някои предположения, направени по време на изпълнението и те са документирани в javadoc над класа и методите.

Тази реализация няма всички методи на Hashtable като keySet, putAll и т.н., но обхваща най-често използваните методи като get, put, remove, size и т.н.

Има повторение на кода в get, put и remove за намиране на индекса и може да се подобри, за да има нов метод за намиране на индекс.

class HashEntry<K, V> {
    private K key;
    private V value;
    public HashEntry(K key, V value) {
        this.key = key;
        this.value = value;
    }
    public void setKey(K key) { this.key = key; }
    public K getKey() { return this.key; }

    public void setValue(V value) { this.value = value; }
    public V getValue() { return this.value; }
}

/**
 * Hashtable implementation ...
 *   - with linear probing
 *   - without loadfactor & without rehash implementation.
 *   - throws exception when table is full
 *   - returns null when trying to remove non existent key
 *
 * @param <K>
 * @param <V>
 */
public class Hashtable<K, V> {

    private final static int DEFAULT_CAPACITY = 16;

    private int count;
    private int capacity;
    private HashEntry<K, V>[] table;

    public Hashtable() {
        this(DEFAULT_CAPACITY);
    }

    public Hashtable(int capacity) {
        super();
        this.capacity = capacity;
        table = new HashEntry[capacity];
    }

    public boolean isEmpty() { return (count == 0); }

    public int size() { return count; }

    public void clear() { table = new HashEntry[this.capacity]; count = 0; }

    /**
     * Returns null if either probe count is higher than capacity else couldn't find the element.
     * 
     * @param key
     * @return
     */
    public V get(K key) {
        V value = null;
        int probeCount = 0;
        int hash = this.hashCode(key);
        while (table[hash] != null && !table[hash].getKey().equals(key) && probeCount <= this.capacity) {
            hash = (hash + 1) % this.capacity;
            probeCount++;
        }
        if (table[hash] != null && probeCount <= this.capacity) {
            value = table[hash].getValue();
        }
        return value; 
    }

    /**
     * Check on the no of probes done and terminate if probe count reaches to its capacity.
     * 
     * Throw Exception if table is full.
     * 
     * @param key
     * @param value
     * @return
     * @throws Exception
     */
    public V put(K key, V value) throws Exception {
        int probeCount = 0;
        int hash = this.hashCode(key);
        while (table[hash] != null && !table[hash].getKey().equals(key) && probeCount <= this.capacity) {
            hash = (hash + 1) % this.capacity;
            probeCount++;
        }
        if (probeCount <= this.capacity) {
            if (table[hash] != null) {
                table[hash].setValue(value);                
            } else {
                table[hash] = new HashEntry(key, value);
                count++;
            }
            return table[hash].getValue();
        } else {
            throw new Exception("Table Full!!");
        }
    }

    /**
     * If key present then mark table[hash] = null & return value, else return null.  
     * 
     * @param key
     * @return
     */
    public V remove(K key) {
        V value = null;
        int probeCount = 0;
        int hash = this.hashCode(key);
        while (table[hash] != null && !table[hash].getKey().equals(key) && probeCount <= this.capacity) {
            hash = (hash + 1) % this.capacity;
            probeCount++;
        }
        if (table[hash] != null && probeCount <= this.capacity) {
            value = table[hash].getValue(); 
            table[hash] = null;
            count--;
        }
        return value;
    }

    public boolean contains(Object value) {
        return this.containsValue(value);
    }

    public boolean containsKey(Object key) {
        for (HashEntry<K, V> entry : table) {
            if (entry != null && entry.getKey().equals(key)) {
                return true;
            }
        }
        return false;
    }

    public boolean containsValue(Object value) {
        for (HashEntry<K, V> entry : table) {
            if (entry != null && entry.getValue().equals(value)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
        StringBuilder data = new StringBuilder();
        data.append("{");
        for (HashEntry<K, V> entry : table) {
            if (entry != null) {
                data.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
            }
        }
        if (data.toString().endsWith(", ")) {
            data.delete(data.length() - 2, data.length());
        }
        data.append("}");
        return data.toString();
    }

    private int hashCode(K key) { return (key.hashCode() % this.capacity); }

    public static void main(String[] args) throws Exception {
        Hashtable<Integer, String> table = new Hashtable<Integer, String>(2);
        table.put(1, "1");
        table.put(2, "2");
        System.out.println(table);
        table.put(1, "3");
        table.put(2, "4");
        System.out.println(table);
        table.remove(1);
        System.out.println(table);
        table.put(1, "1");
        System.out.println(table);
        System.out.println(table.get(1));
        System.out.println(table.get(3));
        // table is full so below line
        // will throw an exception
        table.put(3, "2");
    }
}

Примерно изпълнение на горния код.

{2=2, 1=1}
{2=4, 1=3}
{2=4}
{2=4, 1=1}
1
null
Exception in thread "main" java.lang.Exception: Table Full!!
    at Hashtable.put(Hashtable.java:95)
    at Hashtable.main(Hashtable.java:177)
person JRG    schedule 28.06.2017