Logo
Ana Sayfa
ÖZEL KODLAMA DERSLERİ
Yazılım Özel Ders
Tüm birebir programlara genel bakış
Python Yazılım Kursu
Sıfırdan ileri seviyeye birebir Python
Java Yazılım Kursu
OOP odaklı birebir Java eğitimi
AP Computer Science Principles
AP CSP sınav hazırlığı
GRUP DERSLERİ
Python & Django Masterclass
SINIRLI KONTENJAN
Java & Spring Boot Masterclass
SINIRLI KONTENJAN
C# .NET Masterclass
SINIRLI KONTENJAN
VİDEO DERSLER
Sıfırdan Temel Python Kursu
Kendi Hızında Öğren
ÜCRETSİZ
Seviye Testi
Ücretsiz — Python, Java, algoritma seviye testleri
Kariyerini Keşfet
Sertifika Doğrula
Belge numarası ve soyad ile doğrulama

Hash Map Nasıl Çalışır? Python Dict ve Java HashMap Karşılaştırması

Yazar: Berk Keskin 21.08.2026 ~13 dk okuma 4 Okunma
hash-map-nasil-calisir-python-dict-java-hashmap

Bir hash map, herhangi bir anahtarı sabit boyutlu bir sayısal değere dönüştüren bir hash fonksiyonu aracılığıyla veriyi neredeyse anında bulabilen bir veri yapısıdır. Bu sayısal değer, iç dizideki bir "bucket" (kova) indeksine eşlenir ve anahtar-değer çifti doğrudan o konumda saklanır. Python'daki dict ile Java'daki HashMap, bu mantığı iki farklı iç mimariyle uygular ama temel prensip aynıdır: anahtarı sayıya çevir, sayıyı konuma çevir, veriyi o konumda tut veya oradan oku.

Hash Fonksiyonu ve Bucket (Kova) Mantığı Nedir?

Hash fonksiyonu, boyutu ve türü ne olursa olsun bir anahtarı (string, sayı, nesne) sabit uzunlukta bir tam sayıya, yani hash koduna dönüştüren fonksiyondur. Bu dönüşüm tek yönlüdür; amaç şifreleme değil, hızlı ve tekrarlanabilir bir konum hesaplamaktır. Hash map, elemanlarını dizi benzeri bir yapıda, sabit sayıda bucket içinde tutar ve hash kodunu bu bucket sayısına göre bir indekse indirger.

Bu indirgeme genellikle mod işlemiyle yapılır: bucket_indeksi = hash_kodu % bucket_sayisi. Örneğin bucket sayısı 16 ise, hash kodu 1043 olan bir anahtar 1043 % 16 = 3 işlemiyle 3 numaralı bucket'a yerleştirilir. Java'da bu işlem doğrudan mod yerine genellikle bit maskeleme ile yapılır, ancak mantık aynıdır: geniş bir sayı uzayını, dizinin sınırlı boyutuna sıkıştırmak.

İyi bir hash fonksiyonunun üç temel özelliği olmalıdır. Birincisi determinizmdir: aynı anahtar, her çağrıldığında mutlaka aynı hash kodunu üretmelidir; aksi halde daha önce eklenen bir değer asla bulunamaz. İkincisi, hash kodlarının bucket'lara mümkün olduğunca düzgün dağılmasıdır; dağılım bozuksa bazı bucket'lar boş kalırken bazıları aşırı doluşur ve performans çöker. Üçüncüsü ise hesaplama hızıdır; hash fonksiyonu kendisi yavaşsa, hash map'in sunduğu hız avantajı anlamsızlaşır.

Akışı somut bir örnekle özetlemek gerekirse: "izmir" anahtarı hash fonksiyonundan geçer ve örneğin 8734521 gibi bir sayısal hash koduna dönüşür. Bu sayı, bucket sayısına göre mod alınarak, diyelim ki 5 numaralı bucket'a eşlenir. Değer artık o bucket'ta saklanır ve aynı anahtarla tekrar sorgulandığında aynı hash kodu, aynı bucket indeksi hesaplanacağı için veri doğrudan bulunur, dizinin tamamı taranmaz. Bu "anahtar → hash kodu → bucket indeksi → değer" zinciri, hash map'in hızının temel sebebidir ve bu mantığı algoritmik düşünme pratiğiyle pekiştirmek isteyenler algoritma bilgi seviyeni ölçen testi çözerek kendi kavrama düzeyini görebilir.

Çakışma (Collision) Nedir? Zincirleme ve Açık Adresleme

Bucket sayısı sınırlı, olası anahtar sayısı ise pratikte sonsuz olduğundan, iki farklı anahtarın aynı hash kodunu ya da mod işlemi sonrası aynı bucket indeksini üretmesi kaçınılmazdır. Bu duruma çakışma (collision) denir ve her hash map implementasyonunun bu senaryoyu yönetecek bir stratejisi olmak zorundadır. İki temel yaklaşım vardır: zincirleme ve açık adresleme.

Zincirleme (chaining) yönteminde her bucket, tek bir değer değil, bir liste ya da bağlı yapı tutar. Aynı indekse düşen tüm anahtar-değer çiftleri bu listeye eklenir. Bir anahtar arandığında önce doğru bucket bulunur, sonra bu küçük liste içinde anahtar eşleşmesi yapılana kadar gezilir. Java'nın HashMap sınıfı bu yöntemi kullanır ve belirli bir yoğunluktan sonra listeyi dengeli bir ağaca çevirerek en kötü durumu iyileştirir.

Açık adresleme (open addressing) yönteminde ise her bucket yalnızca tek bir eleman tutabilir. Hedeflenen bucket doluysa, önceden belirlenmiş bir kurala göre (probing) başka boş bir slot aranır. Linear probing bir sonraki slotu sırayla dener, quadratic probing ise adım büyüklüğünü kareler halinde artırarak ilerler. Python'un dict yapısı, bu yaklaşımın bir varyantını kullanır.

  • Bellek kullanımı: Zincirleme, her bucket için ek liste/düğüm yapısı gerektirdiğinden fazladan bellek tüketir; açık adresleme ise tüm veriyi tek bir dizi içinde tuttuğu için daha kompakttır.
  • Önbellek (cache) dostu olma: Açık adresleme, veriyi bitişik bellek bölgelerinde tuttuğundan CPU önbelleğini daha verimli kullanır; zincirlemede bağlı yapılar bellekte dağınık olabileceğinden önbellek isabet oranı düşebilir.
  • Silme işleminin zorluğu: Zincirlemede bir elemanı silmek listeden çıkarmak kadar basittir; açık adreslemede ise bir slotu doğrudan boşaltmak, probing zincirini kırıp başka bir elemanın bulunamaz hale gelmesine yol açabileceğinden özel "tombstone" (mezar taşı) işaretleyicileri gerekir.
  • Yoğunluk toleransı: Zincirleme, yüksek doluluk oranlarında bile makul performans sunabilirken, açık adresleme doluluk arttıkça probing sürelerinin hızla uzamasına daha duyarlıdır.

Hash Map Neden Ortalama O(1), En Kötü Durumda O(n)?

Bir hash map'in "ortalama durumda sabit zamanlı" olarak tanımlanmasının nedeni, iyi dağılmış bir hash fonksiyonuyla her bucket'ta yalnızca sabit sayıda (genellikle sıfır veya bir) eleman bulunmasıdır. Anahtar hesaplandığında doğrudan ilgili bucket'a gidilir ve o bucket'taki eleman sayısı sabit olduğu için arama, ekleme ve silme işlemleri veri büyüklüğünden bağımsız, yani ortalama O(1) sürede tamamlanır. Bu, dizide baştan sona arama yapmak zorunda kalan doğrusal yapılarla karşılaştırıldığında hash map'in asıl değerini oluşturan noktadır.

Bu sabit zamanlı davranışın sürdürülebilir olması, yük faktörü (load factor) adı verilen bir oranın kontrol altında tutulmasına bağlıdır. Yük faktörü, hash map içindeki eleman sayısının toplam bucket sayısına bölünmesiyle hesaplanır (eleman sayısı / bucket sayısı). Bu oran arttıkça, her bucket'a ortalama düşen eleman sayısı da artar ve çakışma olasılığı yükselir.

Yük faktörü, implementasyona bağlı belirli bir eşiği (örneğin 0.75 gibi yaygın bir değeri) aştığında, hash map otomatik olarak resize (yeniden boyutlandırma) işlemi tetikler: bucket dizisi büyütülür (genellikle iki katına çıkarılır) ve ardından mevcut tüm anahtarlar yeni bucket sayısına göre yeniden hesaplanarak dağıtılır. Bu ikinci adıma rehashing denir. Rehashing sırasında geçici olarak O(n) maliyetli bir işlem gerçekleşir, ancak bu maliyet çok sayıda ekleme işlemine yayıldığında ortalama maliyet yine sabit kalır; buna "amorti edilmiş O(1)" denir.

Buna karşılık gerçek en kötü durum O(n), hash fonksiyonunun kalitesinin bozulduğu ya da kötü niyetli/talihsiz bir girdi kümesiyle karşılaşıldığı senaryolarda ortaya çıkar. Eğer hash fonksiyonu düzgün dağılım sağlayamıyorsa ve tüm anahtarlar aynı bucket'a düşüyorsa, hash map fiilen tek bir uzun listeye dönüşür; bu durumda her arama, o listenin tamamının taranmasını gerektirir ve süre eleman sayısıyla doğrusal olarak artar. Bu nedenle mülakatlarda "hash map neden O(1) garanti etmez" sorusunun doğru cevabı, çakışma yoğunluğunun ve hash fonksiyonu kalitesinin doğrudan zaman karmaşıklığını belirlediğini göstermektir.

Python'da dict Yapısının İç İşleyişi

Python'da bir dict oluşturduğunuzda, her anahtar arka planda hash() fonksiyonundan geçirilir ve bu fonksiyonun ürettiği tam sayı değeri, anahtarın hangi slotta saklanacağını belirlemek için kullanılır. CPython yorumlayıcısı, bu hash değerini iç tablonun boyutuna göre bir indekse dönüştürür ve anahtar-değer çiftini o konuma yerleştirir. Aynı anahtarla tekrar sorgu yaptığınızda, Python yeniden hash hesaplayıp doğrudan ilgili slota gider; bu yüzden büyük veri kümelerinde bile arama işlemi son derece hızlıdır.

Çakışma durumunda CPython, zincirleme yerine açık adresleme tabanlı bir strateji izler. Yani iki farklı anahtar aynı slota düşerse, çakışan anahtar için sözde rastgele bir sıra izleyen "probing" mekanizmasıyla tablo içinde boş bir sonraki slot aranır. Bu tasarım, bellek içinde birbirine yakın verilerin daha hızlı erişilmesini sağlar ve CPython'ın dict performansını yüksek tutmasının temel nedenlerinden biridir.

Bu mekanizmanın çalışabilmesi için anahtarların hashlenebilir (hashable), yani değişmez (immutable) olması zorunludur. Bir liste gibi mutable bir nesne dict anahtarı olarak kullanılamaz çünkü içeriği değişirse hash değeri de değişir ve bu, tablo bütünlüğünü bozar. Buna karşılık bir tuple, içindeki tüm elemanlar da immutable olduğu sürece anahtar olarak rahatlıkla kullanılabilir. Bu ayrımı kavramak, hem günlük kodlamada hem de teknik mülakatlarda sıkça karşılaşılan bir konudur.

d = {"isim": "Ayşe", "yas": 28}
d["sehir"] = "İzmir"

print(hash("isim"))  # anahtarın hash değeri

# tuple anahtar olarak kullanılabilir
koordinat = {(35.2, 39.0): "İzmir merkez"}
print(koordinat[(35.2, 39.0)])

# liste hashlenemez, TypeError fırlatır
try:
    hatali = {[1, 2]: "olmaz"}
except TypeError as e:
    print("Hata:", e)

# var olmayan anahtara erişim KeyError verir
try:
    print(d["meslek"])
except KeyError as e:
    print("KeyError:", e)

Bu iç işleyişi anlamak, sadece teorik bir bilgi değil; doğru veri yapısı seçimi yapabilmenin de temelidir. Anahtar tasarımı, hash çakışmalarının etkisi ve immutable veri kullanımı gibi konuları uygulamalı örneklerle pekiştirmek isteyenler için birebir Python dersleri bu tür veri yapısı konularını sıfırdan ileri seviyeye taşıyacak şekilde işlenir.

Java'da HashMap Yapısının İç İşleyişi

Java'da HashMap, bir anahtar eklendiğinde önce o nesnenin hashCode() metodunu çağırır ve dönen değeri iç dizinin boyutuna göre işleyerek bir bucket indeksi hesaplar. Aynı bucket'a birden fazla anahtar düşerse, HashMap bu anahtarları birbirinden ayırmak için equals() metodunu kullanır. Yani hashCode indeksi bulur, equals ise o indekste doğru elemanı seçer.

Burada teknik açıdan kritik olan nokta şudur: hashCode() ve equals() sözleşmesi gereği, equals() metoduna göre birbirine eşit kabul edilen iki nesnenin mutlaka aynı hashCode() değerini döndürmesi gerekir. Bu kural ihlal edilirse — örneğin equals override edilir ama hashCode edilmezse — mantıksal olarak eşit iki nesne farklı bucket'lara düşebilir. Sonuç olarak HashMap aynı "anahtarı" farklı slotlarda tekrar tekrar saklayabilir, bu da anahtar kaybına veya beklenmeyen duplicate anahtarlara yol açar. Tersi durumda, yani farklı nesnelerin aynı hashCode'u paylaşması ise sorun değildir; bu normal bir çakışmadır ve equals() ile çözülür.

Bir bucket içindeki eleman sayısı arttıkça HashMap başlangıçta bunları bağlı liste (linked list) olarak zincirler. Ancak bir bucket'taki eleman sayısı belirli bir eşiği aştığında (yüksek çakışma durumunda), Java bu yapıyı performansı korumak amacıyla dengeli bir ağaç yapısına, yani red-black tree'ye dönüştürür. Bu dönüşüm, kötü tasarlanmış bir hashCode() nedeniyle çok sayıda eleman aynı bucket'a düştüğünde arama süresini doğrusal yerine logaritmik seviyede tutmayı amaçlar.

import java.util.HashMap;
import java.util.Objects;

class Ogrenci {
    String numara;
    Ogrenci(String numara) { this.numara = numara; }

    @Override
    public int hashCode() {
        return Objects.hash(numara);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Ogrenci)) return false;
        Ogrenci diger = (Ogrenci) o;
        return numara.equals(diger.numara);
    }
}

public class Main {
    public static void main(String[] args) {
        HashMap notlar = new HashMap<>();
        notlar.put(new Ogrenci("101"), "AA");

        // equals() ve hashCode() tutarlı olduğu için aynı anahtar kabul edilir
        System.out.println(notlar.get(new Ogrenci("101")));
    }
}

hashCode ve equals ilişkisini mülakatlarda net biçimde açıklayabilmek, veri yapıları bilginizin derinliğini gösterir. Bu tür sözleşme tabanlı konuları örnek üzerinden çalışmak isteyenler birebir Java dersleri ile kod yazarak ilerleyebilir.

Python dict ile Java HashMap: Temel Farklar

Python dict ve Java HashMap aynı temel fikri, yani hash tabanlı hızlı erişimi paylaşsa da, dil felsefeleri nedeniyle davranışları önemli noktalarda ayrışır. Aşağıdaki tablo, mülakatlarda en sık sorulan dört kritik farkı özetler.

Özellik Python dict Java HashMap
Tip esnekliği Farklı tipte anahtar ve değer aynı dict içinde serbestçe kullanılabilir Generic tip parametreleriyle anahtar ve değer tipi sabitlenir
Sıralama davranışı Ekleme sırasını korur ve iterasyonda bu sırayla döner Sıralama garantisi vermez, iterasyon sırası belirsiz kabul edilmelidir
Thread-safety Varsayılan olarak thread-safe değildir, senkronizasyon manuel yapılmalıdır Varsayılan olarak thread-safe değildir, alternatif olarak ConcurrentHashMap kullanılır
Anahtar kısıtları Anahtar hashable, yani immutable olmak zorundadır Anahtarın hashCode() ve equals() metotları tutarlı biçimde tanımlanmış olmalıdır

Bu farklar salt akademik detaylar değildir; günlük kod yazarken doğrudan hataya ya da performans sorununa dönüşebilir. Örneğin çok iş parçacıklı bir Java uygulamasında düz HashMap kullanmak yerine ConcurrentHashMap tercih etmemek, veri bozulmasına yol açabilir; Python tarafında ise çoklu thread'lerde dict üzerinde senkronizasyon olmadan yazma-okuma yapmak benzer riskler taşır. Sıralama davranışındaki fark da özellikle Java'dan Python'a veya tam tersi yönde kod taşıyan geliştiricilerin sıkça gözden kaçırdığı bir noktadır. Bu tür ince ayrımları pratik sorularla pekiştirmek isteyenler Java bilgi seviyesi ölçüm testi ile mevcut bilgisini hızlıca değerlendirebilir.

Hash Map Ne Zaman Tercih Edilmeli?

Hash map, anahtar üzerinden doğrudan erişim gerektiren ve sıralamanın önemli olmadığı hemen her senaryoda ilk akla gelmesi gereken veri yapısıdır. Bir kullanıcı ID'sine karşılık gelen profil bilgisini, bir ürün koduna karşılık gelen stok miktarını ya da bir kelimeye karşılık gelen tekrar sayısını saklamak istediğinizde, listede döngü kurup arama yapmak yerine anahtarı doğrudan hash map'e sormak çok daha verimlidir. Bu nedenle hash map, hızlı anahtar-değer erişimi gereken hemen her sistemin temel bileşenidir.

Pratikte hash map'in en çok tercih edildiği üç senaryo şöyle sıralanabilir:

  • Benzersiz anahtarlarla sayım ve gruplama işlemleri: bir metindeki kelime frekanslarını çıkarmak, bir listedeki tekrar eden elemanları tespit etmek veya kategori bazlı gruplama yapmak.
  • Önbellekleme (caching) senaryoları: bir hesaplamanın sonucunu tekrar tekrar üretmemek için anahtar-sonuç eşlemesini bellekte tutmak, özellikle maliyetli fonksiyon çağrılarında.
  • Hızlı varlık kontrolü: bir elemanın bir kümede olup olmadığını sabit sürede sorgulamak (örneğin ziyaret edilmiş düğümleri, işlenmiş kayıtları takip etmek).

Ancak hash map her zaman doğru seçim değildir. Anahtarların sıralı tutulması gerektiği durumlarda —örneğin bir sıralı liste üzerinde aralık sorgusu yapmak, en küçük veya en büyük anahtarı sürekli bulmak, ya da anahtarları belirli bir sırayla dolaşmak gerektiğinde— hash tabanlı yapılar yetersiz kalır, çünkü hash fonksiyonu anahtarlar arasında herhangi bir sıra ilişkisi korumaz. Bu noktada Java'daki TreeMap gibi ağaç tabanlı map yapıları devreye girer. TreeMap, verileri dengeli bir ikili arama ağacı mantığıyla saklar; her ekleme ve arama işlemi ağacın yüksekliğiyle orantılı olarak logaritmik sürede gerçekleşir ve anahtarlar her zaman sıralı biçimde dolaşılabilir. Yani hash map "en hızlı erişim" sorusuna, ağaç tabanlı map ise "sıralı ve öngörülebilir erişim" sorusuna cevap verir.

Teknik mülakatlarda bu tercih genellikle doğrudan bir soru olarak değil, bir senaryo üzerinden sorulur: "Bir sistemde en sık kullanılan öğeleri sıralı biçimde tutmanız gerekiyorsa hangi veri yapısını seçersiniz ve neden?" gibi bir soruda beklenen, hash map ile ağaç tabanlı map arasındaki performans-sıralama takasını net biçimde açıklayabilmektir. Bu tür kavramları soyut ezber yerine gerçek kod üzerinde pekiştirmek isteyenler için birebir Python ve Java dersleri kapsamında veri yapısı seçim kriterleri uygulamalı örneklerle çalışılabilir.

Sık Sorulan Sorular

Hash map içinde arama işlemi gerçekten her zaman O(1) midir?

Hayır, bu ortalama durum karmaşıklığıdır. Hash fonksiyonu anahtarları bucket'lara dengeli dağıtıyorsa arama, ekleme ve silme işlemleri pratikte sabit sürede gerçekleşir. Ancak çok sayıda anahtar aynı bucket'a düşerse (çakışma) ve zincirleme kullanılıyorsa arama süresi o bucket'taki eleman sayısıyla orantılı hale gelir; en kötü durumda bu O(n)'e kadar çıkabilir. Bu yüzden mülakatlarda "ortalama O(1), en kötü durumda O(n)" cevabı beklenir.

Python dict'te anahtarların eklenme sırası korunur mu?

Evet, modern Python sürümlerinde dict yapısı anahtarların eklenme sırasını korur ve döngüyle dolaşıldığında bu sıraya göre sonuç verir. Ancak bu davranış, dict'i sıralı bir veri yapısı yapmaz; anahtarlar değerlerine göre değil, ekleme zamanına göre sıralı tutulur. Anahtarları değerlerine göre sıralı tutmak gerekiyorsa ağaç tabanlı bir yapı veya ayrı bir sıralama adımı gerekir.

Java'da hashCode ve equals neden birlikte override edilir?

HashMap, bir anahtarı bucket'a yerleştirirken hashCode() metodunu, aynı bucket içinde doğru anahtarı bulurken de equals() metodunu kullanır. Yalnızca equals() override edilip hashCode() olduğu gibi bırakılırsa, mantıksal olarak eşit iki nesne farklı hash koduna sahip olabilir ve HashMap onları farklı bucket'lara yerleştirir; bu da aynı anahtarla ikinci kez arama yapıldığında nesnenin bulunamamasına yol açar. Bu yüzden iki metot her zaman birlikte ve tutarlı biçimde override edilmelidir.

HashMap veya Python dict thread-safe midir, çoklu iş parçacığında güvenle kullanılabilir mi?

Hayır, ikisi de varsayılan olarak thread-safe değildir. Birden fazla iş parçacığı aynı anda üzerinde değişiklik yaptığında veri tutarsızlığı veya beklenmeyen hatalar oluşabilir. Java'da bu ihtiyaç için ConcurrentHashMap gibi eşzamanlılığa uygun alternatifler tercih edilir; Python tarafında ise paylaşılan dict üzerinde kilit mekanizmaları kullanmak gerekir.

Load factor (yük faktörü) nedir ve HashMap performansını nasıl etkiler?

Load factor, HashMap'in dolu bucket oranının belirli bir eşiği aşması durumunda iç kapasitesini otomatik olarak büyütmesini tetikleyen orandır. Bu oran düşük tutulursa çakışma azalır ama bellek kullanımı artar; yüksek tutulursa bellek tasarrufu sağlanır ama çakışma olasılığı ve dolayısıyla arama süresi artar. Bu denge, hash map'in ortalama O(1) performansını koruyabilmesinin arkasındaki temel mekanizmadır.

Hangi durumlarda hash map yerine sıralı bir map yapısı tercih edilmelidir?

Anahtarların belirli bir sırayla dolaşılması, en küçük veya en büyük anahtarın sık sık sorgulanması ya da belirli bir aralıktaki anahtarların listelenmesi gerektiğinde ağaç tabanlı bir map yapısı tercih edilmelidir. Hash map bu tür sıralı sorgularda doğal bir avantaj sunmaz, çünkü hash fonksiyonu anahtarlar arasında sıra ilişkisi kurmaz.

Hash map'in mantığını hash fonksiyonundan çakışma çözümüne, Big O davranışından Python ve Java'daki iç işleyiş farklarına kadar bütün olarak kavramak, teknik mülakatlarda sıkça karşılaşılan veri yapısı sorularının büyük bölümüne hazırlıklı olmak demektir. Bu kavramları kod yazarak pekiştirmek isteyenler ücretsiz kodlama bilgisi testi ile mevcut seviyelerini ölçerek başlayabilir.

Bu içeriğin üretilmesinde yapay zeka araçlarından destek alınmıştır.

İlgili Eğitimler

Berk Keskin — Yazılım Geliştirici ve Eğitmen
Yazar

Berk Keskin Kimdir?

Yazılıma 12 yaşında başladı; bugün öğrencinin seviyesine ve hedefine göre şekillenen sürdürülebilir öğrenme sistemleri tasarlıyor. 300'den fazla kişiye ezber değil, düşünerek kod yazmayı öğretti — Berk Akademi'de izlemeye değil üretmeye dayalı öğrenme kültürünü o kuruyor.

WhatsApp Hemen Ara