Bir Java dizesindeki hashCode () tutarlılığı


134

Bir Java Dizesinin hashCode değeri ( String.hashCode () ) olarak hesaplanır :

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

Aşağıdaki ifadenin yanlış olarak değerlendirileceği herhangi bir koşul (örneğin JVM sürümü, satıcı vb.) Var mı?

boolean expression = "This is a Java string".hashCode() == 586653468

1. Güncelleme: Cevabın "evet, böyle durumlar var" olduğunu iddia ediyorsanız - o zaman lütfen "Bu bir Java dizesidir" .hashCode ()! = 586653468 ile ilgili somut bir örnek verin. Spesifik / somut olmaya çalışın olabildiğince.

2. Güncelleme: Hepimiz hashCode () 'un uygulama detaylarına güvenmenin genel olarak kötü olduğunu biliyoruz. Bununla birlikte, özellikle String.hashCode () hakkında konuşuyorum - bu yüzden lütfen cevabı String.hashCode () 'a odaklayın. Object.hashCode () bu soru bağlamında tamamen alakasızdır.


2
Bu işleve gerçekten ihtiyacınız var mı? Neden kesin değere ihtiyacınız var?
Brian Agnew

26
@Brian: String.hashCode () sözleşmesini anlamaya çalışıyorum.
knorv

3
@Knorv Tam olarak nasıl çalıştığını anlamak gerekli değildir - sözleşmeyi ve onun gizli anlamını anlamak daha önemlidir.
mP.

45
@mP: Girişiniz için teşekkürler, ama sanırım buna karar vermek bana kalmış.
knorv

neden ilk karaktere en büyük gücü verdiler? Ekstra hesaplamaları korumak için hızı optimize etmek istediğinizde, bir öncekinin gücünü depolarsınız, ancak bir önceki karakter son karakterden birinciye kadar olur. bu, önbellekte eksiklikler olacağı anlamına gelir. bir algoritmaya sahip olmak daha verimli değil mi: s [0] + s [1] * 31 + s [2] * 31 ^ 2 + ... + s [n-1] * 31 ^ [n-1 ]?
android geliştiricisi

Yanıtlar:


101

Bu belgeleri Java 1.2'ye kadar görebiliyorum.

Genel olarak aynı kalan bir karma kod uygulamasına güvenmemeniz gerektiği doğru olsa da , artık bunun için davranış olarak belgelenmiştir java.lang.String, bu nedenle onu değiştirmek mevcut sözleşmeleri bozmak olarak sayılır.

Mümkün olan yerlerde, sen sürümleri vb genelinde aynı kalıyor hash kodları güvenmemelisiniz - ama aklımda java.lang.Stringalgoritma çünkü özel bir durumudur etti belirtilmiş ... bu kadar uzun gibi doğru önce sürümleriyle uyumluluk terk etmeye istekli elbette algoritma belirtildi.


7
String'in belgelenmiş davranışı, API'nin Java 1.2 v1.1 sürümünden beri belirtilmiştir, karma kod hesaplaması String sınıfı için belirtilmemiştir.
Martin OConnor

Bu durumda kendi hashing kodlarımızı yazsak daha iyi olur mu?
Felype

@Felype: Korkarım burada ne söylemeye çalıştığını gerçekten bilmiyorum.
Jon Skeet

@JonSkeet Demek istediğim, bu durumda taşınabilirlik sağlamak için belki de kendi hash'imizi oluşturmak için kendi kodumuzu yazabiliriz. Bu mu?
Felype

@Felype: Ne tür bir taşınabilirlikten bahsettiğiniz hiç net değil, ne de "bu durumda" ile ne demek istediğiniz - hangi özel senaryoda? Yeni bir soru sorman gerektiğini düşünüyorum.
Jon Skeet

18

JDK 1.0 ve 1.1 ve> = 1.2 ile ilgili bir şey buldum:

JDK 1.0.x ve 1.1.x sürümlerinde, uzun Dizeler için hashCode işlevi her n'inci karakteri örnekleyerek çalıştı. Bu, aynı değere sahip birçok Dizeye sahip olacağınızı ve böylece Hashtable aramasını yavaşlatacağınızı garanti eder. JDK 1.2'de fonksiyon, şimdiye kadar sonucu 31 ile çarpacak ve ardından sırayla bir sonraki karakteri ekleyecek şekilde geliştirilmiştir. Bu biraz daha yavaştır, ancak çarpışmalardan kaçınmada çok daha iyidir. Kaynak: http://mindprod.com/jgloss/hashcode.html

Farklı bir şey, çünkü bir sayıya ihtiyacınız var gibi görünüyor: Karma kod yerine CRC32 veya MD5 kullanmaya ne dersiniz ve gitmekte fayda var - tartışma yok ve hiç endişe yok ...


8

Bir hash kodunun belirli bir değere eşit olmasına güvenmemelisiniz. Sadece aynı uygulama içinde tutarlı sonuçlar verecek. API belgeleri şunları söylüyor:

HashCode'un genel sözleşmesi:

  • Bir Java uygulamasının yürütülmesi sırasında aynı nesne üzerinde birden çok kez çağrıldığında, nesnede eşittir karşılaştırmalarında kullanılan hiçbir bilgi değiştirilmediği sürece, hashCode yöntemi tutarlı bir şekilde aynı tamsayıyı döndürmelidir. Bu tam sayının, bir uygulamanın bir yürütmesinden aynı uygulamanın başka bir yürütmesine kadar tutarlı kalması gerekmez.

DÜZENLE String.hashCode () için javadoc, bir String'in karma kodunun nasıl hesaplandığını belirttiğinden, bunun herhangi bir ihlali genel API spesifikasyonunu ihlal eder.


1
Cevabınız geçerlidir, ancak sorulan belirli soruyu ele almamaktadır.
knorv

6
Bu, genel karma kod sözleşmesidir - ancak String için özel sözleşme, algoritmanın ayrıntılarını verir ve bu genel sözleşme IMO'yu etkili bir şekilde geçersiz kılar.
Jon Skeet

4

Yukarıda belirtildiği gibi, genel olarak aynı kalan bir sınıfın hash koduna güvenmemelisiniz. Aynı uygulamanın aynı VM üzerinde daha sonra çalıştırılmasının bile farklı hash değerleri üretebileceğini unutmayın. AFAIK, Sun JVM'nin karma işlevi her çalıştırmada aynı karmayı hesaplar, ancak bu garanti edilmez.

Bunun teorik olmadığını unutmayın. Java.lang.String için karma işlevi , JDK1.2'de değiştirildi (eski karma, URL'ler veya dosya adları gibi hiyerarşik dizelerle ilgili sorunlar yaşadı, çünkü dizeler için yalnızca sonunda farklı olan aynı karmayı üretme eğilimindeydi).

java.lang.String özel bir durumdur, çünkü hashCode () algoritması (şimdi) belgelenmiştir, bu yüzden muhtemelen buna güvenebilirsiniz. Yine de kötü bir uygulama olduğunu düşünüyorum. Özel, belgelenmiş özelliklere sahip bir karma algoritmaya ihtiyacınız varsa, sadece bir tane :-) yazın.


4
Ancak algoritma, JDK 1.2'den önce belgelerde belirtilmiş miydi? Değilse, bu farklı bir durumdur. Algoritma artık belgelerde belirtilmiştir, bu nedenle onu değiştirmek bir kamu sözleşmesinde kırılma bir değişiklik olacaktır.
Jon Skeet

(1.1 olarak hatırlıyorum.) Orijinal (daha zayıf) algoritma belgelendi. Yanlış. Belgelenen algoritma aslında bir ArrayIndexOutOfBoundsException attı.
Tom Hawtin - tackline

@Jon Skeet: Ah, String.hashCode () algoritmasının belgelendiğini bilmiyordum. Elbette bu işleri değiştirir. Yorumum güncellendi.
sleske

3

Endişelenilmesi gereken bir diğer (!) Konu, Java'nın erken / geç sürümleri arasındaki olası uygulama değişikliğidir. Uygulama ayrıntılarının sabit olduğuna inanmıyorum ve bu nedenle gelecekteki bir Java sürümüne yükseltme yapılması sorunlara neden olabilir.

Sonuç olarak, uygulamasına güvenmem hashCode().

Belki de bu mekanizmayı kullanarak gerçekten çözmeye çalıştığınız sorunu vurgulayabilirsiniz ve bu daha uygun bir yaklaşımı vurgulayacaktır.


1
Cevabınız için teşekkürler. "Bu bir Java dizesidir" .hashCode ()! = 586653468 olduğunda somut bir örnek verebilir misiniz?
knorv

1
Hayýr. Üzgünüm. Demek istediğim, üzerinde test ettiğiniz her şey istediğiniz gibi çalışabilir. Ancak bu hala garanti değil. Dolayısıyla, sanal makinenin kontrolüne sahip olduğunuz (örneğin) kısa vadeli bir proje üzerinde çalışıyorsanız, yukarıdakiler sizin için işe yarayabilir. Ancak daha geniş bir dünyada buna güvenemezsiniz.
Brian Agnew

2
"gelecekteki bir Java sürümüne yükseltme sorunlara neden olabilir". Gelecekteki bir Java sürümüne yükseltme, hashCode yöntemini tamamen kaldırabilir. Veya dizeler için her zaman 0 döndürmesini sağlayın. Bu sizin için uyumsuz değişiklikler. Soru, Sun ^ HOracle ^ JCP'nin bunu büyük bir değişiklik olarak görüp görmeyeceği ve bu nedenle kaçınmaya değer olup olmadığıdır. Algoritma sözleşmede olduğu için, umuyorlar.
Steve Jessop

@SteveJessop iyi, switchdizeler üzerindeki ifadeler belirli bir sabit hash koduna dayanarak koda derlendiğinden, String'nin karma kod algoritmasındaki değişiklikler kesinlikle mevcut kodu
Holger

3

Sadece sorunuzu cevaplamak ve tartışmaya devam etmemek için. Apache Harmony JDK uygulaması farklı bir algoritma kullanıyor gibi görünüyor, en azından tamamen farklı görünüyor:

Güneş JDK

public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;

        for (int i = 0; i < len; i++) {
            h = 31*h + val[off++];
        }
        hash = h;
    }
    return h;
}

Apache Harmony

public int hashCode() {
    if (hashCode == 0) {
        int hash = 0, multiplier = 1;
        for (int i = offset + count - 1; i >= offset; i--) {
            hash += value[i] * multiplier;
            int shifted = multiplier << 5;
            multiplier = shifted - multiplier;
        }
        hashCode = hash;
    }
    return hashCode;
}

Kendiniz kontrol etmekten çekinmeyin ...


23
Sanırım sadece havalı davranıyorlar ve onu optimize ediyorlar. :) "(çarpan << 5) - çarpan" sadece 31 * çarpan, sonuçta ...
gevşeyin

Tamam, bunu kontrol etmek için çok tembeldim. Teşekkürler!
ReneS

1
Ama benim açımdan bunu açıklığa kavuşturmak için ... Karma koda asla güvenmeyin çünkü karma kod dahili bir şeydir.
ReneS

1
"offset", "count" ve "hashCode" değişkenleri ne anlama geliyor? Gelecekteki hesaplamaları önlemek için "karma kod" un önbelleğe alınmış bir değer olarak kullanıldığını ve bu "sayım" karakterlerin sayısı olduğunu varsayıyorum, ancak "uzaklık" nedir? Dize verildiğinde tutarlı olması için bu kodu kullanmak istediğimi varsayalım, ona ne yapmalıyım?
android geliştiricisi

1
@androiddeveloper Şimdi BU ilginç bir soru - kullanıcı adınıza göre tahmin etmeliydim. Gönderen Android dokümanlar sözleşmenin benziyor aynıdır: s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]ben yanılıyorum sürece Android hiçbir değişiklik olmadan Nesnenin Sun'ın uygulanmasını kullandığı için, bu.
Kartik Chugh

2

Değişikliklerden ve muhtemelen uyumsuz VM'lerden endişeleniyorsanız, mevcut karma kod uygulamasını kendi yardımcı program sınıfınıza kopyalayın ve bunu, karma kodlarınızı oluşturmak için kullanın.


Bunu söyleyecektim. Diğer yanıtlar soruyu yanıtlarken, ayrı bir hashCode işlevi yazmak muhtemelen knorv'un sorununa uygun çözümdür.
Nick

1

Karma kod, Dizedeki karakterlerin ASCII değerlerine göre hesaplanacaktır.

Bu, String Sınıfındaki uygulama aşağıdaki gibidir

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        hash = h = isLatin1() ? StringLatin1.hashCode(value)
                              : StringUTF16.hashCode(value);
    }
    return h;
}

Karma kodda çarpışmalar kaçınılmazdır. Örneğin, "Ea" ve "FB" dizeleri 2236 ile aynı hashcode verir

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.