Khi nào sử dụng các biểu tượng thay vì chuỗi trong Ruby?


98

Nếu có ít nhất hai trường hợp của cùng một chuỗi trong tập lệnh của tôi, thay vào đó tôi có nên sử dụng một ký hiệu không?

Câu trả lời:


175

TL; DR

Một nguyên tắc chung đơn giản là sử dụng các ký hiệu mỗi khi bạn cần số nhận dạng nội bộ. Đối với Ruby <2.2, chỉ sử dụng các biểu tượng khi chúng không được tạo động, để tránh rò rỉ bộ nhớ.

Câu trả lời đầy đủ

Lý do duy nhất để không sử dụng chúng cho các số nhận dạng được tạo động là vì lo ngại về bộ nhớ.

Câu hỏi này rất phổ biến vì nhiều ngôn ngữ lập trình không có ký hiệu, chỉ có chuỗi và do đó chuỗi cũng được sử dụng làm định danh trong mã của bạn. Bạn nên lo lắng về những biểu tượng có nghĩa là gì , không chỉ khi bạn nên sử dụng các biểu tượng . Các ký hiệu được dùng để nhận dạng. Nếu bạn tuân theo triết lý này, rất có thể bạn sẽ làm đúng.

Có một số khác biệt giữa việc triển khai các ký hiệu và chuỗi. Điều quan trọng nhất về các biểu tượng là chúng không thể thay đổi . Điều này có nghĩa là chúng sẽ không bao giờ bị thay đổi giá trị. Do đó, các ký hiệu được khởi tạo nhanh hơn các chuỗi và một số thao tác như so sánh hai ký hiệu cũng nhanh hơn.

Thực tế là một biểu tượng là bất biến cho phép Ruby sử dụng cùng một đối tượng mỗi khi bạn tham chiếu đến biểu tượng, tiết kiệm bộ nhớ. Vì vậy, mỗi khi trình thông dịch đọc :my_keynó có thể lấy nó từ bộ nhớ thay vì khởi tạo lại nó. Điều này ít tốn kém hơn so với việc khởi tạo một chuỗi mới mỗi lần.

Bạn có thể nhận được danh sách tất cả các ký hiệu đã được khởi tạo bằng lệnh Symbol.all_symbols:

symbols_count = Symbol.all_symbols.count # all_symbols is an array with all 
                                         # instantiated symbols. 
a = :one
puts a.object_id
# prints 167778 

a = :two
puts a.object_id
# prints 167858

a = :one
puts a.object_id
# prints 167778 again - the same object_id from the first time!

puts Symbol.all_symbols.count - symbols_count
# prints 2, the two objects we created.

Đối với các phiên bản Ruby trước 2.2, một khi một biểu tượng được khởi tạo, bộ nhớ này sẽ không bao giờ trống nữa . Cách duy nhất để giải phóng bộ nhớ là khởi động lại ứng dụng. Vì vậy các ký hiệu cũng là một nguyên nhân chính gây ra hiện tượng rò rỉ bộ nhớ khi sử dụng không đúng cách. Cách đơn giản nhất để tạo rò rỉ bộ nhớ là sử dụng phương pháp to_symtrên dữ liệu đầu vào của người dùng, vì dữ liệu này sẽ luôn thay đổi, một phần bộ nhớ mới sẽ được sử dụng mãi mãi trong phiên bản phần mềm. Ruby 2.2 đã giới thiệu trình thu gom rác biểu tượng , giúp giải phóng các biểu tượng được tạo động, vì vậy việc rò rỉ bộ nhớ được tạo ra bằng cách tạo biểu tượng động sẽ không còn là mối quan tâm nữa.

Trả lời câu hỏi của bạn:

Có đúng là tôi phải sử dụng một biểu tượng thay vì một chuỗi nếu có ít nhất hai chuỗi giống nhau trong ứng dụng hoặc tập lệnh của tôi không?

Nếu những gì bạn đang tìm kiếm là một số nhận dạng được sử dụng trong nội bộ mã của bạn, bạn nên sử dụng các ký hiệu. Nếu bạn đang in kết quả đầu ra, bạn nên sử dụng chuỗi, ngay cả khi nó xuất hiện nhiều lần, thậm chí phân bổ hai đối tượng khác nhau trong bộ nhớ.

Đây là lý do:

  1. Việc in các ký hiệu sẽ chậm hơn in các chuỗi vì chúng được chuyển thành chuỗi.
  2. Có nhiều ký hiệu khác nhau sẽ làm tăng mức sử dụng bộ nhớ tổng thể của ứng dụng của bạn vì chúng không bao giờ được phân bổ. Và bạn sẽ không bao giờ sử dụng tất cả các chuỗi từ mã của mình cùng một lúc.

Trường hợp sử dụng của @AlanDert

@AlanDert: nếu tôi sử dụng nhiều lần một cái gì đó như% input {type:: checkbox} trong mã haml, thì tôi nên sử dụng cái gì làm hộp kiểm?

Me: Vâng.

@AlanDert: Nhưng để in ra một biểu tượng trên trang html, nó phải được chuyển đổi thành chuỗi, phải không? lợi ích của việc sử dụng nó sau đó là gì?

Loại đầu vào là gì? Mã định danh của loại đầu vào bạn muốn sử dụng hoặc thứ gì đó bạn muốn hiển thị cho người dùng?

Đúng là một lúc nào đó nó sẽ trở thành mã HTML, nhưng tại thời điểm bạn đang viết dòng mã đó, nó có nghĩa là một mã định danh - nó xác định loại trường đầu vào bạn cần. Do đó, nó được sử dụng lặp đi lặp lại trong mã của bạn và luôn có cùng một "chuỗi" ký tự làm mã định danh và sẽ không tạo ra rò rỉ bộ nhớ.

Điều đó nói rằng, tại sao chúng ta không đánh giá dữ liệu để xem liệu các chuỗi có nhanh hơn không?

Đây là một điểm chuẩn đơn giản mà tôi đã tạo cho việc này:

require 'benchmark'
require 'haml'

str = Benchmark.measure do
  10_000.times do
    Haml::Engine.new('%input{type: "checkbox"}').render
  end
end.total

sym = Benchmark.measure do
  10_000.times do
    Haml::Engine.new('%input{type: :checkbox}').render
  end
end.total

puts "String: " + str.to_s
puts "Symbol: " + sym.to_s

Ba đầu ra:

# first time
String: 5.14
Symbol: 5.07
#second
String: 5.29
Symbol: 5.050000000000001
#third
String: 4.7700000000000005
Symbol: 4.68

Vì vậy, sử dụng smbols thực sự nhanh hơn một chút so với sử dụng chuỗi. Tại sao vậy? Nó phụ thuộc vào cách HAML được thực hiện. Tôi sẽ cần hack một chút mã HAML để xem, nhưng nếu bạn tiếp tục sử dụng các ký hiệu trong khái niệm định danh, ứng dụng của bạn sẽ nhanh hơn và đáng tin cậy hơn. Khi câu hỏi xuất hiện, hãy đánh giá tiêu chuẩn và nhận câu trả lời của bạn.


@andrewcockerham Liên kết được cung cấp của bạn không hoạt động (Lỗi-404). Bạn phải xóa cuối cùng /(sau strings) khỏi liên kết. Đây là: www.reactive.io/tips/2009/01/11/the-difference-between-ruby-‌ biểu tượng-và-chuỗi
Atul Khanduri

14

Nói một cách đơn giản, một biểu tượng là một cái tên, bao gồm các ký tự, nhưng bất biến. Ngược lại, một chuỗi là một vùng chứa có thứ tự cho các ký tự, mà nội dung của chúng được phép thay đổi.


4
+1. Biểu tượng và Chuỗi là những thứ hoàn toàn khác nhau. Thực sự không có bất kỳ sự nhầm lẫn nào về việc sử dụng cái nào, trừ khi chúng đã được dạy một cách tồi tệ (nghĩa là ngụy biện "một biểu tượng chỉ là một chuỗi bất biến").
Jörg W Mittag

@ JörgWMittag: Chính xác.
Boris Stitnicky

5
bạn có lý, tuy nhiên không trả lời câu hỏi đã được đưa ra. OP là khó hiểu chuỗi với các biểu tượng, nó không phải là đủ cho nó là những thứ khác nhau - bạn nên giúp anh ta để hiểu những gì họ đều giống nhau và trong những gì chúng khác nhau
fotanus

1
@ JörgWMittag có vẻ như đang xảy ra trên khắp các trang web, trừ khi bạn xem xét tài liệu hoặc đủ may mắn để tìm thấy những người quan tâm để giải thích mọi thứ như họ thực sự là.
sargas

8
  1. Biểu tượng Ruby là một đối tượng có so sánh O (1)

Để so sánh hai chuỗi, chúng ta có thể cần phải xem xét mọi ký tự. Đối với hai chuỗi có độ dài N, điều này sẽ yêu cầu N + 1 so sánh (mà các nhà khoa học máy tính gọi là "O (N) time").

def string_comp str1, str2
  return false if str1.length != str2.length
  for i in 0...str1.length
    return false if str1[i] != str2[i]
  end
  return true
end
string_comp "foo", "foo"

Nhưng vì mọi lần xuất hiện của: foo đều đề cập đến cùng một đối tượng, chúng ta có thể so sánh các ký hiệu bằng cách xem ID đối tượng. Chúng ta có thể làm điều này với một phép so sánh duy nhất (mà các nhà khoa học máy tính gọi là "O (1) time").

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. Biểu tượng Ruby là một nhãn trong bảng liệt kê dạng tự do

Trong C ++, chúng ta có thể sử dụng "liệt kê" để biểu diễn họ của các hằng số có liên quan:

enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status  = CLOSED;

Nhưng vì Ruby là một ngôn ngữ động, chúng tôi không lo lắng về việc khai báo loại BugStatus hoặc theo dõi các giá trị pháp lý. Thay vào đó, chúng tôi biểu diễn các giá trị liệt kê dưới dạng ký hiệu:

original_status = :open
current_status  = :closed

3.Một biểu tượng Ruby là một tên không đổi, duy nhất

Trong Ruby, chúng ta có thể thay đổi nội dung của một chuỗi:

"foo"[0] = ?b # "boo"

Nhưng chúng tôi không thể thay đổi nội dung của một biểu tượng:

:foo[0]  = ?b # Raises an error
  1. Biểu tượng Ruby là từ khóa cho đối số từ khóa

Khi chuyển các đối số từ khóa cho một hàm Ruby, chúng tôi chỉ định các từ khóa bằng cách sử dụng các ký hiệu:

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. Biểu tượng Ruby là một lựa chọn tuyệt vời cho khóa băm

Thông thường, chúng tôi sẽ sử dụng các ký hiệu để đại diện cho các khóa của bảng băm:

options = {}
options[:auto_save]     = true
options[:show_comments] = false

5

Dưới đây là một tiêu chuẩn tốt về chuỗi và ký hiệu mà tôi tìm thấy tại codecademy:

require 'benchmark'

string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
symbol_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]

string_time = Benchmark.realtime do
  1000_000.times { string_AZ["r"] }
end

symbol_time = Benchmark.realtime do
  1000_000.times { symbol_AZ[:r] }
end

puts "String time: #{string_time} seconds."
puts "Symbol time: #{symbol_time} seconds."

Đầu ra là:

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.

2
Đừng để ý đến sự thật rằng đây là một phần mười giây.
Casey

Tất cả đều là tương đối. Đôi khi vấn đề thứ trăm.
Yurii

2
Một phần trăm giây trên một triệu lần lặp? Nếu đó là cách tối ưu hóa tốt nhất có sẵn cho bạn thì chương trình của bạn đã được tối ưu hóa khá tốt, tôi nghĩ vậy.
Casey

0
  • sử dụng các ký hiệu làm mã nhận dạng khóa băm

    {key: "value"}

  • biểu tượng cho phép bạn gọi phương thức theo một thứ tự khác

     ghi def (tệp :, data :, mode: "ascii")
          # đã xóa cho ngắn gọn
     kết thúc
     ghi (dữ liệu: 123, tệp: "test.txt")
  • đóng băng để giữ dưới dạng chuỗi và tiết kiệm bộ nhớ

    label = 'My Label'.freeze

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.