Tại sao nên sử dụng các ký hiệu làm khóa băm trong Ruby?


161

Rất nhiều lần mọi người sử dụng các biểu tượng làm khóa trong hàm băm Ruby.

Lợi thế của việc sử dụng một chuỗi là gì?

Ví dụ:

hash[:name]

so với

hash['name']

Câu trả lời:


226

TL; DR:

Sử dụng các biểu tượng không chỉ tiết kiệm thời gian khi thực hiện so sánh, mà còn tiết kiệm bộ nhớ, vì chúng chỉ được lưu trữ một lần.

Biểu tượng Ruby là bất biến (không thể thay đổi), điều này làm cho việc tìm kiếm thứ gì đó dễ dàng hơn nhiều

Câu trả lời ngắn gọn (ish):

Sử dụng các biểu tượng không chỉ tiết kiệm thời gian khi thực hiện so sánh, mà còn tiết kiệm bộ nhớ, vì chúng chỉ được lưu trữ một lần.

Các biểu tượng trong Ruby về cơ bản "các chuỗi bất biến" .. điều đó có nghĩa là chúng không thể thay đổi và nó ngụ ý rằng cùng một biểu tượng khi được tham chiếu nhiều lần trong mã nguồn của bạn, luôn được lưu trữ dưới dạng cùng một thực thể, ví dụ có cùng một id đối tượng .

Mặt khác, chuỗi có thể thay đổi , chúng có thể được thay đổi bất cứ lúc nào. Điều này ngụ ý rằng Ruby cần lưu trữ từng chuỗi bạn đề cập trong toàn bộ mã nguồn của mình trong thực thể riêng biệt, ví dụ: nếu bạn có một chuỗi "tên" nhiều lần được đề cập trong mã nguồn của mình, thì Ruby cần lưu trữ tất cả các chuỗi này trong các đối tượng Chuỗi riêng biệt, bởi vì chúng có thể thay đổi sau này (đó là bản chất của chuỗi Ruby).

Nếu bạn sử dụng một chuỗi làm khóa Hash, Ruby cần đánh giá chuỗi và xem nội dung của nó (và tính hàm băm trên đó) và so sánh kết quả với các giá trị (băm) của các khóa đã được lưu trong Hash .

Nếu bạn sử dụng một biểu tượng làm khóa Hash, thì nó sẽ ngầm hiểu rằng nó không thể thay đổi được, vì vậy về cơ bản, Ruby có thể chỉ cần so sánh (id hàm băm của) id đối tượng với các id đối tượng (băm) của các khóa đã được lưu trữ trong Hash. (nhanh hơn nhiều)

Nhược điểm: Mỗi biểu tượng tiêu thụ một vị trí trong bảng biểu tượng của trình thông dịch Ruby, không bao giờ được phát hành. Biểu tượng không bao giờ được thu gom rác. Vì vậy, trường hợp góc là khi bạn có một số lượng lớn các ký hiệu (ví dụ: các biểu tượng được tạo tự động). Trong trường hợp đó, bạn nên đánh giá điều này ảnh hưởng đến kích thước của trình thông dịch Ruby của bạn.

Ghi chú:

Nếu bạn thực hiện so sánh chuỗi, Ruby có thể so sánh các biểu tượng chỉ bằng id đối tượng của chúng mà không phải đánh giá chúng. Điều đó nhanh hơn nhiều so với việc so sánh các chuỗi cần được đánh giá.

Nếu bạn truy cập hàm băm, Ruby luôn áp dụng hàm băm để tính toán "khóa băm" từ bất kỳ khóa nào bạn sử dụng. Bạn có thể tưởng tượng một cái gì đó giống như MD5-hash. Và rồi Ruby so sánh những "khóa băm" đó với nhau.

Câu trả lời dài:

https://web.archive.org/web/20180709094450/http://www.reactive.io/tips/2009/01/11/the-difference-b Among-ruby-symbols-and-strings

http://www.randomhacks.net.s3-website-us-east-1.amazonaws.com/2007/01/20/13-ways-of-looking-at-a-ruby-symbol/


5
Fyi, Symbols sẽ là GCd trong phiên bản tiếp theo của Ruby: bug.ruby-lang.org/issues/9634
Ajedi32

2
Ngoài ra, Chuỗi được tự động đóng băng khi được sử dụng làm khóa Hash trong Ruby. Vì vậy, không chính xác rằng String có thể thay đổi khi nói về chúng trong bối cảnh này.
Ajedi32

1
Cái nhìn sâu sắc về chủ đề & Liên kết đầu tiên trong phần "Câu trả lời dài" được xóa hoặc di chuyển.
Hbksagar

2
Biểu tượng là rác được thu thập trong Ruby 2.2
Marc-André Lafortune

2
Câu trả lời chính xác! Về mặt trolling, "câu trả lời ngắn" của bạn cũng đủ dài. ;)
Technophyle

22

Lý do là hiệu quả, với nhiều mức tăng qua Chuỗi:

  1. Biểu tượng là bất biến, vì vậy câu hỏi "điều gì xảy ra nếu phím thay đổi?" không cần phải hỏi.
  2. Các chuỗi được nhân đôi trong mã của bạn và thường sẽ chiếm nhiều dung lượng hơn trong bộ nhớ.
  3. Tra cứu băm phải tính toán băm của các khóa để so sánh chúng. Đây là O(n)cho Chuỗi và hằng cho Biểu tượng.

Hơn nữa, Ruby 1.9 đã giới thiệu một cú pháp đơn giản hóa chỉ để băm với các khóa biểu tượng (ví dụ h.merge(foo: 42, bar: 6)) và Ruby 2.0 có các đối số từ khóa chỉ hoạt động cho các khóa biểu tượng.

Ghi chú :

1) Bạn có thể ngạc nhiên khi biết rằng Ruby đối xử với Stringcác khóa khác với bất kỳ loại nào khác. Thật:

s = "foo"
h = {}
h[s] = "bar"
s.upcase!
h.rehash   # must be called whenever a key changes!
h[s]   # => nil, not "bar"
h.keys
h.keys.first.upcase!  # => TypeError: can't modify frozen string

Chỉ dành cho khóa chuỗi, Ruby sẽ sử dụng bản sao được đóng băng thay vì chính đối tượng.

2) Các chữ cái "b", "a" và "r" chỉ được lưu trữ một lần cho tất cả các lần xuất hiện :bartrong một chương trình. Trước Ruby 2.2, một ý tưởng tồi là liên tục tạo ra cái mới Symbolskhông bao giờ được sử dụng lại, vì chúng sẽ tồn tại trong bảng tra cứu Biểu tượng toàn cầu mãi mãi. Ruby 2.2 sẽ thu gom rác, vì vậy không phải lo lắng.

3) Trên thực tế, việc tính toán hàm băm cho Biểu tượng không mất nhiều thời gian trong Ruby 1.8.x, vì ID đối tượng được sử dụng trực tiếp:

:bar.object_id == :bar.hash # => true in Ruby 1.8.7

Trong Ruby 1.9.x, điều này đã thay đổi khi băm thay đổi từ phiên này sang phiên khác (bao gồm cả phiên bản Symbols):

:bar.hash # => some number that will be different next time Ruby 1.9 is ran

+1 cho ghi chú tuyệt vời của bạn! Ban đầu tôi không đề cập đến hàm băm trong câu trả lời của mình, vì tôi đã cố gắng làm cho nó dễ đọc hơn :)
Tilo

@Tilo: thật vậy, đó là lý do tại sao tôi đã viết câu trả lời của tôi :-) Tôi vừa mới chỉnh sửa câu trả lời của tôi kể đến cú pháp đặc biệt trong Ruby 1.9 và đặt tên các tham số hứa của Ruby 2.0
Marc-André Lafortune

Bạn có thể giải thích cách tìm kiếm Hash không đổi đối với Biểu tượng và O (n) cho Chuỗi không?
Asad Moosvi

7

Re: lợi thế của việc sử dụng một chuỗi là gì?

  • Kiểu dáng: đó là Ruby-way
  • (Rất) tìm kiếm giá trị nhanh hơn một chút vì băm một biểu tượng tương đương với băm một số nguyên so với băm một chuỗi.

  • Nhược điểm: tiêu thụ một vị trí trong bảng biểu tượng của chương trình không bao giờ được phát hành.


4
+1 để đề cập rằng biểu tượng không bao giờ được thu gom rác.
Vortico

biểu tượng không bao giờ là rác được thu thập - không đúng sự thật kể từ ruby ​​2.2+
eudaimonia

0

Tôi rất quan tâm đến việc theo dõi liên quan đến chuỗi đông lạnh được giới thiệu trong Ruby 2.x.

Khi bạn xử lý nhiều chuỗi đến từ một kiểu nhập văn bản (ví dụ tôi đang nghĩ đến các thông số hoặc tải trọng HTTP, thông qua Rack), cách sử dụng chuỗi ở mọi nơi dễ dàng hơn.

Khi bạn đối phó với hàng tá trong số họ nhưng họ không bao giờ thay đổi (nếu họ là "từ vựng" kinh doanh của bạn), tôi muốn nghĩ rằng việc đóng băng chúng có thể tạo ra sự khác biệt. Tôi chưa thực hiện bất kỳ điểm chuẩn nào, nhưng tôi đoán nó sẽ đóng hiệu suất biểu tượng.

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.