HashTable – Java data structures

Java HashTable is a data structure that allows you to safely work with data even in multi-threaded applications. Although it is an older but still reliable implementation, its use has specific characteristics. In the following lines, we’ll look at how the HashTable class works, what its advantages and limitations are, and also when it makes sense to use it instead of more modern alternatives such as HashMap.

HashTable - Java data structures
HashTable - Java data structures

The HashTable class is one of the older Java implementations of hash tables.

HashTable collection - inheritance hierarchy (source: media.geeksforgeeks.org/wp-content/uploads/20201124183400/HierarchyofHashtable.png)

HashTable collection – inheritance hierarchy (source: media.geeksforgeeks.org/wp-content/uploads/20201124183400/HierarchyofHashtable.png)

HashTable in Java – data structure introduction

The HashTable class is an implementation of the Map interface that stores key-value pairs and uses a hash table for fast operations. Each key must be unique and can never be null, nor can the value, so null values are not allowed in HashTable.

Unlike more modern collections like HashMap, HashTable is synchronized, which means it is safe for use in multithreaded applications, but at the same time its operations can be slower.

6 min.Laptop with on-screen programming code

HashMap – Java data structures

Learn about the HashMap data structure in Java - an overview of methods, operations, advantages, disadvantages and practical examples of its use.

HashTable uses the hashCode() method of objects to evenly divide pairs into internal “buckets”. This allows to perform adding, searching and deleting operations in constant time (O(1)) with an ideal layout.

HashTable – constructors

To create a HashTable instance, we have several constructors available:

1. Empty HashTable

Creates an empty hash table with default capacity (11) and load factor (0.75).

Hashtable<Integer, String> table = new Hashtable<>();

2.HashTable with defined capacity

Creates a new, empty hash table with the specified initial capacity and default load factor (0.75).

Hashtable<Integer, String> table = new Hashtable<>(20);

3. HashTable with defined capacity and load factor

Creates a new, empty hash table with the specified initial capacity and the specified load factor.

Hashtable<Integer, String> table = new Hashtable<>(20, 0.82f);

4. HashTable constructed from Map

Creates a new hash table with the same mapping as the given Map.

Hashtable(Map<? extends K,? extends V> t)

Initial capacity, Load Factor

The Hashtable instance has two parameters that affect its performance: initial capacity and load factor. The capacity is the number of segments in the hashtable, and the initial capacity is simply the capacity at the time the hashtable is created. In the case of the same hash node (called a hash collision), multiple records are stored in a single segment and must be searched sequentially. The load factor indicates how much the hash table can fill up before its capacity is automatically increased.

In general, the default load factor (0.75) offers a good compromise between time and space costs. Higher values reduce the space overhead, but increase the time cost of retrieving a record (which is reflected in most hashtable operations, including get and put). Initial capacity manages the tradeoff between wasted space and the need for repeated operations that are time-consuming.

If the initial capacity is greater than the maximum number of records the hash table will contain divided by its load factor, no rehash operations will ever occur. However, setting the initial capacity too high can waste space. Therefore, when constructing a HashTable, we recommend making the best estimate of how many elements we will store in the collection and choosing the correct initial capacity (and possibly load factor).

If many records are to be created in a hashtable, creating it with a large enough capacity may allow records to be inserted more efficiently than having it perform automatic rehashing as needed to increase the capacity of the table.

Hash Function

A well-designed hash function for objects (hashCode) is extremely important because it is what evenly distributes elements into the correct buckets, reducing the likelihood of collisions and maintaining the efficiency and performance of the collection.

When inserting an element into the collection, its hash value is calculated and the segment where the element should be stored is determined. Of course, this is not enough because two objects with the same hashCode may not be the same, so within the same bucket, objects are compared using the equals() method to ensure the uniqueness of the inserted elements.

HashTable – basic operations

Some of the basic operations we can perform with HashTable include:

  • Adding pairs:
    The put(key, value) method puts a new pair into the table. If a new pair with a matching key is inserted, the old value is overwritten.
  • Removing Pairs:
    The remove(key) method removes the pair with the given key.
  • Emptiness test:
    The isEmpty() method tells us if the HashTable contains any pair at all.
  • Presence test:
    The containsKey(key), containsValue(value) methods detect whether a given key or value is present in the table.
  • Finding the size:
    The size() method returns the number of pairs in the table.
  • Clearing the table:
    The clear() method removes all pairs from the table.
  • Iteration:
    Using the keySet(), values() or entrySet() methods we can iterate over keys, values or integer pairs.

HashTable – documentation

For a complete overview of the methods of the HashTable class, see official documentation.

HashTable – advantages and disadvantages

Benefits of HashTable

  • The
    HashTable synchronization is synchronized, making it safe for use in multi-threaded applications without additional synchronization.
  • Fast Operations:
    Ideally, it provides constant-time (O(1)) add, search, and delete operations.
  • Ease of use: The
    API is clear and intuitive, making it easy to work with mapping data structures.

Disadvantages of HashTable

  • Deprecation:
    HashTable is considered an older implementation. Modern collections like HashMap offer better performance and more options.
  • No null values:
    Unlike some collections, HashTable does not allow null keys or null values, which can be limiting.
  • Sensitivity to the implementation of hashCode and equals
    If the class we store as an element does not have the hashCode() and equals() methods implemented correctly, this can lead to unexpected behavior and performance degradation.
  • Performance in case of collisions:
    When there are a large number of collisions (when multiple elements have the same hash code), performance may be degraded because operations may behave more slowly.

HashTable – when to use it and when not?

HashTable is suitable in the following cases:

  • We need a synchronized map without the need for explicit synchronization.
  • There is no need to store null values.

On the other hand, if you don’t need synchronization or want a similar collection with higher performance, it is recommended to use HashMap.

Example of using HashTable in Java

HashTable excels at efficiently storing key-value pairs and retrieving them quickly thanks to a hashing method when we need to retrieve and use them. Therefore, hash tables have versatile uses.

Now we will demonstrate this in a program in which we first create a HashTable using the constructor and store in it the states adjacent to Slovakia. After that, we’ll show the use of some basic methods for working with the HashTable.

Main.java

import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        // Hashtable creation
        Hashtable<String, String> skNeighbors = new Hashtable<>();

        // Adding country codes and country names
        skNeighbors.put("SK", "Slovakia");
        skNeighbors.put("CZ", "Czech Republic");
        skNeighbors.put("AT", "Austria");
        skNeighbors.put("HU", "Hungary");
        skNeighbors.put("UA", "Ukraine");
        skNeighbors.put("PL", "Poland");

        // Display all country codes and names using Enumeration
        System.out.println("Neighboring states of Slovakia:");
        Enumeration<String> keys = skNeighbors.keys();
        while (keys.hasMoreElements()) {
            String code = keys.nextElement();
            if(!code.equals("SK"))
                System.out.println(code + " - " + skNeighbors.get(code));
        }

        // Lookup a country by its code
        String searchCode = "DE";
        if (skNeighbors.containsKey(searchCode)) {
            System.out.println("\n✅ Country found for code " + searchCode + ": " + skNeighbors.get(searchCode));
        } else {
            System.out.println("\n❌ No country found for code " + searchCode);
        }

        // Display the total number of countries in the directory
        System.out.println("\nTotal number of countries in the Hashtable (before remove): " + skNeighbors.size());

        // Try to remove a country by its code which is not present
        String removeCode = "DE";
        String removedCountry = skNeighbors.remove(removeCode);
        System.out.println("Removed country: " + removeCode + " - " + removedCountry);

        // Try to remove a country by its code
        removeCode = "SK";
        removedCountry = skNeighbors.remove(removeCode);
        System.out.println("Removed country: " + removeCode + " -> " + removedCountry);

        // Check if a specific country exists in the table
        String searchCountry = "Germany";
        if (skNeighbors.containsValue(searchCountry)) {
            System.out.println("✅ " + searchCountry + " is in the directory.");
        } else {
            System.out.println("❌ " + searchCountry + " is NOT in the directory.");
        }

        // Display the total number of countries in the directory
        System.out.println("\nTotal number of countries in the Hashtable (after remove): " + skNeighbors.size());

        // Clear all country entries
        skNeighbors.clear();
        System.out.println("Country directory cleared. Is it empty? " + skNeighbors.isEmpty());

        // Display all remaining countries using keySet()
        System.out.println("Remaining countries after clearing:");
        Set<String> keySet = skNeighbors.keySet();
        for (String code : keySet) {
            System.out.println(code + " - " + skNeighbors.get(code));
        }
    }
}

The output of this example is:

HashTable - output from this example

We have prepared the files with the above example in the form of code that you can run directly in Java. Download the Java code for HashTable here.

About the author

Jozef Wagner

Java Developer Senior

I have been programming in Java for more than 10 years, currently I am working in msg life Slovakia as a senior Java programmer and I help customers to implement their requirements into Life Factory insurance software. In my free time I like to relax in the mountains or play a good computer game.

Let us know about you