2011-03-01 13 views
38

Để trích dẫn từ Guidelines and rules for GetHashCode bởi Eric Lippert:Làm cách nào để tạo một HashCode trong .net (C#) cho một chuỗi an toàn để lưu trữ trong cơ sở dữ liệu?

Rule: tiêu dùng của GetHashCode không thể dựa vào nó là ổn định theo thời gian hoặc trên AppDomains

Giả sử bạn có một đối tượng khách hàng mà có một loạt các các trường như Tên, Địa chỉ, v.v. Nếu bạn tạo hai đối tượng chính xác với cùng một dữ liệu trong hai quy trình khác nhau, chúng không phải trả lại cùng mã băm . Nếu bạn tạo một đối tượng như vậy trên Thứ Ba trong một quy trình, hãy tắt nó, và chạy lại chương trình vào Thứ Tư, mã băm có thể là khác nhau.

Điều này đã cắn người trong quá khứ. Tài liệu cho các ghi chú Hệ thống.String.GetHashCode cụ thể là hai chuỗi giống nhau có thể có các mã băm khác nhau trong các phiên bản khác nhau của CLR và trên thực tế chúng thực hiện. Không lưu trữ chuỗi băm trong cơ sở dữ liệu và mong đợi chúng giống nhau mãi mãi, bởi vì chúng sẽ không tồn tại.

Vì vậy, cách chính xác để tạo HashCode của chuỗi mà tôi có thể lưu trữ trong cơ sở dữ liệu là gì?

(Xin vui lòng cho tôi biết tôi không phải là người đầu tiên đã để lại lỗi này trong phần mềm tôi đã viết!)

+2

Vâng, tôi không bao giờ dựa vào GetHashCode, bởi vì tôi biết, làm thế nào cẩu thả tôi thực hiện phương pháp này. Tôi tin rằng những người khác không làm điều đó tốt hơn ... ;-) –

+3

Bạn không phải là người đầu tiên đã để lại lỗi này trong phần mềm mà bạn đã viết. – Bobby

+2

Động cơ Dbase đã rất tốt ở các chuỗi băm. Chỉ cần tạo chỉ mục cho cột. –

Trả lời

64

Tùy thuộc vào những thuộc tính bạn muốn có băm. Ví dụ, bạn có thể chỉ cần viết một cái gì đó như thế này:

public int HashString(string text) 
{ 
    // TODO: Determine nullity policy. 

    unchecked 
    { 
     int hash = 23; 
     foreach (char c in text) 
     { 
      hash = hash * 31 + c; 
     } 
     return hash; 
    } 
} 

Vì vậy, miễn là bạn tài liệu rằng đó là cách băm được tính, đó là hợp lệ. Nó không hề an toàn về mặt mật mã hay bất cứ thứ gì như thế, nhưng bạn có thể kiên trì nó mà không gặp vấn đề gì. Hai chuỗi hoàn toàn bằng nhau theo nghĩa thứ tự (nghĩa là không có sự bình đẳng văn hóa vv được áp dụng, chính xác từng ký tự giống nhau) sẽ tạo ra cùng một mã băm với mã này.

Những vấn đề đến khi bạn dựa vào không có giấy tờ băm - ví dụ: một cái gì đó mà tuân GetHashCode() nhưng là không có cách nào để đảm bảo giữ nguyên từ phiên bản lên phiên bản ... như string.GetHashCode().

Viết và ghi tài liệu băm của riêng bạn như thế này hơi giống như nói, "Thông tin nhạy cảm này được băm với MD5 (hoặc bất kỳ điều gì)". Vì vậy, miễn là nó là một băm được xác định tốt, đó là tốt.

EDIT: Các câu trả lời khác đã đề xuất sử dụng băm mật mã như SHA-1 hoặc MD5.Tôi sẽ nói rằng cho đến khi chúng ta biết có một yêu cầu về bảo mật mã hóa thay vì chỉ là sự ổn định, không có điểm nào trong việc đi qua rigmarole của việc chuyển đổi chuỗi thành một mảng byte và băm. Tất nhiên nếu mã băm có nghĩa là được sử dụng cho bất kỳ điều gì liên quan đến bảo mật, mã băm tiêu chuẩn ngành là chính xác là những gì bạn nên tiếp cận. Nhưng điều đó không được đề cập ở bất kỳ đâu trong câu hỏi.

+3

Có ma thuật nào khoảng 23 và '* 31' không? Thay vào đó, bất kỳ lý do nào để chọn những giá trị đó trên bất kỳ giá trị nào khác? ... trên bất kỳ phương pháp băm khác [được ghi chép] nào? Tôi đoán không, mặc dù 31 là một ít hơn ASCII printables đã giữ cho tôi không cần thiết đáng ngờ. – ruffin

+10

@ruffin: Chúng là giá trị được đề xuất bởi Josh Bloch. Nhân với 31 là hiệu quả bởi vì nó có thể được thực hiện như một sự thay đổi và trừ đi. Có rất nhiều câu hỏi khác nói về điều này - đó là một chút của một nghệ thuật đen tối, phải trung thực. –

+15

Gọn gàng! Từ [Effective Java (2008), trang 48] (https://books.google.com.vn/books?id=ka2VUBqHiWkC): * Giá trị 31 được chọn vì nó là số nguyên tố lẻ. Nếu nó đã được ngay cả và nhân tràn, thông tin sẽ bị mất, như phép nhân tương đương với chuyển dịch. Lợi thế của việc sử dụng một số nguyên tố ít rõ ràng hơn, nhưng nó là truyền thống. Một thuộc tính tốt đẹp của 31 là phép nhân có thể được thay thế bằng phép dịch và phép trừ để có hiệu năng tốt hơn: '31 * i == (i << 5) - i'. Các máy ảo hiện đại tự động thực hiện loại tối ưu hóa này. * Hình như một số cách đọc thú vị; cảm ơn một lần nữa. – ruffin

1

Câu trả lời là chỉ cần viết hàm băm của riêng bạn. Bạn có thể tìm nguồn cho một số bằng cách theo các liên kết trong các nhận xét đến bài viết bạn đã đăng. Hoặc bạn có thể sử dụng hàm băm có sẵn ban đầu dành cho mật mã (MD5, SHA1, v.v.) và không sử dụng tất cả các bit.

6

Dưới đây là một reimplementation của the current way .NET calculates it's string hash code for 64 bit systems. Điều này không sử dụng con trỏ như GetHashCode() thực hiện vì vậy nó sẽ hơi chậm hơn, nhưng nó làm cho nó linh hoạt hơn để thay đổi nội bộ để string, điều này sẽ cung cấp cho một mã băm phân phối đồng đều hơn Jon Skeet's version mà có thể dẫn đến thời gian tra cứu tốt hơn trong từ điển .

public static class StringExtensionMethods 
{ 
    public static int GetStableHashCode(this string str) 
    { 
     unchecked 
     { 
      int hash1 = 5381; 
      int hash2 = hash1; 

      for(int i = 0; i < str.Length && str[i] != '\0'; i += 2) 
      { 
       hash1 = ((hash1 << 5) + hash1)^str[i]; 
       if (i == str.Length - 1 || str[i+1] == '\0') 
        break; 
       hash2 = ((hash2 << 5) + hash2)^str[i+1]; 
      } 

      return hash1 + (hash2*1566083941); 
     } 
    } 
} 
Các vấn đề liên quan