Ruby 2.4 为正则表达式新增加了一个新的 #match? 方法,它比 Ruby 2.3 中Regexp的任何一个方法都要 快三倍 :
Regexp#match?: 2630002.5 i/s Regexp#===: 872217.5 i/s - 3.02x slower Regexp#=~: 859713.0 i/s - 3.06x slower Regexp#match: 539361.3 i/s - 4.88x slower
扩展基准测试源代码
当你调用 Regexp#===, Regexp#=~, 或者是 Regexp#match 时, Ruby 会使用匹配结果 MatchData 来对 $~ 全局变量进行设置:
/^foo (/w+)$/ =~ 'foo bar' # => 0 $~ # => #<MatchData "foo bar" 1:"bar"> /^foo (/w+)$/.match('foo baz') # => #<MatchData "foo baz" 1:"baz"> $~ # => #<MatchData "foo baz" 1:"baz"> /^foo (/w+)$/ === 'foo qux' # => true $~ # => #<MatchData "foo qux" 1:"qux">
Regexp#match? 会返回一个布尔值并且避免构建一个MatchData对象或者更新全局状态:
/^foo (/w+)$/.match?('foo wow') # => true $~ # => nil
通过直接跳过全局变量的操作,Ruby就能够避免为 MatchData分配内存。
现在你可以在任意一个 Enumerable 对象上调用 #sum 方法了:
[1, 1, 2, 3, 5, 8, 13, 21].sum # => 54
#sum 方法有一个默认为 0 的可选参数。这个值是求和计算的起始值,意思是 [].sum 的结果为0。
如果你在一个非整形数组上调用 #sum,那么你就要提供一个初始值才行:
class ShoppingList attr_reader :items def initialize(*items) @items = items end def +(other) ShoppingList.new(*items, *other.items) end end eggs = ShoppingList.new('eggs') # => #<ShoppingList:0x007f952282e7b8 @items=["eggs"]> milk = ShoppingList.new('milks') # => #<ShoppingList:0x007f952282ce68 @items=["milks"]> cheese = ShoppingList.new('cheese') # => #<ShoppingList:0x007f95228271e8 @items=["cheese"]> eggs + milk + cheese # => #<ShoppingList:0x007f95228261d0 @items=["eggs", "milks", "cheese"]> [eggs, milk, cheese].sum # => #<TypeError: ShoppingList can't be coerced into Integer> [eggs, milk, cheese].sum(ShoppingList.new) # => #<ShoppingList:0x007f9522824cb8 @items=["eggs", "mi
在代码的最后一行,一个空的购物清单(ShoppingList.new)被提供出来作为初始值了。
在 Ruby 2.4 中你可以使用 File 或者 Dir 模块来检查目录护着文件是否为空:
Dir.empty?('empty_directory') # => true Dir.empty?('directory_with_files') # => false File.empty?('contains_text.txt') # => false File.empty?('empty.txt') # => true
File.empty? 方法等同于 File.zero? 现在它在所有维护的 Ruby 版本中都已经是可用的了:
File.zero?('contains_text.txt') # => false File.zero?('empty.txt') # => true
不幸的是这些还不能用于 Pathname。
在 Ruby 2.4 中你可以在一个Regexp匹配结果上调用 #named_captures 来得到一个包含了你所命名的匹配分组以及它们所对应值的哈希表:
pattern = /(?<first_name>John) (?<last_name>/w+)/ pattern.match('John Backus').named_captures # => { "first_name" => "John", "last_name" => "Backus" }
Ruby 2.4 还增加了一个 #values_at 方法用来提取你关心的被命名的匹配值:
pattern = /(?<year>/d{4})-(?<month>/d{2})-(?<day>/d{2})/ pattern.match('2016-02-01').values_at(:year, :month) # => ["2016", "02"]
#values_at 方法对于基于位置的匹配分组也能用:
pattern = /(/d{4})-(/d{2})-(/d{2})$/ pattern.match('2016-07-18').values_at(1, 3) # => ["2016", "18"]
如果你想要访问一个整型数中特定位置(从右至左)上的数字,那就可以使用 Integer#digits:
123.digits # => [3, 2, 1] 123.digits[0] # => 3 # Equivalent behavior in Ruby 2.3: 123.to_s.chars.map(&:to_i).reverse # => [3, 2, 1]
如果给定一个非十进制的数你想要知道它的基于位置的 数字 信息,可以传入一个不同的 基数 。例如,要查看一个十六进制数字的基于位置的数字信息,你可以传入16:
0x7b.digits(16) # => [11, 7] 0x7b.digits(16).map { |digit| digit.to_s(16) } # => ["b", "7"]
在 Ruby 2.3 中 Logger 库设置起来会有一点点麻烦:
logger1 = Logger.new(STDOUT) logger1.level = :info logger1.progname = 'LOG1' logger1.debug('This is ignored') logger1.info('This is logged') # >> I, [2016-07-17T23:45:30.571508 #19837] INFO -- LOG1: This is logged
Ruby 2.4 将这一配置挪到了 Logger 的构造器中:
logger2 = Logger.new(STDOUT, level: :info, progname: 'LOG2') logger2.debug('This is ignored') logger2.info('This is logged') # >> I, [2016-07-17T23:45:30.571556 #19837] INFO -- LOG2: This is logged
使用 OptionParser解析命令行标识常常涉及到许多要将选项向下解析成哈希表的套路化的东西:
require 'optparse'require 'optparse/date'require 'optparse/uri'config = {}cli = OptionParser.new do |options| options.define('--from=DATE', Date) do |from| config[:from] = from end options.define('--url=ENDPOINT', URI) do |url| config[:url] = url end options.define('--names=LIST', Array) do |names| config[:names] = names end end
现在你可以通过在对参数进行解析时的:into关键词参数来提供一个哈希表了:
require 'optparse' require 'optparse/date' require 'optparse/uri' cli = OptionParser.new do |options| options.define '--from=DATE', Date options.define '--url=ENDPOINT', URI options.define '--names=LIST', Array end config = {} args = %w[ --from 2016-02-03 --url https://blog.blockscore.com/ --names John,Daniel,Delmer ] cli.parse(args, into: config) config.keys # => [:from, :url, :names] config[:from] # => #<Date: 2016-02-03 ((2457422j,0s,0n),+0s,2299161j)> config[:url] # => #<URI::HTTPS https://blog.blockscore.com/> config[:names] # => ["John", "Daniel", "Delmer"]
在 Ruby 2.4 中 Array 类定义了它自己的 #min和#max实例方法。这一修改戏剧性地提升了Array上 #min 和 #max 方法的运行速度:
require 'benchmark/ips' Benchmark.ips do |bench| NUMS = 1_000_000.times.map { rand } # By binding the Enumerable method to our array # we can bench the previous speed in Ruby 2.3 ENUM_MIN = Enumerable.instance_method(:min).bind(NUMS) # Bind the `#min` method to our test array also # so our benchmark code is as similar as possible ARRAY_MIN = Array.instance_method(:min).bind(NUMS) bench.report('Array#min') do ARRAY_MIN.call end bench.report('Enumerable#min') do ENUM_MIN.call end bench.compare! end # >> Warming up -------------------------------------- # >> Array#min 3.000 i/100ms # >> Enumerable#min 2.000 i/100ms # >> Calculating ------------------------------------- # >> Array#min 35.147 (± 2.8%) i/s - 177.000 in 5.039133s # >> Enumerable#min 21.839 (± 4.6%) i/s - 110.000 in 5.040531s # >> Comparison: # >> Array#min: 35.1 i/s # >> Enumerable#min: 21.8 i/s - 1.61x slower
到 Ruby 2.4 为止你还得管理许多的数字类型:
# Find classes which subclass the base "Numeric" class: numerics = ObjectSpace.each_object(Module).select { |mod| mod < Numeric } # In Ruby 2.3: numerics # => [Complex, Rational, Bignum, Float, Fixnum, Integer, BigDecimal] # In Ruby 2.4: numerics # => [Complex, Rational, Float, Integer, BigDecimal]
现在像 Fixnum 以及 Bignum 这样实现细节 Ruby 都能为你进行管理。这应该可以帮助你避免掉像下面这样的有点微妙的BUG:
def categorize_number(num) case num when Fixnum then 'fixed number!' when Float then 'floating point!' end end # In Ruby 2.3: categorize_number(2) # => "fixed number!" categorize_number(2.0) # => "floating point!" categorize_number(2 ** 500) # => nil # In Ruby 2.4: categorize_number(2) # => "fixed number!" categorize_number(2.0) # => "floating point!" categorize_number(2 ** 500) # => "fixed number!"
如果你在源代码中硬编码了 Bignum 或者 Fixnum,也没关系。这些常量现在会指向 Integer:
Fixnum # => Integer Bignum # => Integer Integer # => Integer
#round, #ceil, #floor, 和 #truncate 现在可以接受一个精度参数了
4.55.ceil(1) # => 4.6 4.55.floor(1) # => 4.5 4.55.truncate(1) # => 4.5 4.55.round(1) # => 4.6
这些方法在Integer上运行的效果也都是一样的:
4.ceil(1) # => 4.0 4.floor(1) # => 4.0 4.truncate(1) # => 4.0 4.round(1) # => 4.0
看看下面这个句子:
My name is JOHN. That is spelled J-Ο-H-N
在Ruby 2.3中,在这个字符串上面调用 #downcase 方法,输出如下:
my name is john. that is spelled J-Ο-H-N
这是因为这个字符串中的“J-Ο-H-N”是使用的unicode字符。
Ruby 的字母大小写方法现在能正常处理unicode字符了:
sentence = "/uff2a-/u039f-/uff28-/uff2e" sentence # => "J-Ο-H-N" sentence.downcase # => "j-ο-h-n" sentence.downcase.capitalize # => "J-ο-h-n" sentence.downcase.capitalize.swapcase # => "j-Ο-H-N"
当要创建一个字符串时,你现在可以声明一个:capacity选项来告诉Ruby它应该为你的字符串分配多少内存了。这样对性能的提升有所帮助,因为在你使字符串变大时可以避免Ruby重新分配内存:
require 'benchmark/ips' Benchmark.ips do |bench| bench.report("Without capacity") do append_me = ' ' * 1_000 template = String.new 100.times { template << append_me } end bench.report("With capacity") do append_me = ' ' * 1_000 template = String.new(capacity: 100_000) 100.times { template << append_me } end bench.compare! end # >> Warming up -------------------------------------- # >> Without capacity 1.690k i/100ms # >> With capacity 3.204k i/100ms # >> Calculating ------------------------------------- # >> Without capacity 16.031k (± 7.4%) i/s - 160.550k in 10.070740s # >> With capacity 37.225k (±18.0%) i/s - 362.052k in 10.005530s # >> # >> Comparison: # >> With capacity: 37225.1 i/s # >> Without capacity: 16031.3 i/s - 2.32x slower
尽管 String#match会返回 MatchData,但 Ruby 2.3 的 Symbol#match 会返回匹配的位置。这样的不一致在Ruby 2.4中进行了已经被修复:
# Ruby 2.3 behavior: 'foo bar'.match(/^foo (/w+)$/) # => #<MatchData "foo bar" 1:"bar"> :'foo bar'.match(/^foo (/w+)$/) # => 0 # Ruby 2.4 behavior: 'foo bar'.match(/^foo (/w+)$/) # => #<MatchData "foo bar" 1:"bar"> :'foo bar'.match(/^foo (/w+)$/) # => #<MatchData "foo bar" 1:"bar">
现在你可以在一个条件表达式中对多个变量进行赋值了:
branch1 = if (foo, bar = %w[foo bar]) 'truthy' else 'falsey' endbranch2 = if (foo, bar = nil) 'truthy' else 'falsey' endbranch1 # => "truthy"branch2 # => "falsey"
尽管你可能并不应该做那种事情。
如果在一个线程中遇到了异常,那么 Ruby 默认会悄悄地吞下那个错误:
puts 'Starting some parallel work' thread = Thread.new do sleep 1 fail 'something very bad happened!' end sleep 2 puts 'Done!'
$ ruby parallel-work.rb Starting some parallel work Done!
如果你想要在当一个线程中异常发生时让整个进程都失败,那你就可以使用 Thread.abort_on_exception = true。在上面的 parallel-work.rb 中加上这个会改变程序的输出:
$ ruby parallel-work.rb Starting some parallel work parallel-work.rb:9:in 'block in <main>': something very bad happened! (RuntimeError)
现在在 Ruby 2.4 中你有了一个位于错误被悄悄忽略和终止整个程序之间的中间位置。不使用 abort_on_exception,你可以设置 Thread.report_on_exception = true:
$ ruby parallel-work.rb Starting some parallel work #<Thread:0x007ffa628a62b8@parallel-work.rb:6 run> terminated with exception: parallel-work.rb:9:in 'block in <main>': something very bad happened! (RuntimeError) Done!