Estas classes são muito populares. Isso porque elas têm grande utilidade para o desenvolvedor. Infelizmente elas também são horrivelmente ineficientes. Hashtable e HashMap embalam cada par chave/valor em um objeto Entry. Uma instância de Entry é surpreendentemente grande. Ela não somente segura uma referência para a chave e valor mas também armazena o hash e uma referência para o próximo Entry do hash bucket*. Se você olhar o heap dump com um analisador de memória, ficará chocado com o quanto de espaço é gasto por eles em aplicações grandes. Se você olhar o código-fonte da classe HashSet, você verá que os desenvolvedores foram extremamente preguiçosos e apenas usaram um HashMap por trás dos panos! Antes de usar qualquer uma dessas classes, pense novamente. IdentityHashMap pode ser uma alternativa viável. Mas tenha cuidado: ele intencionalmente quebra a interface Map. Ele é muito mais eficiente no uso da memória devido à implementação de uma hashtable aberta (sem buckets), sem necessidade de um objeto Entry e usa um simples Object[] na retaguarda. Em vez de um HashSet, um simples ArrayList pode funcionar similarmente bem (você pode usar o método contains(Object)) desde que ele seja pequeno e buscas sejam raras. Para Sets, use apenas um ArrayList ou mesmo um array. Realmente é uma vergonha que não existam implementações eficientes de Map e Set na Standard JDK!

*Nota do Tradutor: bucket é o local onde são armazenados os itens que possuem o mesmo hash (em caso de colisão).