Ruby 2.1 詳情

jopen 11年前發布 | 76K 次閱讀 Ruby Ruby開發

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></pre>

像上面展示的例子中,我們可以看到關鍵字參數可以幫助我們消除哪個參數是哪個的歧義, 但是默認值并不總是必須的. 現在我們不必總是加默認值了。

字符串#freeze優化

Ruby中字符串是易變的,任何字符串文字在每次調用他們時都將產生一個新的字符串,例如

def env
  "development"
end

 returns new String object on each call

env.object_id   #=> 70329318373020 env.object_id   #=> 70329318372900</pre>

這是十分浪費的,創建并垃圾回收了許多的對象。為了讓你能夠避免這種情況,在字符串文字上直接調用#freeze,這將導致在凍結字符串表中查找字符串。這就意味著同樣的字符串將被再次使用。

def env
  "development".freeze
end

 returns the same String object on each call

env.object_id   #=> 70365553080120 env.object_id   #=> 70365553080120</pre>

在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</pre>

在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</pre>

同樣它也提供了更好的添加方法修飾符的方式, 下面是一個使用 Module#prepend 來包裝一個方法完成前/后調用。

module Around
  def around(method)
    prepend(Module.new do
      definemethod(method) do |*args, &block|
        send(:"before#{method}") if respondto?(:"before#{method}", true)
        result = super(*args, &block)
        send(:"after_#{method}") if respondto?(:"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</pre>

輸出

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"}</pre>

這將在所有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"}</pre>

細粒度方法緩存

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</pre>

異常

現在異常有一個#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</pre>

目前引發的錯誤不會輸出到任何地方,并且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</pre>

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</pre>

輸出

{
    "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"))</pre>

在沒有激活對象分配追蹤的情況下這兩個方法都是可用的,但是你只能得到很少的輸出信息。

終于,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", ...</pre>

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</pre>

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</pre>

String#scrub

String#scrub已經添加到Ruby2.1中來幫助處理以無效字節為結尾的字符串的。

# create a string that can't be sensibly printed

 'latin 1' encoded string with accented character

string = "&ouml;ops".encode("ISO-8859-1")

 misrepresented as UTF-8

string.force_encoding("UTF-8")

 and mixed with a UTF-8 character

string = "&iexcl;#{string}!"</pre>

你再也不會故意創建一個像這樣的字符串了(至少我希望如此), 但是像是一個字符串搞壞大量系統的情形并不少見。

僅僅處理最終結果是不可能解決問題的,但是我們至少可以去掉那些無效的字符。

# replace with 'replacement character'
string.scrub        #=> "&iexcl;?ops!"

 delete

string.scrub("")    #=> "&iexcl;ops!"

 replace with chosen character

string.scrub("?")   #=> "&iexcl;?ops!"

 yield to a block for custom replacement

 (in this case the invalid bytes as hex)

string.scrub {|bytes| "<#{bytes.unpack("H*").join}>"}   #=> "&iexcl;<f6>ops!"</pre>

同樣的結果可以通過調用#encoding并傳遞當前編碼以及invalid: :replace作為參數實現。

string.encode("UTF-8", invalid: :replace)                 #=> "&iexcl;?ops!"
string.encode("UTF-8", invalid: :replace, replace: "?")   #=> "&iexcl;?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.310.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"</pre>

上面的例子在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"</pre>

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</pre>

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></pre>

結合 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(";

=> #<Net::HTTPOK 200 OK readbody=true></pre>

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></pre>

最后一個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</pre>

改進socket錯誤信息

sockets錯誤信息改進,在錯誤信息中將包含socket地址。

require "socket"

TCPSocket.new("localhost", 8080)   #=> #<Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 8080></pre>

Hash#shift變得更快

Hash#shift的性能將有非常大的提高,Ruby 1.9中耦合哈希被有序的插入,這使得它可以用來實現一個簡單的最少最近使用緩存。

class LRUCache
  def initialize(size)
    @size, @hash = size, {}
  end

  def      @hash[key] = @hash.delete(key)   end

  def []=(key, value)     @hash.delete(key)     @hash[key] = value     @hash.shift if @hash.size > @size   end end</pre>

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</pre>

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</pre>

Set的另一個主要的變化是,調用一個 set 的 #to_set 會返回它自身,而不是它的拷貝。

require "set"

set = Set["foo", "bar", "baz"] set.object_id          #=> 70286489985620 set.to_set.object_id   #=> 70286489985620</pre>

使用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</pre>

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)</pre>

這種方式沒有被提供在所有的平臺上,你應該查看一下 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</pre>

如果外部編碼是ASCII-8BIT,IO忽略內部編碼

如果你設置默認的內部和外部編碼,Ruby將自動從外部編碼轉碼到內部編碼。有個例外是,當外部編碼設置為ASCII-8BIT (即二進制), 不會進行轉碼。.

當為IO方法提供該編碼作為參數時,應該有同樣的例外,但之前有一個bug,進行了轉碼。現在Bug被修復了。

File.read("example.txt", encoding: "ascii-8bit:utf-8").encoding   

=> #<Encoding:ASCII-8BIT></pre>

#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"</pre>

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'&nbsp;for&nbsp;Example'>

 doesn't return inherited class method

Example.singleton_method(:name)    #=> #<NameError: undefined singleton method name'&nbsp;for&nbsp;Example'></pre>

example = Object.new

def example.test end

example.singleton_method(:test)   #=> #<Method: #<Object:0x007fc54997a610>.test></pre>

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</pre>

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</pre>

產生如下警告。

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]</pre>

整數/大數 #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 = [264 - 1].pack("Q!")   #=> "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
signed_long_long_min = [-263].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</pre>

Dir glob 返回合成的字符

Mac OS X上的增強版HFS文件系統使用UTF8-MAC編碼文件名, 這會帶有分解的字符,舉個例子, &eacute;代表 e 和 U+0301,而不只是 U+00E9 (特列除外).Dir.glob和Dir[]現在會使用合成的字符將他們正常化為 UTF8編碼的字符串。

File.write("composede\u{301}xample.txt", "")
File.write("precomposed\u{e9}xample.txt", "")

puts Dir["*"].map(&:dump)</pre>

"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]</pre>

或者你想用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>"</pre>

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"</pre>

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</pre>

open-uri 支持重復的header值

open-uri 允許 Kernel#opento 使用URI 打開一個資源,并且使用 OpenURI::Meta 來擴展返回的值。這會得到一個新的#metas 方法,當多次使用同一個header時(如set-cookie),該方法會被用來返回一個header值的數組。

require "open-uri"

f = open("使用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</pre>

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</pre>

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</pre>

注冊自己的服務:

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</pre>

并且發現服務的地址:

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</pre>

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 ...</pre>

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"]]</pre>

RbConfig::SIZEOF

RbConfig::SIZEOF被添加一提供C類型的尺寸.

require "rbconfig/sizeof"

RbConfig::SIZEOF["short"]   #=> 2 RbConfig::SIZEOF["int"]     #=> 4 RbConfig::SIZEOF["long"]    #=> 8</pre>

使用Syslog::Logger設置facility

Syslog::Logger, Syslog的Logger兼容性接口, 可以設置facility。

require "syslog/logger"

facility = Syslog::LOG_LOCAL0 logger = Syslog::Logger.new("MyApp", facility)

logger.debug("test")</pre>

沒有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"]</pre>

OpenSSL 大數

OpenSSL::BN.new現在接受整形和字符串。

require "openssl"

OpenSSL::BN.new(4_611_686_018_427_387_904)   #=> #<OpenSSL::BN:0x007fce7a0c56e8></pre>

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</pre>

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)</pre>

輸出:

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)</pre>

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</pre>

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!