使用了 Regexp#match? 的更快的正则表达式Ruby 2.4 为正则表达式新增加了一个新的 #match? 方法,它比 Ruby 2.3 中Regexp的任何一个方法都要快三倍: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | require 'benchmark/ips'
Benchmark.ips do |bench|
EMPTY_STRING = ''
WHITESPACE = " \n\t\n "
CONTAINS_TEXT = ' hi '
PATTERN = /\A[[:space:]]*\z/
bench.report('Regexp#match?') do
PATTERN.match?(EMPTY_STRING)
PATTERN.match?(WHITESPACE)
PATTERN.match?(CONTAINS_TEXT)
end
bench.report('Regexp#match') do
PATTERN.match(EMPTY_STRING)
PATTERN.match(WHITESPACE)
PATTERN.match(CONTAINS_TEXT)
end
bench.report('Regexp#=~') do
PATTERN =~ EMPTY_STRING
PATTERN =~ WHITESPACE
PATTERN =~ CONTAINS_TEXT
end
bench.report('Regexp#===') do
PATTERN === EMPTY_STRING
PATTERN === WHITESPACE
PATTERN === CONTAINS_TEXT
end
bench.compare!
end
|
当你调用 Regexp#===, Regexp#=~, 或者是 Regexp#match 时, Ruby 会使用匹配结果MatchData 来对 $~ 全局变量进行设置: 1 2 3 4 5 6 7 8 | /^foo (\w+)$/ =~ 'foo bar'
$~
/^foo (\w+)$/.match('foo baz')
$~
/^foo (\w+)$/ === 'foo qux'
$~
|
Regexp#match? 会返回一个布尔值并且避免构建一个MatchData对象或者更新全局状态: 1 2 | /^foo (\w+)$/.match?('foo wow')
$~
|
通过直接跳过全局变量的操作,Ruby就能够避免为 MatchData分配内存。 新的用于 Enumerable 的 #sum 方法现在你可以在任意一个 Enumerable 对象上调用 #sum 方法了: 1 | [1, 1, 2, 3, 5, 8, 13, 21].sum
|
#sum 方法有一个默认为 0 的可选参数。这个值是求和计算的起始值,意思是 [].sum 的结果为0。 如果你在一个非整形数组上调用 #sum,那么你就要提供一个初始值才行: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class ShoppingList
attr_reader :items
def initialize(*items)
@items = items
end
def +(other)
ShoppingList.new(*items, *other.items)
end
end
eggs = ShoppingList.new('eggs')
milk = ShoppingList.new('milks')
cheese = ShoppingList.new('cheese')
eggs + milk + cheese
[eggs, milk, cheese].sum
[eggs, milk, cheese].sum(ShoppingList.new)
|
在代码的最后一行,一个空的购物清单(ShoppingList.new)被提供出来作为初始值了。 一个用于检查目录或者文件是否为空的新方法在 Ruby 2.4 中你可以使用 File 或者 Dir 模块来检查目录护着文件是否为空: 1 2 3 4 5 | Dir.empty?('empty_directory')
Dir.empty?('directory_with_files')
File.empty?('contains_text.txt')
File.empty?('empty.txt')
|
File.empty? 方法等同于 File.zero? 现在它在所有维护的 Ruby 版本中都已经是可用的了: 1 2 | File.zero?('contains_text.txt')
File.zero?('empty.txt')
|
不幸的是这些还不能用于 Pathname。
从Regexp匹配结果中提取被命名的匹配值
在 Ruby 2.4 中你可以在一个Regexp匹配结果上调用 #named_captures 来得到一个包含了你所命名的匹配分组以及它们所对应值的哈希表: 1 2 | pattern = /(?<first_name>John) (?<last_name>\w+)/
pattern.match('John Backus').named_captures
|
Ruby 2.4 还增加了一个 #values_at 方法用来提取你关心的被命名的匹配值: 1 2 | pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
pattern.match('2016-02-01').values_at(:year, :month)
|
#values_at 方法对于基于位置的匹配分组也能用: 1 2 | pattern = /(\d{4})-(\d{2})-(\d{2})$/
pattern.match('2016-07-18').values_at(1, 3)
|
新的 Integer#digits 方法如果你想要访问一个整型数中特定位置(从右至左)上的数字,那就可以使用 Integer#digits: 1 2 3 4 5 | 123.digits
123.digits[0]
123.to_s.chars.map(&:to_i).reverse
|
如果给定一个非十进制的数你想要知道它的基于位置的数字信息,可以传入一个不同的 基数。例如,要查看一个十六进制数字的基于位置的数字信息,你可以传入16: 1 2 | 0x7b.digits(16)
0x7b.digits(16).map { |digit| digit.to_s(16) }
|
对 Logger 接口的提升在 Ruby 2.3 中 Logger 库设置起来会有一点点麻烦: 1 2 3 4 5 6 7 8 | logger1 = Logger.new(STDOUT)
logger1.level = :info
logger1.progname = 'LOG1'
logger1.debug('This is ignored')
logger1.info('This is logged')
|
Ruby 2.4 将这一配置挪到了 Logger 的构造器中: 1 2 3 4 5 6 | logger2 = Logger.new(STDOUT, level: :info, progname: 'LOG2')
logger2.debug('This is ignored')
logger2.info('This is logged')
|
将 CLI 选项解析成了一个哈希表使用 OptionParser解析命令行标识常常涉及到许多要将选项向下解析成哈希表的套路化的东西: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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关键词参数来提供一个哈希表了: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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
config[:from]
config[:url]
config[:names]
|
更快的 Array#min 和 Array#max
在 Ruby 2.4 中 Array 类定义了它自己的 #min和#max实例方法。这一修改戏剧性地提升了Array上 #min 和 #max 方法的运行速度: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | require 'benchmark/ips'
Benchmark.ips do |bench|
NUMS = 1_000_000.times.map { rand }
ENUM_MIN = Enumerable.instance_method(:min).bind(NUMS)
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
|
对整型数进行了简化到 Ruby 2.4 为止你还得管理许多的数字类型: 1 2 3 4 5 6 7 8 | numerics = ObjectSpace.each_object(Module).select { |mod| mod < Numeric }
numerics
numerics
|
现在像 Fixnum 以及 Bignum 这样实现细节 Ruby 都能为你进行管理。这应该可以帮助你避免掉像下面这样的有点微妙的BUG: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def categorize_number(num)
case num
when Fixnum then 'fixed number!'
when Float then 'floating point!'
end
end
categorize_number(2)
categorize_number(2.0)
categorize_number(2 ** 500)
categorize_number(2)
categorize_number(2.0)
categorize_number(2 ** 500)
|
如果你在源代码中硬编码了 Bignum 或者 Fixnum,也没关系。这些常量现在会指向 Integer: 用于浮点数修改器的新的参数支持#round, #ceil, #floor, 和 #truncate 现在可以接受一个精度参数了 1 2 3 4 | 4.55.ceil(1)
4.55.floor(1)
4.55.truncate(1)
4.55.round(1)
|
这些方法在Integer上运行的效果也都是一样的: 1 2 3 4 | 4.ceil(1)
4.floor(1)
4.truncate(1)
4.round(1)
|
对于unicode字符大小写敏感看看下面这个句子: 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字符了: 1 2 3 4 5 | sentence = "\uff2a-\u039f-\uff28-\uff2e"
sentence
sentence.downcase
sentence.downcase.capitalize
sentence.downcase.capitalize.swapcase
|
一个用来指定新字符串大小的新选项
当要创建一个字符串时,你现在可以声明一个:capacity选项来告诉Ruby它应该为你的字符串分配多少内存了。这样对性能的提升有所帮助,因为在你使字符串变大时可以避免Ruby重新分配内存: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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
|
对针对符号的匹配行为进行了固定化尽管 String#match会返回 MatchData,但 Ruby 2.3 的 Symbol#match 会返回匹配的位置。这样的不一致在Ruby 2.4中进行了已经被修复: 1 2 3 4 5 6 7 8 9 | 'foo bar'.match(/^foo (\w+)$/)
:'foo bar'.match(/^foo (\w+)$/)
'foo bar'.match(/^foo (\w+)$/)
:'foo bar'.match(/^foo (\w+)$/)
|
条件表达式中的多重赋值现在你可以在一个条件表达式中对多个变量进行赋值了: 1 2 3 4 5 6 7 8 9 10 11 | branch1 =
if (foo, bar = %w[foo bar])
'truthy'
else
'falsey'
endbranch2 =
if (foo, bar = nil)
'truthy'
else
'falsey'
endbranch1
|
尽管你可能并不应该做那种事情。 针对线程的异常报告方面的改进如果在一个线程中遇到了异常,那么 Ruby 默认会悄悄地吞下那个错误: 1 2 3 4 5 6 7 8 9 10 11 12 | puts 'Starting some parallel work'
thread =
Thread.new do
sleep 1
fail 'something very bad happened!'
end
sleep 2
puts 'Done!'
|
1 2 3 | $ ruby parallel-work.rb
Starting some parallel work
Done!
|
如果你想要在当一个线程中异常发生时让整个进程都失败,那你就可以使用 Thread.abort_on_exception = true。在上面的 parallel-work.rb 中加上这个会改变程序的输出: 1 2 3 | $ 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!
|