Chức năng Hash tốt cho chuỗi


160

Tôi đang cố gắng nghĩ ra một hàm băm tốt cho chuỗi. Và tôi đã nghĩ rằng có thể là một ý tưởng tốt để tổng hợp các giá trị unicode cho năm ký tự đầu tiên trong chuỗi (giả sử nó có năm, nếu không thì dừng ở nơi nó kết thúc). Đó sẽ là một ý tưởng tốt, hay nó là một ý tưởng tồi?

Tôi đang làm điều này trong Java, nhưng tôi sẽ không tưởng tượng rằng điều đó sẽ tạo ra nhiều sự khác biệt.


4
Các hàm băm tốt phụ thuộc rất nhiều vào đầu vào của hàm băm và các yêu cầu của thuật toán. Một hàm băm như vậy sẽ không tốt lắm nếu tất cả các chuỗi của bạn bắt đầu với cùng năm ký tự. Nó cũng sẽ có xu hướng dẫn đến một phân phối bình thường.
WhirlWind

1
Bản sao có thể có của 98153
Michael Mrozek

14
Tại sao bạn không thể sử dụng Stringcủa riêng mình hashCode()?
Bart Kiers

@WhirlWind, đúng, tôi không chắc chuỗi sẽ có gì, ngoài ra nó có thể sẽ là văn bản tiếng Anh.
Leif Andersen

@Barl, chủ yếu là vì giáo sư của tôi đã bảo chúng tôi triển khai hàm băm băm riêng của chúng tôi ... và lý do tôi không muốn sử dụng Java, là vì nó chung chung và tôi sẽ tưởng tượng một hàm functor cụ thể hơn sẽ tốt hơn.
Leif Andersen

Câu trả lời:


161

Thông thường băm sẽ không tính tổng, nếu không stoppotssẽ có cùng hàm băm.

và bạn sẽ không giới hạn nó ở n ký tự đầu tiên vì nếu không nhà và nhà sẽ có cùng hàm băm.

Nói chung, các hàm băm lấy các giá trị và nhân nó với một số nguyên tố (làm cho nó có nhiều khả năng tạo ra các giá trị băm duy nhất) Vì vậy, bạn có thể làm một cái gì đó như:

int hash = 7;
for (int i = 0; i < strlen; i++) {
    hash = hash*31 + charAt(i);
}

@jonathanasdf Làm thế nào bạn có thể nói rằng nó luôn cung cấp cho bạn một khóa băm duy nhất. Có bằng chứng toán học nào không? Tôi nghĩ rằng chúng ta phải thực hiện mod băm với một số nguyên tố lớn hơn, nếu không xảy ra sự cố tràn.
devsda

17
@devsda Anh ấy không nói luôn là duy nhất, anh ấy nói nhiều khả năng là độc nhất. Về lý do tại sao, một tìm kiếm nhanh trên google cho thấy bài viết này: tính toán cuộc sống.wordpress.com/2008/11/20/ nam giải thích lý do tại sao 31 được sử dụng để băm chuỗi Java. Không có bằng chứng toán học nào được đưa ra, nhưng nó giải thích khái niệm chung là tại sao các số nguyên tố hoạt động tốt hơn.
Pharap

2
Cảm ơn rất nhiều vì đã làm rõ ý tưởng làm băm tốt hơn. Chỉ cần kiểm tra lại - Giá trị trả về hashCode () sẽ được Java sử dụng để ánh xạ tới một số chỉ mục bảng trước khi lưu trữ đối tượng. Vì vậy, nếu hashCode () trả về m, nó sẽ thực hiện một số thứ như (m mod k) để lấy chỉ mục của bảng có kích thước k. Có đúng không?
WhiteHat

1
"băm = băm * 31 + charAt (i);" tạo ra cùng một hàm băm cho đốm, ngọn, dừng, opts và chậu.
Jack Straub

1
@maq Tôi tin bạn là chính xác. Không biết tôi đang nghĩ gì.
Jack Straub

139

Nếu đó là một điều bảo mật, bạn có thể sử dụng tiền điện tử Java:

import java.security.MessageDigest;

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(stringToEncrypt.getBytes());
String encryptedString = new String(messageDigest.digest());

93
Đẹp. Tôi có một ứng dụng học máy, thực hiện NLP thống kê trên một khối lượng lớn. Sau một vài lần bình thường hóa hình thái ban đầu trên các từ gốc trong văn bản, tôi vứt bỏ các giá trị chuỗi và sử dụng mã băm thay thế. Trong toàn bộ kho văn bản của tôi, có khoảng 600.000 từ duy nhất và sử dụng chức năng mã băm java mặc định, tôi đã nhận được khoảng 3,5% va chạm. Nhưng nếu tôi SHA-256 giá trị chuỗi và sau đó tạo mã băm từ chuỗi đã tiêu hóa, tỷ lệ va chạm nhỏ hơn 0,0001%. Cảm ơn!
benjismith

3
Cảm ơn đã cung cấp thông tin về các va chạm và số lượng từ. Rất hữu ích.
philipp

19
@benjismith Một trong một triệu là quá lớn ... là "dưới 0,0001%" một cách nói xiên "chính xác là 0"? Tôi thực sự nghi ngờ rằng bạn đã thấy một vụ va chạm SHA-256 bởi vì điều đó chưa bao giờ được quan sát, ở bất cứ đâu, bao giờ; thậm chí không cho 160 bit SHA-1. Nếu bạn có hai chuỗi sản xuất cùng một SHA-256, cộng đồng bảo mật rất thích nhìn thấy chúng; bạn sẽ nổi tiếng thế giới ... một cách rất mơ hồ. Xem So sánh các chức năng SHA
Tim Sylvester

7
@TimSylvester, bạn hiểu lầm. Tôi đã không tìm thấy va chạm SHA-256. Tôi đã tính toán SHA-256 và sau đó đưa các chuỗi byte kết quả vào một hàm "hashCode" điển hình của Java, bởi vì tôi cần hàm băm 32 bit. Đó là nơi tôi tìm thấy những vụ va chạm. Không có gì đáng chú ý :)
benjismith

1
Không có sự khác biệt giữa 'băm' và 'mã hóa'? Tôi hiểu MessageDigest là một hàm băm một chiều, phải không? Ngoài ra, khi tôi sử dụng hàm, tôi đã nhận được chuỗi băm là rất nhiều ký tự UTF rác khi tôi mở tệp trong LibreOffice. Có thể lấy chuỗi băm dưới dạng một loạt các ký tự chữ và số thay vì các ký tự UTF rác không?
Nav

38

Bạn có thể nên sử dụng String.hashCode () .

Nếu bạn thực sự muốn tự thực hiện hashCode:

Không nên loại trừ các phần quan trọng của một đối tượng khỏi tính toán mã băm để cải thiện hiệu suất - Joshua Bloch, Java hiệu quả

Chỉ sử dụng năm ký tự đầu tiên là một ý tưởng tồi . Hãy suy nghĩ về các tên phân cấp, chẳng hạn như URL: tất cả chúng sẽ có cùng mã băm (vì tất cả đều bắt đầu bằng "http: //", có nghĩa là chúng được lưu trữ trong cùng một nhóm trong bản đồ băm, thể hiện hiệu suất khủng khiếp.

Đây là một câu chuyện chiến tranh được diễn giải trên chuỗi hashode từ " Java hiệu quả ":

Hàm băm Chuỗi được triển khai trong tất cả các bản phát hành trước 1,2 được kiểm tra tối đa mười sáu ký tự, cách đều nhau trong chuỗi, bắt đầu bằng ký tự đầu tiên. Đối với các bộ sưu tập lớn các tên phân cấp, chẳng hạn như URL, hàm băm này hiển thị hành vi khủng khiếp.


1
Nếu một người đang sử dụng một bộ sưu tập băm kép, có thể đáng để có băm đầu tiên thực sự nhanh chóng và bẩn thỉu. Nếu một chuỗi có một nghìn chuỗi dài, một nửa trong số đó được ánh xạ bởi hàm crum thành một giá trị cụ thể và một nửa trong số đó được ánh xạ tới các giá trị riêng biệt, hiệu suất trong bảng băm đơn sẽ rất tệ, nhưng hiệu suất trong một đôi bảng băm, trong đó hàm băm thứ hai kiểm tra toàn bộ chuỗi, có thể gần gấp đôi so với bảng băm đơn (vì một nửa chuỗi sẽ không phải được băm hoàn toàn). Mặc dù vậy, không có bộ sưu tập Java tiêu chuẩn nào thực hiện băm kép.
supercat

Liên kết Java hiệu quả bị hỏng @Frederik
KG

17

Nếu bạn đang làm điều này trong Java thì tại sao bạn lại làm điều đó? Chỉ cần gọi .hashCode()vào chuỗi


2
Tôi đang thực hiện nó như là một phần của lớp và một phần của bài tập là viết lên một số hàm băm khác nhau. Giáo sư nói với chúng tôi để có được sự giúp đỡ bên ngoài cho những người 'tốt hơn'.
Leif Andersen

20
Nếu bạn cần phải nhất quán giữa các phiên bản và triển khai JVM, bạn không nên dựa vào .hashCode(). Thay vào đó, sử dụng một số thuật toán được biết đến.
Stephen Ostermiller

7
Thuật toán String::hashCodeđược chỉ định trong JDK, vì vậy nó có khả năng di động như chính sự tồn tại của lớp java.lang.String.
yshavit


8

Hàm này do Nick cung cấp là tốt nhưng nếu bạn sử dụng Chuỗi mới (byte [] byte) để thực hiện chuyển đổi thành Chuỗi, thì không thành công. Bạn có thể sử dụng chức năng này để làm điều đó.

private static final char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

public static String byteArray2Hex(byte[] bytes) {
    StringBuffer sb = new StringBuffer(bytes.length * 2);
    for(final byte b : bytes) {
        sb.append(hex[(b & 0xF0) >> 4]);
        sb.append(hex[b & 0x0F]);
    }
    return sb.toString();
}

public static String getStringFromSHA256(String stringToEncrypt) throws NoSuchAlgorithmException {
    MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
    messageDigest.update(stringToEncrypt.getBytes());
    return byteArray2Hex(messageDigest.digest());
}

Điều này có thể giúp ai đó


Bạn chỉ có thể truyền mảng byte cho messageDigest.update ().
szgal

byteArray2Hex () - đó hoàn toàn là những gì tôi đang tìm kiếm! Cảm ơn rất nhiều :)
Krzysiek

5
// djb2 hash function
unsigned long hash(unsigned char *str)
{
    unsigned long hash = 5381;
    int c;

    while (c = *str++)
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

nguồn logic đằng sau hàm băm djb2 - SO


1
Tôi nghĩ rằng đó chỉ là một số nguyên tố để bắt đầu, để chúng ta có ít va chạm hơn.
CornSmith

5

FNV-1 được đồn đại là một hàm băm tốt cho chuỗi.

Đối với các chuỗi dài (dài hơn khoảng 200 ký tự), bạn có thể có hiệu suất tốt từ hàm băm MD4 . Là một chức năng mã hóa, nó đã bị phá vỡ khoảng 15 năm trước, nhưng với mục đích phi mật mã, nó vẫn rất tốt và nhanh đến mức đáng ngạc nhiên. Trong ngữ cảnh của Java, bạn sẽ phải chuyển đổi 16 bitchar giá trị thành các từ 32 bit, ví dụ: bằng cách nhóm các giá trị đó thành các cặp. Một triển khai nhanh MD4 trong Java có thể được tìm thấy trong sphlib . Có lẽ quá mức cần thiết trong bối cảnh của một bài tập trong lớp, nhưng nếu không thì đáng để thử.


Hàm băm này tốt hơn nhiều so với hàm đi kèm với java.
clankill3r

3

Nếu bạn muốn xem các triển khai tiêu chuẩn công nghiệp, tôi sẽ xem java.security.MessageDigest .

"Thông báo tiêu hóa là các hàm băm một chiều an toàn, lấy dữ liệu có kích thước tùy ý và đưa ra giá trị băm có độ dài cố định."


1

đây là một liên kết giải thích nhiều hàm băm khác nhau, hiện tại tôi thích hàm băm ELF cho vấn đề cụ thể của bạn. Nó nhận như là một chuỗi có độ dài tùy ý.


1

sdbm: thuật toán này đã được tạo cho thư viện cơ sở dữ liệu sdbm (một sự tái hiện miền công cộng của ndbm)

static unsigned long sdbm(unsigned char *str)
{   
    unsigned long hash = 0;
    int c;
    while (c = *str++)
            hash = c + (hash << 6) + (hash << 16) - hash;

    return hash;
}

0
         public String hashString(String s) throws NoSuchAlgorithmException {
    byte[] hash = null;
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        hash = md.digest(s.getBytes());

    } catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < hash.length; ++i) {
        String hex = Integer.toHexString(hash[i]);
        if (hex.length() == 1) {
            sb.append(0);
            sb.append(hex.charAt(hex.length() - 1));
        } else {
            sb.append(hex.substring(hex.length() - 2));
        }
    }
    return sb.toString();
}

-1

Đó là một ý tưởng tốt để làm việc với số lẻ khi cố gắng phát triển một hàm hast tốt cho chuỗi. Hàm này lấy một chuỗi và trả về một giá trị chỉ mục, cho đến nay nó hoạt động khá tốt. và ít va chạm. chỉ số dao động từ 0 - 300 thậm chí có thể nhiều hơn thế, nhưng tôi đã không tăng cao hơn cho đến nay ngay cả với các từ dài như "kỹ thuật cơ điện"

int keyHash(string key)
{
    unsigned int k = (int)key.length();
    unsigned int u = 0,n = 0;

    for (Uint i=0; i<k; i++)
    {
        n = (int)key[i];
        u += 7*n%31;
    }
    return u%139;
}

một điều khác bạn có thể làm là nhân mỗi ký tự int parse với chỉ số khi nó tăng như từ "gấu" (0 * b) + (1 * e) + (2 * a) + (3 * r) sẽ cung cấp cho bạn một giá trị int để chơi với. hàm băm đầu tiên ở trên va chạm tại "đây" và "nghe" nhưng vẫn tuyệt vời khi đưa ra một số giá trị độc đáo tốt. cái bên dưới không va chạm với "ở đây" và "nghe" vì tôi nhân mỗi nhân vật với chỉ số khi nó tăng lên.

int keyHash(string key)
{
    unsigned int k = (int)key.length();
    unsigned int u = 0,n = 0;

    for (Uint i=0; i<k; i++)
    {
        n = (int)key[i];
        u += i*n%31;
    }
    return u%139;
}

-1

Đây là một hàm băm đơn giản mà tôi sử dụng cho bảng băm tôi đã xây dựng. Về cơ bản, nó để lấy một tệp văn bản và lưu trữ mọi từ trong một chỉ mục đại diện cho thứ tự chữ cái.

int generatehashkey(const char *name)
{
        int x = tolower(name[0])- 97;
        if (x < 0 || x > 25)
           x = 26;
        return x;
}

Điều này về cơ bản là các từ được băm theo chữ cái đầu tiên của chúng. Vì vậy, từ bắt đầu bằng 'a' sẽ có khóa băm là 0, 'b' sẽ nhận được 1 và cứ thế và 'z' sẽ là 25. Số và ký hiệu sẽ có khóa băm là 26. Đây là một lợi thế mà điều này mang lại ; Bạn có thể tính toán dễ dàng và nhanh chóng nơi một từ nhất định sẽ được lập chỉ mục trong bảng băm vì tất cả theo thứ tự bảng chữ cái, đại loại như thế này: Mã có thể được tìm thấy ở đây: https://github.com/abhijitcpatil/general

Đưa ra văn bản sau đây làm đầu vào: Một ngày nọ, Atticus nói với Jem, tôi muốn nói rằng bạn bắn vào lon thiếc ở sân sau, nhưng tôi biết bạn sẽ đuổi theo những con chim. Bắn tất cả các tia màu xanh mà bạn muốn, nếu bạn có thể bắn chúng, nhưng hãy nhớ rằng việc giết một con chim nhại là tội lỗi. Đó là lần duy nhất tôi từng nghe Atticus nói rằng thật tội lỗi khi làm điều gì đó, và tôi đã hỏi cô Maudie về điều đó. Cha nói đúng, cha cô nói. Mockingbird không làm một việc gì ngoài việc tạo ra âm nhạc cho chúng ta thưởng thức. Họ không ăn hết vườn của mọi người, không làm tổ trong những chiếc cũi ngô, họ không làm một việc gì ngoài việc hát lên tiếng lòng của chúng tôi dành cho chúng tôi. Đó là lý do tại sao giết một con chim nhại.

Đây sẽ là đầu ra:

0 --> a a about asked and a Atticus a a all after at Atticus
1 --> but but blue birds. but backyard
2 --> cribs corn can cans
3 --> do dont dont dont do dont do day
4 --> eat enjoy. except ever
5 --> for for fathers
6 --> gardens go
7 --> hearts heard hit
8 --> its in it. I it I its if I in
9 --> jays Jem
10 --> kill kill know
11 --> 
12 --> mockingbird. music make Maudie Miss mockingbird.”
13 --> nest
14 --> out one one only one
15 --> peoples
16 --> 17 --> right remember rather
18 --> sin sing said. she something sin say sin Shoot shot said
19 --> to Thats their thing they They to thing to time the That to the the tin to
20 --> us. up us
21 --> 
22 --> why was was want
23 --> 
24 --> you you youll you
25 --> 
26 --> Mockingbirds  Your em Id

2
Hàm băm tốt phân phối các giá trị bằng nhau trên các nhóm.
Jonathan Peterson

-1

Điều này sẽ tránh mọi va chạm và nó sẽ nhanh cho đến khi chúng ta sử dụng sự dịch chuyển trong tính toán.

 int k = key.length();
    int sum = 0;
    for(int i = 0 ; i < k-1 ; i++){
        sum += key.charAt(i)<<(5*i);
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.