Skip to main content

Command Palette

Search for a command to run...

Tabelas Hash

Updated
7 min read
Tabelas Hash
L

I am a Senior Software Architect with extensive experience in backend development, specializing in PHP frameworks such as Laravel and Hyperf. My expertise lies in software architecture and building scalable, high-performance applications with a strong focus on Developer Experience. I design and evolve modern architectures including well-structured monoliths, microservices, and event-driven systems, always striving for a balance between performance, simplicity, and cost. I have hands-on experience in high-scale environments with over 100k users, legacy modernization, cloud cost optimization, robust API design, event-driven architectures, technical debt management, and advocating best engineering practices. As a tech community enthusiast, I organize events at DevParaná and actively share knowledge to strengthen the local ecosystem.

As a dedicated professional, I thrive on collaboration and communication, fostering strong relationships with colleagues and clients alike. My adaptability allows me to navigate challenges with ease, while my problem-solving skills enable me to find innovative solutions in dynamic environments. I believe in the power of empathy and active listening, which helps me understand diverse perspectives and create inclusive spaces. My commitment to continuous learning drives me to seek growth opportunities, ensuring that I contribute effectively to any team. I am passionate about leveraging my emotional intelligence to inspire and motivate those around me.

  • Event-Driven Architecture
  • API Design
  • Microsserviços
  • Developer Experience (DX)
  • Clean Architecture / DDD / SOLID
  • Microservices Architecture
  • Docker
  • PHP (Laravel, Hyperf, Swoole)
  • Software Architecture
  • Cloud Computing
  • Node.js
  • Laravel (PHP)
  • Hyperf
  • Kubernetes
  • GraphQL
  • Swoole

Uma tabela hash é uma estrutura de dados que associa chaves de pesquisa a valores.

Sua principal função é, a partir de uma chave simples, fazer uma busca rápida e eficiente para encontrar o valor desejado.

1. Funções Hash

1.1. Introdução

A função hash é uma função que, a partir de uma entrada de dados, gera um valor numérico que identifica a posição de um elemento em uma tabela hash.

Nós já vimos algumas outras estruturas de dados aqui nesse perfil, como buscar um elemento em uma lista encadeada ou em uma árvore binária, e vimos que a complexidade dessas operações é O(n) e O(log n), respectivamente.

A tabela hash, por sua vez, consegue fazer essa busca em O(1), ou seja, em tempo constante.

Isso significa que, não importa o tamanho da tabela, a busca será feita no mesmo tempo.

1.2. Exemplo

Muitas vezes, a função hash é usada para criar um índice para um array, e é por isso que a função hash deve ser rápida e eficiente.

Desenvolvi uma função hash muito simples, que pega uma palavra e retorna um número que representa essa palavra.

package main

import (
"fmt"
)

func main() {
for {
run()
}
}

func run(){
array_palavras := [10000]string{}
fmt.Println("Digite uma palavra para ser hasheada")
var palavra string
fmt.Scanln(&palavra)
hash_palavra := fake_hash(palavra)
fmt.Println("A palavra", palavra, "tem o hash", hash_palavra)
array_palavras[hash_palavra] = palavra
fmt.Println("A palavra", palavra, "foi adicionada ao array na posicao", hash_palavra)
fmt.Println("Digite um hash para buscar a palavra correspondente")
var hash int
fmt.Scanln(&hash)
fmt.Println("A palavra correspondente ao hash", hash, "é", array_palavras[hash])
}

func fake_hash(palavra string) int {
hash := 0
for i := 0; i < len(palavra); i++ {
hash += int(palavra[i])
}
return hash
}

Nesse exemplo, a função fake_hash pega a palavra e soma os valores ASCII de cada caractere, retornando um número que representa a palavra.

Mas fique tranquilo, provavelmente você não vai precisar criar uma função hash, pois as linguagens de programação já possuem funções hash prontas e otimizadas.

Pode ser que na sua linguagem de programação a função hash seja chamada de outra coisa, como “map” ou “dicionário”, mas o conceito é o mesmo.

2. Usabilidade

2.1. Lista Telefônica

Um exemplo clássico de uso de tabela hash é a lista telefônica.

Imagine que você quer encontrar o número de telefone de uma pessoa.

Se a lista telefônica fosse uma lista encadeada, você teria que percorrer toda a lista até encontrar o nome da pessoa.

Se fosse uma árvore binária, você teria que percorrer a árvore até encontrar o nome da pessoa.

Mas, como a lista telefônica é uma tabela hash, você pode encontrar o número de telefone de uma pessoa em tempo constante, ou seja, em O(1).

Vamos entender mais sobre isso.

Ao adicionar um nome e um número de telefone à lista telefônica, a função hash é usada para gerar um índice para esse nome.

Quando você quer encontrar o número de telefone de uma pessoa, a função hash é usada para gerar o índice correspondente ao nome da pessoa, e o número de telefone é retornado.

Dessa forma:

package main

import (
"fmt"
)
func main() {
lista_telefonica := make(map[string]string)
lista_telefonica["João"] = "1234-5678"
lista_telefonica["Maria"] = "8765-4321"
lista_telefonica["José"] = "4321-5678"
lista_telefonica["Ana"] = "5678-4321"
fmt.Println("O número de telefone de João é", lista_telefonica["João"])
fmt.Println("O número de telefone de Maria é", lista_telefonica["Maria"])
fmt.Println("O número de telefone de José é", lista_telefonica["José"])
fmt.Println("O número de telefone de Ana é", lista_telefonica["Ana"])
}

Nesse exemplo, a função hash é usada para gerar um índice para cada nome, e o número de telefone é retornado em tempo constante.

2.2. DNS

Outro exemplo clássico de uso de tabela hash é o DNS (Domain Name System).

O DNS é um sistema que traduz nomes de domínio da Internet em endereços IP.

Imagine que você quer acessar um site, como www.google.com.

Se o DNS não fosse uma tabela hash, você teria que percorrer toda a lista de domínios até encontrar o nome do site, imagina o tempo que isso levaria.

Mas, como o DNS é uma tabela hash, você pode encontrar o endereço IP de um site em tempo constante, ou seja, em O(1).

Vamos entender mais sobre isso com um exemplo.

package main

import (
"fmt"
)
func main() {
dns := make(map[string]string)
dns["www.google.com"] = "192.168.5.5"
dns["www.facebook.com"] = "192.168.40.21"
dns["www.twitter.com"] = "192.168.11.11"
fmt.Println("O endereço IP de www.google.com é", dns["www.google.com"])
fmt.Println("O endereço IP de www.facebook.com é", dns["www.facebook.com"])
fmt.Println("O endereço IP de www.twitter.com é", dns["www.twitter.com"])
}

Entende como são exemplos bem similares? A ideia é a mesma, a tabela hash é usada para gerar um índice para cada nome, e o endereço IP é retornado em tempo constante.

3. Colisões

3.1. Introdução

Uma colisão ocorre quando duas chaves de pesquisa diferentes têm o mesmo valor hash.

Mas isso é possível? Como a função hash gera um valor único para cada chave de pesquisa?

Bom, a função hash não gera um valor único para cada chave de pesquisa, ela gera um valor que representa a chave de pesquisa.

E, como o número de chaves de pesquisa é muito maior do que o número de valores hash, é possível que duas chaves de pesquisa diferentes tenham o mesmo valor hash.

3.2. Tratamento de Colisões

Existem várias maneiras de tratar colisões, mas as duas mais comuns são:

  • Encadeamento
  • Endereçamento Aberto

3.2.1. Encadeamento

No encadeamento, cada entrada da tabela hash é uma lista encadeada.

Quando ocorre uma colisão, a chave de pesquisa é adicionada à lista encadeada correspondente.

Isso significa que, se duas chaves de pesquisa diferentes têm o mesmo valor hash, elas são adicionadas à mesma lista encadeada.

A principal vantagem do encadeamento é que ele é simples de implementar, mas a principal desvantagem é que ele pode ser ineficiente em termos de espaço e desempenho.

3.2.2. Endereçamento Aberto

No endereçamento aberto, quando ocorre uma colisão, a chave de pesquisa é adicionada a outra posição da tabela hash.

Isso significa que, se duas chaves de pesquisa diferentes têm o mesmo valor hash, a segunda chave de pesquisa é adicionada a outra posição da tabela hash.

A principal vantagem do endereçamento aberto é que ele é eficiente em termos de espaço e desempenho, mas a principal desvantagem é que ele é mais complexo de implementar.

4. Desempenho

4.1. Fator de Carga

O fator de carga é a razão entre o número de chaves de pesquisa e o número de posições da tabela hash.

Quanto maior o fator de carga, maior a probabilidade de colisões.

Por isso, é importante manter o fator de carga baixo, para garantir um bom desempenho da tabela hash.

Um fator de carga ideal é menor que 0,7, ou seja, menos de 70% das posições da tabela hash estão ocupadas.

4.1.1. Como Calcular o Fator de Carga

O fator de carga é calculado da seguinte forma:

NK = número de chaves de pesquisa
NP = número de posições da tabela hash

NK / NP = fator de carga

Por exemplo, se a tabela hash tem 100 posições e 70 chaves de pesquisa, o fator de carga é 0,7.

70 / 100 = 0,7

4.2. Redimensionamento

Quando o fator de carga é maior que 0,7, é necessário redimensionar a tabela hash.

O redimensionamento da tabela hash é feito da seguinte forma:

  • Crie uma nova tabela hash com o dobro do tamanho da tabela hash original
  • Adicione todas as chaves de pesquisa da tabela hash original à nova tabela hash
  • Descarte a tabela hash original

O redimensionamento da tabela hash é uma operação custosa, mas é necessária para garantir um bom desempenho da tabela hash.

5. SHA

SHA (Secure Hash Algorithm) é uma família de funções hash criptográficas.

Ela é uma ótima função hash, pois gera um valor único para cada chave de pesquisa.

SHA é amplamente utilizada em criptografia, segurança da informação e autenticação.

SHA é uma função hash muito segura, e é praticamente impossível encontrar duas chaves de pesquisa diferentes com o mesmo valor hash.

Uma das principais vantagens de SHA é que ela é rápida, eficiente e unidirecional, ou seja, é fácil calcular o valor hash de uma chave de pesquisa, mas é praticamente impossível calcular a chave de pesquisa a partir do valor hash.

Segue um exemplo de como usar SHA em Go:

package main
import (
"crypto/sha256"
"fmt"
)

func main() {
palavra := "hello"
hash := sha256.Sum256([]byte(palavra))
fmt.Printf("O hash de %s é %x\n", palavra, hash)
}

Nesse exemplo, a função hash SHA-256 é usada para gerar um valor hash para a palavra “hello”.

Mas se você quiser usar o SHA-256 para gerar a palavra “hello” a partir do valor hash, você não vai conseguir.

6. Conclusão

As tabelas hash são estruturas de dados muito eficientes para fazer buscas rápidas e eficientes.

Elas são amplamente utilizadas em aplicações do mundo real, como listas telefônicas, DNS, criptografia e segurança da informação.

Você aprendeu sobre funções hash, usabilidade, colisões, desempenho e SHA.

Nunca implemente uma função hash e use em produção, sempre use funções hash prontas e otimizadas da sua linguagem de programação.

Espero que você tenha aprendido bastante sobre tabelas hash, e que você possa aplicar esse conhecimento em suas aplicações do mundo real.

Se você tiver alguma dúvida ou sugestão, deixe nos comentários.

Até a próxima!

More from this blog

L

Luiz Schons

28 posts