Ruby 2.1 详情
2013 年圣诞节发布的 Ruby 2.1 是 Ruby 的下一个重要版本,仅与2.0版本的发布有10个月的间隔。该版本包含了大量的更新和提升,这篇文章就来揭秘新特性的具体细节。
新的版本控制策略
Ruby2.1改为了基于语义化版本控制 版本控制方案。
具体方案是MAJOR.MINOR.TEENY, 因此2.1.0中,主版本号是2, 次版本号是1,以及微版本号是0. 微版本号代表小Bug和安全补丁的修正程度。次版本号代表向后兼容的新特性,主版本号则是无法发布为次版本号的非兼容的更新。
这就意味着而不是说,曾经1.9.3大更新1.9.3-p545小更新变成了2.1大更新2.1.1小更新了。
计划每隔12个月释放一个次版本更新,因此2014年圣诞节我们应该可以看到Ruby2.2了。
必须的关键字参数
Ruby 2.0.0中引入的 关键字参数 在 2.1中加了点小小的改进. 必须的关键字参数允许你在方法定义时删除关键字参数的默认值, 并且在方法被调用时,如果没有提供确切值则跑出异常。
# length is required def pad(num, length:, char: "0") num.to_s.rjust(length, char) end pad(42, length: 6) #=> "000042" pad(42) #=> #<ArgumentError: missing keyword: length>
像上面展示的例子中,我们可以看到关键字参数可以帮助我们消除哪个参数是哪个的歧义, 但是默认值并不总是必须的. 现在我们不必总是加默认值了。
字符串#freeze优化
Ruby中字符串是易变的,任何字符串文字在每次调用他们时都将产生一个新的字符串,例如
def env "development" end # returns new String object on each call env.object_id #=> 70329318373020 env.object_id #=> 70329318372900
这是十分浪费的,创建并垃圾回收了许多的对象。为了让你能够避免这种情况,在字符串文字上直接调用#freeze,这将导致在冻结字符串表中查找字符串。这就意味着同样的字符串将被再次使用。
def env "development".freeze end # returns the same String object on each call env.object_id #=> 70365553080120 env.object_id #=> 70365553080120
在Hash表中字符串文字作为键时,将被同样对待,但不需要调用#freeze
a = {"name" => "Arthur"} b = {"name" => "Ford"} # same String object used as key in both hashes a.keys.first.object_id #=> 70253124073040 b.keys.first.object_id #=> 70253124073040
在2.1开发期间,这个功能开始是作为一个附加语法,用"string"f导致一个冻结字符串。最终决定切换到在文字上调用#freeze的特定技术,这可以让代码向前和向后兼容,另外主观上很多人不喜欢新的语法。
def 返回方法的名字作为标志符
定义一个方法的结果不在是nil, 取而代之的是方法名字的标识符。一个规范的例子是使得一个方法称为私有的。
class Client def initialize(host, port) # ... end private def do_request(method, path, body, **headers) # ... end def get(path, **headers) do_request(:get, path, nil, **headers) end end
同样它也提供了更好的添加方法修饰符的方式, 下面是一个使用 Module#prepend 来包装一个方法完成前/后调用。
module Around def around(method) prepend(Module.new do define_method(method) do |*args, &block| send(:"before_#{method}") if respond_to?(:"before_#{method}", true) result = super(*args, &block) send(:"after_#{method}") if respond_to?(:"after_#{method}", true) result end end) method end end class Example extend Around around def call puts "call" end def before_call puts "before" end def after_call puts "after" end end Example.new.call
输出
before call after
define_method和define_singleton_method方法也被更改为返回标识符而不是他们的参数。
有理数和复数字面量
我们已经有了整数(1) 和浮点数(1.0) 字面量, 现在我们也有有理数(1r)和复数(1i)字面量了。
他们配合Ruby的类型转换机制可以轻松搞定数学操作,好比,一个数学上的有理数1/3可以在Ruby中写作1/3r。3i会生成复数0+3i。这意味着复数可以写成标准的标准的数学符号, 2+3i 生成复数2+3i!
数组/枚举 #to_h
Ruby 2.0.0中许多类都有一个#to_h方法,现在数组和任何其他包含枚举的类也都有#to_h方法了。
[[:id, 42], [:name, "Arthur"]].to_h #=> {:id=>42, :name=>"Arthur"} require "set" Set[[:id, 42], [:name, "Arthur"]].to_h #=> {:id=>42, :name=>"Arthur"}
这将在所有Hash上返回数组的枚举方法中派上用场。
headers = {"Content-Length" => 42, "Content-Type" => "text/html"} headers.map {|k, v| [k.downcase, v]}.to_h #=> {"content_length" => 42, "content_type" => "text/html"}
细粒度方法缓存
Ruby2.1之前使用一个全局方法缓存,当代码中任何一个地方定义一个方法,模块引入,模块对象拓展时,这一全局方法缓存都会失效。这使得一些类--如OpenStruct -- 以及一些技术 -- 如exception tagging -- 出于性能原因而不可用。
现在不会有这个问题了, Ruby 2.1 使用基于类层次的方法缓存, 只有讨论中的类和任意子类才会失效缓存。
一个已经加入到 RubyVM 类的方法会返回一些方法缓存状态的调试信息。
class Foo end RubyVM.stat #=> {:global_method_state=>133, :global_constant_state=>820, :class_serial=>5689} # setting constant increments :global_constant_state Foo::Bar = "bar" RubyVM.stat(:global_constant_state) #=> 821 # defining instance method increments :class_serial class Foo def foo end end RubyVM.stat(:class_serial) #=> 5690 # defining global method increments :global_method_state def foo end RubyVM.stat(:global_method_state) #=> 134
异常
现在异常有一个#cause方法,会返回造成的异常。当你从一个异常恢复并引发其他异常时,这个异常会被自动设置。
require "socket" module MyProject Error = Class.new(StandardError) NotFoundError = Class.new(Error) ConnectionError = Class.new(Error) def self.get(path) response = do_get(path) raise NotFoundError, "#{path} not found" if response.code == "404" response.body rescue Errno::ECONNREFUSED, SocketError => e raise ConnectionError end end begin MyProject.get("/example") rescue MyProject::Error => e e #=> #<MyProject::ConnectionError: MyProject::ConnectionError> e.cause #=> #<Errno::ECONNREFUSED: Connection refused - connect(2) for "example.com" port 80> end
目前引发的错误不会输出到任何地方,并且rescue不会关注原因。但是只有当调试时,自动设置异常原因才有些帮助。
Exceptions 也添加了#backtrace_locations方法 不知为啥2.0.0奇怪的消失了. 他返回Thread::Backtrace::Location 对象,而不是字符串,这更加方便访问调用栈信息了。
分代垃圾回收
Ruby2.1引入了分代垃圾回收器,将所有的对象分到青年代和老年代。在标记阶段,只会对青年代运行一次常规的垃圾回收,因为较老的对象被标记的频率更低。1.9.3引入的延迟清理系统会执行清理工作。一个对象会被放入老年代如果它活过一次青年代垃圾回收。
如果老年代中有对象引用青年代中的对象, 但是你仅仅看到青年代似乎没有引用其他对象,并且你可能回收了一个正在使用的对象。写屏障可以通过对引用青年代对象的老年代对象(像old_arry.push(young_string))添加“记忆位”避免这一行为。当青年代标记阶段会考虑进这个“记忆位”。
大部分的分代垃圾回收器需要在所有对象上加上写屏障, 但是随着许多用于Ruby的第三方C拓展的出现使得这变得不可能, 因此一个变通方案是那些没有写屏障保护的对象(“阴暗”对象)将不会被放入老年代中。 这样并不理想,因为你不能体会到分代回收器的所有好处, 但是它最大化了向后兼容。
然而标记阶段是目前写屏障的最大开销,并且你的代码决定了你的性能。
垃圾回收
GC.start方法有两个新的关键字参数,full_mark和immediate_sweep. 两个默认都是true。
full_mark如果设为true,则青年代和老年代都会被标记,否则只标记青年代。 immediate_sweep如果设为true,一次‘stop the world’的清理将会被执行,否则将会执行延迟清理, 直到被要求清理时,并且以最低要求进行清理。
GC.start # trigger a full GC run GC.start(full_mark: false) # only collect young generation GC.start(immediate_sweep: false) # mark only GC.start(full_mark: false, immediate_sweep: false) # minor GC
GC.stress调试选项现在可以设置一个integer的标志,来控制强调垃圾回收器的哪一部分。
GC.stress = true # full GC at every opportunity GC.stress = 1 # minor marking at every opportunity GC.stress = 2 # lazy sweep at every opportunity
GC.stat的输出已经被更新为包括更多细节,以及方法本身现在接受一个关键字参数来返回该关键字对应的值,而不是构建并返回完整的散列。
GC.stat #=> {:count=>6, ... } GC.stat(:major_gc_count) #=> 2 GC.stat(:minor_gc_count) #=> 4
GC 也添加了一个latest_gc_info方法,来返回最近垃圾回收的相关信息。
GC.latest_gc_info #=> {:major_by=>:oldgen, :gc_by=>:newobj, :have_finalizer=>false, :immediate_sweep=>false}
GC 调整环境变量
现在,当Ruby程序运行起来之后,它会去留意全部的新产生的环境变量,这可以用于调整垃圾回收器的行为。
RUBY_GC_HEAP_INIT_SLOTS
之前叫 RUBY_HEAP_MIN_SLOTS。用来设置初始分配的大小,默认值是 10000.
RUBY_GC_HEAP_FREE_SLOTS
之前叫 RUBY_FREE_MIN。用来设置在垃圾之后可用空间(slots)的最小值。如果垃圾回收之后没有空出最够多的空间(slots),会分配新的空间(slots)。默认值是 4096。
RUBY_GC_HEAP_GROWTH_FACTOR
用给定的因数来增长分配的slots的数量。 (下一个的slots的数量) = (当前slots的数量) * (该因数)。默认值是1.8。
RUBY_GC_HEAP_GROWTH_MAX_SLOTS
在一次分配中所允许分配slots的最大值。默认值是0,意思是没有限制。
RUBY_GC_MALLOC_LIMIT
这个并不是新添加的,但是值得说一下。它是在不触发垃圾回收的情况下所允许分配内存的数量。默认值是16 * 1024 * 1024 (16MB)。
RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR
malloc_limit 增长的速率。默认值是1.4。
RUBY_GC_MALLOC_LIMIT_MAX
malloc_limit所能达到的最大值。默认值是 32 * 1024 * 1024 (32MB).
RUBY_GC_OLDMALLOC_LIMIT
在触发一次全面的垃圾回收之前,老一代可以增加的量。默认值是16 * 1024 * 1024 (16MB).
RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR
old_malloc_limit 增长的速率,默认值是1.2。
RUBY_GC_OLDMALLOC_LIMIT_MAX
old_malloc_limit所能达到的最大值。默认值是 128 * 1024 * 1024 (128MB).
使用ObjectSpace工具跟踪内存泄露
当你引用旧的或者大的对象时,Ruby 2.1提供了很多工具帮助你跟踪它们,不让垃圾回收器认领它们。
我们现在得到了方法的集合来跟踪对象的分配并报告它们。
require "objspace" module Example class User def initialize(first_name, last_name) @first_name, @last_name = first_name, last_name end def name "#{@first_name} #{@last_name}" end end end ObjectSpace.trace_object_allocations do obj = Example::User.new("Arthur", "Dent").name ObjectSpace.allocation_sourcefile(obj) #=> "example.rb" ObjectSpace.allocation_sourceline(obj) #=> 10 ObjectSpace.allocation_class_path(obj) #=> "Example::User" ObjectSpace.allocation_method_id(obj) #=> :name ObjectSpace.allocation_generation(obj) #=> 6 end
allocation_generation 返回的数字是对象被创建后执行垃圾回收的次数。因此如果这是个很小的数字,那么对象是在应用程序的生命周期的早期创建的。
还有trace_object_allocations_start和trace_object_allocations_stop替代trace_object_allocations块,trace_object_allocations_clear清除已记录的分配数据
此外,它可能输出更多的信息到文件或者JSON字符串,为了更进一步的分析或者可视化。
require "objspace" ObjectSpace.trace_object_allocations do puts ObjectSpace.dump(["foo"].freeze) end
输出
{ "address": "0x007fd122123f40", "class": "0x007fd121072098", "embedded": true, "file": "example.rb", "flags": { "wb_protected": true }, "frozen": true, "generation": 6, "length": 1, "line": 4, "references": [ "0x007fd122123f68" ], "type": "ARRAY" }
你也可以使用 useObjectSpace.dump_all 来转储这个的堆。
require "objspace" ObjectSpace.trace_object_allocations_start # do things ... ObjectSpace.dump_all(output: File.open("heap.json", "w"))
在没有激活对象分配追踪的情况下这两个方法都是可用的,但是你只能得到很少的输出信息。
终于,ObjectSpace.reachable_objects_from_root有点类似ObjectSpace.reachable_objects_from但是它不接受参数并且从根工作. 有一个小怪癖,这个方法返回一个已经放入“身份比较”模式的hash, 因此你需要抽取相同的用于获取任何东西的键的string对象。 幸运的是有一个变通方案。
equire "objspace" reachable = ObjectSpace.reachable_objects_from_root reachable = {}.merge(reachable) # workaround compare_by_identity reachable["symbols"] #=> ["freeze", "inspect", "intern", ...
Refinements
Refinements 不再是实验性的了,并且也不会产生警告了, 另外还有几个小的调整使他变得更可用了。
连同使用顶级的#using来激活一个文件的refinements,现在有了一个Module#using方法来激活模块的refinements。 然而, ‘using’一个refinement 的效果依旧是词法的, 重新开始一个模块定义时不会主动激活它。
module NumberQuery refine String do def number? match(/\A(0|-?[1-9][0-9]*)\z/) ? true : false end end end module Example using NumberQuery "42".number? #=> true end module Example "42".number? #=> #<NoMethodError: undefined method `number?' for "42":String> end
Refinement 定义现在被Module#include继承, 这意味着你可以组合一大串refinements定义到一个单独的模块中,并且使用一个using来激活它们。
module BlankQuery refine Object do def blank? respond_to?(:empty?) ? empty? : false end end refine String do def blank? strip.length == 0 end end refine NilClass do def blank? true end end end module NumberQuery refine Object do def number? false end end refine String do def number? match(/\A(0|-?[1-9][0-9]*)\z/) ? true : false end end refine Numeric do def number? true end end end module Support include BlankQuery include NumberQuery end class User using Support # ... def points=(obj) raise "points can't be blank" if obj.blank? raise "points must be a number" unless obj.number? @points = obj end end
String#scrub
String#scrub已经添加到Ruby2.1中来帮助处理以无效字节为结尾的字符串的。
# create a string that can't be sensibly printed # 'latin 1' encoded string with accented character string = "öops".encode("ISO-8859-1") # misrepresented as UTF-8 string.force_encoding("UTF-8") # and mixed with a UTF-8 character string = "¡#{string}!"
你再也不会故意创建一个像这样的字符串了(至少我希望如此), 但是像是一个字符串搞坏大量系统的情形并不少见。
仅仅处理最终结果是不可能解决问题的,但是我们至少可以去掉那些无效的字符。
# replace with 'replacement character' string.scrub #=> "¡�ops!" # delete string.scrub("") #=> "¡ops!" # replace with chosen character string.scrub("?") #=> "¡?ops!" # yield to a block for custom replacement # (in this case the invalid bytes as hex) string.scrub {|bytes| "<#{bytes.unpack("H*").join}>"} #=> "¡<f6>ops!"
同样的结果可以通过调用#encoding并传递当前编码以及invalid: :replace作为参数实现。
string.encode("UTF-8", invalid: :replace) #=> "¡�ops!" string.encode("UTF-8", invalid: :replace, replace: "?") #=> "¡?ops!"
Bignum/Rational 性能提升
Bignum and Rational 现在使用 GNU Multiple Precision Arithmetic Library (GMP)来提升性能
$SAFE 级别 4 被移除
设置$SAFE = 4目的是将Ruby放入一个“沙箱”模型并且允许执行不受信任的代码。 然而这并不是十分有效, 因为这需要代码分散到整个Ruby中,并且这几乎从来没有被用到,所以就被移除了。
$SAFE = 4 #=> #<ArgumentError: $SAFE=4 is obsolete>
clock_gettime
目前Ruby可以通过Process.clock_gettime访问系统的clock_gettime()函数,这可以方便地访问多个不同的时间值。它的第一个参数必须是clock id:
Process.clock_gettime(Process::CLOCK_REALTIME) #=> 1391705719.906066
Process::CLOCK_REALTIME将返回一个unix时间戳。这和Time.now.to_f返回值相同,但是因为它跳过创建时间实例,所以会更快一点。
Process.clock_gettime的另一个用途是访问一个单调时钟,这个时钟总是向前移动,无论系统时钟如何调整。这对关键时序或者基准测试是完美的。
然而,单调时钟的值只有和另一个任意的开始参考点的值做比较时才有意义。
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) sleep 1 Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time #=> 1.0051147330086678
另一个时钟CLOCK_PROCESS_CPUTIME_ID对基准测试是有用的,它的工作方式和单调时钟相似,总是向前移动,只有和另一个cpu time做参考时才有意义,但是它只有在CPU工作的情况下,时间才向前移动。
start_time = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID) sleep 1 Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID) - start_time #=> 0.005225999999999981
这三个时钟,实时、单调和CPU总是可用的。你可以访问其他的时钟,这依赖于你的系统,对于其他可访问的时钟,请查询文档
为了检查支持哪些时钟,你能检查它的clock id的常量。
Process.const_defined?(:CLOCK_PROCESS_CPUTIME_ID) #=> true Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID) #=> false
也有一个Process.clock_getres的方法可以用来发现系统提供了哪些特定的时钟。
升级RubyGems
Ruby自带的RubyGems版本升级到2.2。基础的Gemfile支持已添加对Gemfile.lock的支持,并向着合入所有Bundler功能到RubyGems而努力。
gem install的--file(或者-g)选项不再要求依赖文件的文件名,它将自动检测Gemfile。如果文件不存在,gem install将产生Gemfile.lock,如果文件存在将遵循特定的版本。
$ ls Gemfile $ gem install -g Fetching: going_postal-0.1.4.gem (100%) Installing going_postal (0.1.4) $ ls Gemfile Gemfile.lock
在 RubyGems历史文件你能看到完整的改动列表。
移除弃用的Rake特性
绑定的Rake已经更新到版本10.1.0, 这移除了大量弃用的特性. Rake较老的版本已经警告这些特性好一阵子了,所以希望你不会遇到版本兼容性问题。
查看Rake 10.0.3 和10.1.0的全部发布说明来获得更多细节
RDoc模板更新
内置的RDoc版本目前是4.1, 它为默认模板带来一个很不错的可访问性的提升更新. 查看RDoc History file 来了解所有更新。
进程标题
添加新的方法Process.setproctitle用于设置进程的标题,不用再设置$0。还增加了相关的方法Process.argv0用于查看$0的初始值,如果$0被设置。
在后台处理进程中有如下的代码
data.each_with_index do |datum, i| Process.setproctitle("#{Process.argv0} - job #{i} of #{data.length}") process(datum) end
如果你运行ps命令,可以看到如下的显示
$ ps PID TTY TIME CMD 339 ttys000 0:00.23 -bash 7321 ttys000 0:00.06 background.rb - job 10 of 30
冻结符号
目前连接整数和浮点数的符号被冻结
:foo.frozen? #=> true :foo.instance_variable_set(:@bar, "baz") #=> #<RuntimeError: can't modify frozen Symbol>
在Ruby将来的版本中这个改变将被设置为符号的垃圾回收
修复了eval作用域解析错误
当eval, instance_eval 或者 module_eval 解析的字符串中含有不带参数的private,protected,public或module_function时,方法的可见作用域变成了它调用处的作用域,如下面这个例子 foo 将会是private的。
class Foo eval "private" def foo "foo" end end
这种情况已经在2.1中得到了修正。所以,在这个例子中,foo应该是public的。
#untrusted?现在是#tainted?的别名
之前,Ruby有两套方法来标识/检查对象是否是untrusted,第一套是#tainted?,#taint和#untaint,另一套是#untrusted?,#untrust, 和#trust。这些方法行为都一样,但是分别设置了标识,所以一个对象可能是 untrusted,但不是tainted。
这些方法已经被统一成了对单个的标识设值或取值。#tainted?等是推荐的用法,而#untrusted?等会产生警告。
string = "foo" string.untrust string.tainted? #=> true
产生的警告
example.rb:2: warning: untrust is deprecated and its behavior is same as taint
Lambda 中的return总是从Lambda返回
Lambdas 不同于内部使用了return的Lambda并从lambda返回的Procs/blocks,它不是封闭方法. 但是有一个例外,如果传给某个方法的lambda带有&并且被yield调用. 这一例外目前已经被移除了。
def call_with_yield yield end def test call_with_yield(&lambda {return "hello from lambda"}) "hello from method" end test #=> "hello from method"
上面的例子在Ruby 2.0.0 之前的版本会返回"hello from lambda"。
获取网络接口地址
目前可以通过Socket.getifaddrs获取系统的网络接口详细信息。将返回Socket::Ifaddr对象数组。
require "socket" info = Socket.getifaddrs.find do |ifaddr| (ifaddr.flags & Socket::IFF_BROADCAST).nonzero? && ifaddr.addr.afamily == Socket::AF_INET end info.addr.ip_address #=> "10.0.1.2"
StringScanncer支持命名捕获
StringScanner#[]接受一个符号作为参数,并将返回最后匹配到的命名捕获
require "strscan" def parse_ini(string) scanner = StringScanner.new(string) current_section = data = {} until scanner.eos? scanner.skip(/\s+/) if scanner.scan(/;/) scanner.skip_until(/[\r\n]+/) elsif scanner.scan(/\[(?<name>[^\]]+)\]/) current_section = current_section[scanner[:name]] = {} elsif scanner.scan(/(?<key>[^=]+)=(?<value>.*)/) current_section[scanner[:key]] = scanner[:value] end end data end
YAML.safe_load
YAML(Psych,yaml底层实现)已经增加了safe_load方法。缺省情况下,只有以下的类可以被反序列化:TrueClass,FalseClass,NilClass,Numeric,String,Array和Hash。为了安全的反序列化其他已知的类,可以将这些类作为参数加入白名单。
如果一个类不被Psych::DisallowedClass允许,也可以用YAML::DisallowedClass引用。
require "yaml" YAML.safe_load(":foo: 1") #=> #<Psych::DisallowedClass: Tried to load unspecified class: Symbol> YAML.safe_load(":foo: 1", [Symbol]) #=> {:foo=>1}
Resolv对单发多播DNS 和LOC 记录的支持
Ruby的Resolv DNS 库中添加了对单发多播DNS查询的基本支持。它还不支持持续查询,并且不能做服务搜索,但是这依旧是一个不错的新特性(查看 dnssd gem 获取更多关于DNS 服务搜索的支持).
require "resolv" resolver = Resolv::MDNS.new resolver.getaddress("example.local") #=> #<Resolv::IPv4 10.0.1.2>
结合 resolv-replace 库,我们就可以在大部分Ruby 的networking 库中使用多播DNS的名字了。
require "resolv-replace" require "net/http" Resolv::DefaultResolver.replace_resolvers([Resolv::Hosts.new, Resolv::MDNS.new]) Net::HTTP.get_response(URI.parse("http://example.local")) #=> #<Net::HTTPOK 200 OK readbody=true>
Resolv 也添加了查询 DNS LOC records的支持.
require "resolv" dns = Resolv::DNS.new # find.me.uk has LOC records for all UK postcodes resource = dns.getresource("W1A1AA.find.me.uk", Resolv::DNS::Resource::IN::LOC) resource.latitude #=> #<Resolv::LOC::Coord 51 31 6.827 N> resource.longitude #=> #<Resolv::LOC::Coord 0 8 37.585 W>
最后一个Resolve更新是, 使用Resolv::DNS#fetch_resource可以获得全部DNS信息。
require "resolv" dns = Resolv::DNS.new dns.fetch_resource("example.com", Resolv::DNS::Resource::IN::A) do |reply, reply_name| reply #=> #<Resolv::DNS::Message:0x007f88192e2cc0 @id=55405, @qr=1, @opcode=0, @aa=0, @tc=0, @rd=1, @ra=1, @rcode=0, @question= [[#<Resolv::DNS::Name: example.com.>, Resolv::DNS::Resource::IN::A]], @answer= [[#<Resolv::DNS::Name: example.com.>, 79148, #<Resolv::DNS::Resource::IN::A:0x0 07f88192e1c80 @address= #<Resolv::IPv4 93.184.216.119>, @ttl=79148>]], @authority=[], @additional=[]> reply_name #=> #<Resolv::DNS::Name: example.com.> end
改进socket错误信息
sockets错误信息改进,在错误信息中将包含socket地址。
require "socket" TCPSocket.new("localhost", 8080) #=> #<Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 8080>
Hash#shift变得更快
Hash#shift的性能将有非常大的提高,Ruby 1.9中耦合哈希被有序的插入,这使得它可以用来实现一个简单的最少最近使用缓存。
class LRUCache def initialize(size) @size, @hash = size, {} end def [](key) @hash[key] = @hash.delete(key) end def []=(key, value) @hash.delete(key) @hash[key] = value @hash.shift if @hash.size > @size end end
Queue,SizedQueue和ConditionVariable性能提升
Queue,SizedQueue和ConditionVariable已经在C语言中加速实现。
Timeout 的内部异常将不能被捕获
我们将不能捕获由Timeout产生的异常,以便使其退出代码块。这只是一个内部实现细节上的变化,我们不需要有所顾虑。当一个timeout超时异常没有被捕获时,会产生一个外部的异常Timeout::Error,该异常可以被正常捕获。
require "timeout" begin Timeout.timeout(1) do begin sleep 2 rescue Exception # no longer swallows the timeout exception end end rescue StandardError => e e #=> #<Timeout::Error: execution expired> end
Set
Set 中加入了#intersect? 和 #disjoint? 方法。当接收者和参数两者之间至少有一个共同值的时候#intersect? 会返回true,反之,返回false。#disjoint? 则恰恰相反,如果集合之间没有相同的值,返回true,反之,返回false。
require "set" a = Set[1,2,3] b = Set[3,4,5] c = Set[4,5,6] a.intersect?(b) #=> true b.intersect?(c) #=> true a.intersect?(c) #=> false a.disjoint?(b) #=> false b.disjoint?(c) #=> false a.disjoint?(c) #=> true
Set的另一个主要的变化是,调用一个 set 的 #to_set 会返回它自身,而不是它的拷贝。
require "set" set = Set["foo", "bar", "baz"] set.object_id #=> 70286489985620 set.to_set.object_id #=> 70286489985620
使用WEBrick更容易的对响应进行流式处理
现在,WEBrick HTTP 响应的body可以被设置成任何实现了#read 和 #readpartial 的对象。之前,该对象必须为IO或String的一个实例。下面这个例子中的类封装了一个枚举器,并使用这种方法实现了每隔10秒钟就将当前的时间以流的方式输出到响应。
require "webrick" class EnumeratorIOAdapter def initialize(enum) @enum, @buffer, @more = enum, "", true end def read(length=nil, out_buffer="") return nil unless @more until (length && @buffer.length >= length) || !fill_buffer; end if length part = @buffer.slice!(0, length) else part, @buffer = @buffer, "" end out_buffer.replace(part) end def readpartial(length, out_buffer="") raise EOFError if @buffer.empty? && !fill_buffer out_buffer.replace(@buffer.slice!(0, length)) end private def fill_buffer @buffer << @enum.next rescue StopIteration @more = false end end server = WEBrick::HTTPServer.new(Port: 8080) server.mount_proc "/" do |request, response| enum = Enumerator.new do |yielder| 10.times do sleep 1 yielder << "#{Time.now}\r\n" end end response.chunked = true response.body = EnumeratorIOAdapter.new(enum) end trap(:INT) {server.shutdown} server.start
Numeric#step
Numeric的#step方法现在可以接收关键词参数by: 和 to: 而不是位置参数。参数 to: 是可选的,如果省略的话会导致一个无限循环。如果使用位置参数的话,你可以将nil作为第一个参数来得到相同的行为。
0.step(by: 5, to: 20) do |i| puts i end
输出:
0 5 10 15 20
0.step(by: 3) do |i| puts iend 0.step(nil, 3) do |i| puts iend
两者都会输出:
0 3 6 9 12 ... and so on
IO
现在 IO#seek方法接受 :CUR, :END和 :SET作为标记,连同老的标记 IO::SEEK_CUR, IO::SEEK_END 和 IO::SEEK_SET一起被命名。
新的 IO::SEEK_DATA 和 IO::SEEK_HOLE(或者 :DATA 和 :HOLE)作为它的第二个参数。
当这些标记被提供之后,可以让第一个参数用于最小尺寸的数据/孔洞的追加。
f = File.new("example.txt") # sets the offset to the start of the next data chunk at least 8 bytes long f.seek(8, IO::SEEK_DATA) # sets the offset to the start of the next empty space at least 32 bytes long f.seek(32, IO::SEEK_HOLE)
这种方式没有被提供在所有的平台上,你应该查看一下 IO.const_defined?(:SEEK_DATA)和 IO.const_defined?(:SEEK_HOLE)。
IO_nonblock不抛出异常
IO#read_nonblock和IO#write_nonblock 都添加了一个exception关键字参数。当设置为false(默认true)时会是的方法返回一个表示error的符号,而不是抛出异常。
require "socket" io = TCPSocket.new("www.example.com", 80) message = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n" loop do IO.select(nil, [io]) result = io.write_nonblock(message, exception: false) break unless result == :wait_writeable end response = "" loop do IO.select([io]) result = io.read_nonblock(32, exception: false) break unless result next if result == :wait_readable response << result end puts response.lines.first
如果外部编码是ASCII-8BIT,IO忽略内部编码
如果你设置默认的内部和外部编码,Ruby将自动从外部编码转码到内部编码。有个例外是,当外部编码设置为ASCII-8BIT (即二进制), 不会进行转码。.
当为IO方法提供该编码作为参数时,应该有同样的例外,但之前有一个bug,进行了转码。现在Bug被修复了。
File.read("example.txt", encoding: "ascii-8bit:utf-8").encoding #=> #<Encoding:ASCII-8BIT>
#include 和 #prepend 现在是public的了
Module 和 Class 中的 #include 和 #prepend 方法现在是public的了。
module NumberQuery def number? match(/\A(0|-?[1-9][0-9]*)\z/) ? true : false end end String.include(NumberQuery) "123".number? #=> true //================================ require "bigdecimal" module FloatingPointFormat def to_s(format="F") super end end BigDecimal.prepend(FloatingPointFormat) decimal = BigDecimal("1.23") decimal.to_s #=> "1.23" # rather than "0.123E1"
Module/Class #singleton_class?
Module 和 Class 中引入了一个#singleton_class? 方法,用来返回接收器是否是一个单类。
class Example singleton_class? #=> false class << self singleton_class? #=> true end end
Module#ancestors 行为更加一致
现在,在一个单类中调用#ancestors后, 返回的数组中会包含单类本身,这使得它的在常规的类中调用和单类中调用的行为更加一致。它同时还修正了一处在单类中可能会出现的不规范的行为,并且只有当一个模块已经被插入(没有包括在内)到这个单类之中时才会发生。
Object.ancestors.include?(Object) #=> true Object.singleton_class.ancestors.include?(Object.singleton_class) #=> true
Object#singleton_method
与#method和#instance_method很相似, 但只返回单例方法。
class Example def self.test end def test2 end end # returns class method Example.singleton_method(:test) #=> #<Method: Example.test> # doesn't return instance method Example.singleton_method(:test2) #=> #<NameError: undefined singleton method `test2' for `Example'> # doesn't return inherited class method Example.singleton_method(:name) #=> #<NameError: undefined singleton method `name' for `Example'>
example = Object.new def example.test end example.singleton_method(:test) #=> #<Method: #<Object:0x007fc54997a610>.test>
Method#original_name
Method 和UnboundMethod 中添加了一个#original_name方法,来返回非别名。
class Example def foo "foo" end alias bar foo end example = Example.new example.method(:foo).original_name #=> :foo example.method(:bar).original_name #=> :foo Example.instance_method(:bar).original_name #=> :foo
Mutex#owned?
Mutex#owned? 不再是实验性的了, 并且这也没什么可多说的。
Hash#reject
调用Hash子类的Hash#reject将会产生一个警告. 在 Ruby 2.2中调用Hash的#reject将会返回一个新的Hash实例,而不是子类的一个实例。 因此为了避免这种潜在的突发变化,会产生一个警告。
class MyHash < Hash end example = MyHash.new example[:a] = 1 example[:b] = 2 example.reject {|k,v| v > 1}.class #=> MyHash
产生如下警告。
example.rb:8: warning: copying unguaranteed attributes: {:a=>1, :b=>2} example.rb:8: warning: following atributes will not be copied in the future version: example.rb:8: warning: subclass: MyHash
Ruby 2.1.1无意中包含了所有的改变,像上例中返回Hash并且不产生警告。不过2.1.2中又撤消了。
Vector#cross_product
Vector 类中添加了一个cross_product 实例方法。
require "matrix" Vector[1, 0, 0].cross_product(Vector[0, 1, 0]) #=> Vector[0, 0, -1]
整数/大数 #bit_length
调用integer的#bit_length方法会返回一个代表该数二进制数的位数的数字。
128.bit_length #=> 8 32768.bit_length #=> 16 2147483648.bit_length #=> 32 4611686018427387904.bit_length #=> 63
pack/unpack 本机字节存储次序 long long
Array#pack和String#unpack中添加了使用Q_/Q!和_/q!指令来处理本机字节存储次序的能力.
# output may differ depending on the endianness of your system unsigned_long_long_max = [2**64 - 1].pack("Q!") #=> "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" signed_long_long_min = [-2**63].pack("q!") #=> "\x00\x00\x00\x00\x00\x00\x00\x80" signed_long_long_max = [2**63 - 1].pack("q!") #=> "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" unsigned_long_long_max.unpack("Q!") #=> 18446744073709551615 signed_long_long_min.unpack("q!") #=> -9223372036854775808 signed_long_long_max.unpack("q!") #=> 9223372036854775807
Dir glob 返回合成的字符
Mac OS X上的增强版HFS文件系统使用UTF8-MAC编码文件名, 这会带有分解的字符,举个例子, é代表 e 和 U+0301,而不只是 U+00E9 (特列除外).Dir.glob和Dir[]现在会使用合成的字符将他们正常化为 UTF8编码的字符串。
File.write("composed_e\u{301}xample.txt", "") File.write("precomposed_\u{e9}xample.txt", "") puts Dir["*"].map(&:dump)
"composed_\u{e9}xample.txt" "example.rb" "precomposed_\u{e9}xample.txt"
更好的强制类型转换 Numeric#quo
Numeric#quo调用#to_r的接受者在实现你自己的Numeric子类时应该允许更好的行为。 这也意味着如果接受者不能被装换时会抛出TypeError而不是ArgumentError。因为TypeError是ArgumentError的子类,那么这也就不是什么问题了。
Binding#local_variable_get/_set/_defined?
Binding 中添加了获取/设置本地变量的方法. 这个迟早能用上,如果你真的想要使用保留字作为关键字参数来使用。
def primes(begin: 2, end: 1000) [binding.local_variable_get(:begin), 2].max.upto(binding.local_variable_get(:end)).each_with_object([]) do |i, array| array << i unless (2...i).any? {|j| (i % j).zero?} end end primes(end: 10) #=> [2, 3, 5, 7]
或者你想用Hash来填充Binding的本地变量, 评估一个模板。
def make_binding(hash) b = TOPLEVEL_BINDING.dup hash.each {|k,v| b.local_variable_set(k, v)} b end require "erb" cover = %Q{<h1><%= title %></h1>\n<h2 class="big friendly"><%= subtitle %></h2>} locals = {:title => "Hitchhiker's Guide to the Galaxy", :subtitle => "Don't Panic"} ERB.new(cover).result(make_binding(locals)) #=> "<h1>Hitchhiker's Guide to the Galaxy</h1>\n<h2 class=\"big friendly\">Don't Panic</h2>"
CGI类方法添加到了CGI::Util模块
CGI有一些有用的类方法来转义url和html字符串. 他们已经被移到了CGI::Util 模块,该模块可以被包括进其他类或者脚本的主作用域。
require "cgi/util" CGI.escape("hello world!") #=> "hello+world%21" include CGI::Util escape("hello world!") #=> "hello+world%21"
Digest::Class.file 将参数传递给initialiser
各种各样的Digest类有一个快捷的方法来生成一个指定文件的digest,该方法被修改为将除了filename之外的其他参数传递给initialiser,所以,你不需要像下面这样:
require "digest" Digest::SHA2.new(512).hexdigest(File.read("example.txt")) #=> "f7fbba..."
你需要这样:
require "digest" Digest::SHA2.file("example.txt", 512).hexdigest #=> "f7fbba..."
Net::SMTP#rset
现在,你可以通过使用Net::SMTP#reset 发送REST命令来取消SMTP传输。
require "net/smtp" smtp = Net::SMTP.start("some.smtp.server") notification = "Hi %s,\n ..." users.each do |user| begin smtp.mailfrom("noreply@example.com") smtp.rcptto(user.email) smtp.data(sprintf(notification, user.name)) rescue smtp.rset end end smtp.finish
open-uri 支持重复的header值
open-uri 允许 Kernel#opento 使用URI 打开一个资源,并且使用 OpenURI::Meta 来扩展返回的值。这会得到一个新的#metas 方法,当多次使用同一个header时(如set-cookie),该方法会被用来返回一个header值的数组。
require "open-uri" f = open("http://google.com") f.meta["set-cookie"].class #=> String f.metas["set-cookie"].class #=> Array f.metas["set-cookie"].length #=> 2
使用Pathname写文件
Pathname中添加了#write和#binwrite方法来写文件。
require "pathname" path = Pathname.new("test.txt").expand_path(__dir__) path.write("foo") path.write("bar", 3) # offset path.write("baz", mode: "a") # append
Tempfile.create
Tempfile 现在有了一个类似与new的create方法,不同的是,他并不是返回一个当对象被回收后使用finaliser来清理文件的Tempfile实例,而是得到一个块内的普通文件对象,并在块结束时清理该文件。
require "tempfile" path = nil Tempfile.create("example") do |f| f #=> #<File:/tmp/example20140428-16851-15kf046> path = f.path end File.exist?(path) #=> false
Rinda 多播支持
Rinda Ring 类现在可以监听/连接到多播地址了。
下面是一个使用Rinda来创建一个极其简单的服务注册来监听多播地址 239.0.0.1的例子
require "rinda/ring" require "rinda/tuplespace" DRb.start_service tuple_space = Rinda::TupleSpace.new server = Rinda::RingServer.new(tuple_space, ["239.0.0.1"]) DRb.thread.join
注册自己的服务:
require "rinda/ring" DRb.start_service ring_finger = Rinda::RingFinger.new(["239.0.0.1"]) tuple_space = ring_finger.lookup_ring_any tuple_space.write([:message_service, "localhost", 8080]) # start messaging service on localhost:8080
并且发现服务的地址:
require "rinda/ring" DRb.start_service ring_finger = Rinda::RingFinger.new(["239.0.0.1"]) tuple_space = ring_finger.lookup_ring_any _, host, port = tuple_space.read([:message_service, String, Fixnum]) # connect to messaging service
tuple_space = ring_finger.lookup_ring_any行引发一个错误让我有点不能理解, 不得不用下面的代码来替换:
tuple_space = nil ring_finger.lookup_ring(0.01) {|x| break tuple_space = x}
更容易设置额外的 HTTP XMLRPC选项
XMLRPC::Client#http返回被Client使用的 Net::HTTP实例,允许最少的配置选项,不必为client设置访问器了。
client = XMLRPC::Client.new("example.com") client.http.keep_alive_timeout = 30 # keep connection open for longer # use client ...
URI.encode_/decode_www_form更新以匹配WHATWG标准
URI.encode_www_form和URI.decode_www_form已经更新以匹配WHATWG 标准.
URI.decode_www_form不再把;作为分隔符了,&是唯一的默认分隔符,但是有一个新的separator关键字参数来允许你改变它。
require "uri" URI.decode_www_form("foo=1;bar=2", separator: ";") #=> [["foo", "1"], ["bar", "2"]]
值为nil时,URI.decode_www_form现在可以成功地解码URI.encode_www_form的输出了。
require "uri" string = URI.encode_www_form(foo: 1, bar: nil, baz: 3) #=> "foo=1&bar&baz=3" URI.decode_www_form("foo=1&bar&baz=3") #=> [["foo", "1"], ["bar", ""], ["baz", "3"]]
RbConfig::SIZEOF
RbConfig::SIZEOF被添加一提供C类型的尺寸.
require "rbconfig/sizeof" RbConfig::SIZEOF["short"] #=> 2 RbConfig::SIZEOF["int"] #=> 4 RbConfig::SIZEOF["long"] #=> 8
使用Syslog::Logger设置facility
Syslog::Logger, Syslog的Logger兼容性接口, 可以设置facility。
require "syslog/logger" facility = Syslog::LOG_LOCAL0 logger = Syslog::Logger.new("MyApp", facility) logger.debug("test")
没有block的CSV.foreach 返回枚举器
调用没有block参数的CSV.foreach返回一个枚举器, 然而一直以来这都会导致一个IOError. 不过现在已经被修复了。
require "csv" enum = CSV.foreach("example.csv") enum.next #=> ["1", "foo"] enum.next #=> ["2", "bar"] enum.next #=> ["3", "baz"]
OpenSSL 大数
OpenSSL::BN.new现在接受整形和字符串。
require "openssl" OpenSSL::BN.new(4_611_686_018_427_387_904) #=> #<OpenSSL::BN:0x007fce7a0c56e8>
Enumerator.new 的size参数可接受任意可调用对象
Enumerator.new接受一个size参数,它既可以是integer,也可以是一个响应#call的对象。 2.0.0之前的版本只接受integer和Procs。现在已经被修复了。
require "thread" queue = Queue.new enum = Enumerator.new(queue.method(:size)) do |yielder| loop {yielder << queue.pop} end queue << "foo" enum.size #=> 1
curses 库被移除了
curses 已经被从标准库中移除了,并且现在使用 agem了。
TSort 类的方法
TSort 通常被用来从一个有依赖关系的列表中确定出一个来完成整个任务的正确顺序。但是,我们用起来有点麻烦,必须实现一个类,包括TSort,并且实现#tsort_each_node 和 #tsort_each_child.
但是现在,TSort变得比较容易使用了(使用hash的场景),之前可以使用的实例方法现在也可以通过模块来使用了,它们接受两个callable对象,一个用来替换#tsort_each_node,另一个用来替换 #tsort_each_child。
require "tsort" camping_steps = { "sleep" => ["food", "tent"], "tent" => ["camping site", "canvas"], "canvas" => ["tent poles"], "tent poles" => ["camping site"], "food" => ["fish", "fire"], "fire" => ["firewood", "matches", "camping site"], "fish" => ["stream", "fishing rod"] } all_nodes = camping_steps.to_a.flatten each_node = all_nodes.method(:each) each_child = -> step, &b {camping_steps.fetch(step, []).each(&b)} puts TSort.tsort(each_node, each_child)
输出:
stream fishing rod fish firewood matches camping site fire food tent poles canvas tent sleep
TCP Fast Open
Ruby 2.1 已经开始支持 TCP Fast Open,只要TCP Fast Open 在你的系统中可用。你可以通过检查常量Socket::TCP_FASTOPEN 和 cket::MSG_FASTOPEN 是否存在来判断 TCP Fast Open 在你的系统中是否可用。
Server:
require "socket" unless Socket.const_defined?(:TCP_FASTOPEN) abort "TCP Fast Open not supported on this system" end server = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) server.setsockopt(Socket::SOL_TCP, Socket::TCP_FASTOPEN, 5) addrinfo = Addrinfo.new(Socket.sockaddr_in(3000, "localhost")) server.bind(addrinfo) server.listen(1) socket = server.accept socket.write(socket.readline)
Client:
require "socket" unless Socket.const_defined?(:MSG_FASTOPEN) abort "TCP Fast Open not supported on this system" end socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) socket.send("foo\n", Socket::MSG_FASTOPEN, Socket.sockaddr_in(3000, "localhost")) puts socket.readline socket.close