String and Symbol in Ruby

Ruby có Symbol cũng như String. Symbol trong Ruby là một khái niệm khá thú vị và được sử dụng rất nhiều. Lập trình viên chúng ta chắc hẳn đã qúa quen với symbol hay string khi sử dụng trong các task công việc. Nhưng có khi nào, ta tự hỏi symbolstring nó khác nhau như nào? Tại sao khi thì dùng symbol, lúc lại sử dụng string. Và dùng chúng trong trường hợp nào là tốt nhất? Ở bài viết này chúng ta cùng đi tìm hiểu về sự khác nhau giữa symbolstring để sử dụng đúng hơn trong khi coding. 😄

What is Symbol?

Symbol là một khái niệm hơi khó hiểu với những người mới bắt đầu với Ruby on Rails hay thậm chí trong cả ngôn ngữ lập trình khác. Có người cho rằng nó chỉ là một biến, hoặc chỉ là một cái tên, nhưng symbol lại không hề đơn giản như vậy. Chúng ta có thể hiểu symbol như là an object with a name.

Difference between Ruby String and Symbol:

1. String is mutable, but Symbol isn’t.

symbol giống với string ở chỗ, symbol cũng có một số method như là: lengthupcasedowncase... Tuy nhiên, chúng lại khác nhau ở chỗ string có thể thay đổi được - tức là chúng ta có thể thay đổi nội dung của một chuỗi nếu muốn, nhưng với symbol điều đó là không thể.

# just like string, you can use length method to count the letters.
>> :hello.length
=> 5

# or upcase method to return a symbol with all capital letters.
>> :hello.upcase
=> :HELLO

# let's say we have a "hello" string,
# we can use brackets with index number to get the letter.
>> "hello"[0]
=> "h"
>> "hello"[3]
=> "l"

# and so can symbol
>> :hello[0]
=> "h"
>> :hello[3]
=> "l"

# we can also use brackets with index number to change the letter
>> "hello"[0] = "k"
=> "k"

# but it doesn't work on symbol. because symbol doesn't have the []= method
>> :hello[0] = "k"
NoMethodError: undefined method `[]=' for :hello:Symbol'`

Vì vậy, bạn cũng có thể nghĩ rằng symbol như một loại immutable string(chuỗi bất biến).

2. Symbol has better performance

Trong Ruby, khi bạn tạo một chuỗi mới, nó sẽ yêu cầu Ruby phân bổ bộ nhớ mới cho nó, như sau:

5.times do
   puts "hello".object_id
end
19661060
19660980
19660920
19660860
19488720
 => 5 

Phương thức object_id sẽ trả về số thứ tự duy nhất trong Ruby, nó sẽ thay đổi theo máy tính khác nhau hoặc phiên bản Ruby.

Trong thế giới Ruby, cùng một đối tượng sẽ có object id giống nhau, và các đối tượng có cùng một object id có nghĩa là chúng cùng một đối tượng.

Và bạn có thể thấy ví dụ trên, chuỗi "hello" có object id khác nhau và chiếm một số không gian bộ nhớ, có nghĩa là chúng là 5 đối tượng khác nhau trong Ruby.

Và, khi dùng symbol, kết qủa ta nhận được lại như sau:

5.times do
  puts :hello.object_id
end
1098268
1098268
1098268
1098268
1098268
 => 5 

Kết quả cho thấy những đối tượng trên có cùng một object id, nghĩa là chúng đang cùng một đối tượng. Khi sử dụng symbol :hello lần đầu tiên, Ruby sẽ cấp phát bộ nhớ và tạo ra symbol này cho bạn, khi bạn cố gắng truy cập vào đó một lần nữa, Ruby sẽ lấy lại nó từ bộ nhớ thay vì tạo ra mới, vì vậy symbol sẽ ít sử dụng bộ nhớ hơn. Và điều này giảm được việc lãng phí bộ nhớ.

Mặc dù symbol tiết kiệm bộ nhớ, nhưng những phiên bản Ruby <= 2.2, bộ nhớ không thể được tái sử dụng tự động, bạn có thể phải khởi động lại ứng dụng để giải phóng bộ nhớ đó, do đó có thể gây rò rỉ bộ nhớ nếu bạn tạo nhiều symbol. Từ ruby 2.2 trở đi, cơ chế Symbol GC (Garbage Collection) đã được giới thiệu, những ký hiệu đã được tạo ra bởi to_sym hoặc các phương pháp thực hiện có thể được tái sử dụng giống như các đối tượng khác.

  • Ngoài ra, ở ví dụ trên, nếu ta sử dụng freeze cho string, chúng ta cũng sẽ nhận được một object id duy nhất.

3. Comparsion of symbols is faster than strings

Ta có ví dụ như sau:

require 'benchmark'
loop_times = 100000000

str = Benchmark.measure do
  loop_times.times do
    "hello" == "hello"
  end
end.total

sym = Benchmark.measure do
  loop_times.times do
    :hello == :hello
  end
end.total

puts "Benchmark"
puts "String: #{str}"
puts "Symbol: #{sym}"

# => Benchmark
# => String: 29.450000000000003
# => Symbol: 7.889999999999999

Như ta thấy, so sánh giữa các symbol nhanh hơn nhiều so với chuỗi, đó là bởi vì các symbol chỉ so sánh nếu chúng là cùng một đối tượng (có id đối tượng giống nhau).

String and Symbol are convertable

Class StringSymbol có support một số method để covert lẫn nhau 😄

# to_sym method can convert string to symbol
>> "name".to_sym
=> :name

# or intern method, it's identical with to_sym method
>> "name".intern
=> :name

# you can also use literal notation %s
>> %s(name)
=> :name

# to_s can convert symbol to string
>> :name.to_s
=> "name"

# id2name method do the same thing with to_s
>> :name.id2name
=> "name"

When should use symbol?

1. Symbol as the key of Hash

 profile = { name: "eddie", age: 18 }
=> {:name=>"eddie", :age=>18}

Bởi vì không symbol là chuỗi bất biến, không thay đổi và việc tra cứu, cũng như so sánh hiệu suất vốn nhanh hơn so với string nên nó rất thích hợp để làm keys của Hash.

2. String has more powerful and useful methods than Symbol

Mặc dù bạn có thể convert symbol sang string, nhưng sau khi tất cả các symbol không phải là string nên nó sẽ không có các phương pháp nhiều như string. Do đó, nếu bạn muốn sử dụng những phương pháp hữu ích của lớp String, thì nên chọn String.

3. Use String or Symbol as parameters?

Ví dụ:

class Cat
  attr_accessor :name
end

kitty = Cat.new
kitty.name = "Nancy"
puts kitty.name       # => Nancy

Ở ví dụ trên, ta có thể thay thế attr_accessor :name bằng attr_accessor "name" đều được. Một số method thì dùng symbol như parameters, một số khác lại sử dụng string. Và đôi khi, có những method đều có thể sử dụng cả hai.

Thanks for your reading!


All Rights Reserved