天天看點

深入下Ruby中的String

ruby語言中的string是mutable的,不像java、c#中的string是immutable的。比如

       str1="abc"

       str2="abc"

在java中,對于字面量的字元串,jvm内部維持一張表,是以如果在java中,str1和str2是同一個string對象。而在ruby中,str1和str2是完全不同的對象。同樣,在java中對于string對象的操作都将産生一個新的對象,而ruby則是操縱同一個對象,比如:

       str="abc"

       str.concat("cdf")

struct rstring {

    struct rbasic basic;

    long len;

    char *ptr;

    union {

      long capa;

      value shared;

    } aux;

};

//ruby.h

    顯然,len是string的長度;ptr是一個char類型的指針,指向實際的字元串;然後是一個聯合,這個稍後再說。如果你看看ruby.h可以發現,幾乎所有定義的對象結構都有一個struct rbasic。顯然,struct rbasic包含由所有對象結構體共享的一些重要資訊的。看看rbasic:

struct rbasic {

 unsigned long flags;

 value klass;

其中的flags是一個多用途的标記,大多數情況下用于記錄結構體的類型,ruby.h中預定義了一些列的宏,比如t_string(表示struct rstring),t_array(表示struct rarray)等。klass是一個value類型,value也是unsigned long,可以地将它當成指針(一個指針4位元組,綽綽有餘了),它指向的是一個ruby對象,這裡以後再深入。

    那麼聯合aux中的capa和shared是幹什麼用的呢?因為ruby的string是可變的,可變意味着len可以改變,我們需要每次都根據len的變換來增減記憶體(使用c中的realloc()函數),這顯然是一個很大的開銷,解決辦法就是預留一定的空間,ptr指向的記憶體大小略大于len,這樣就不需要頻繁調用realloc了,aux.capa就是一個長度,包含額外的記憶體大小。那麼aux.shared是幹什麼的呢?這是一個value類型,說明它是指向某個對象。aux.shared其實是用于加快字元串的建立速度,在一個循環中:

#include<stdio.h>

static value t_test(value self)

{

  value str;

  str=rb_str_new2("str");

  printf("before concat: str:%p, str.aux.shared:%p, str.ptr:%s"n",str,

       (rstring(str)->aux).shared,rstring(str)->ptr);

  rb_str_cat2(str,"ing");

  printf("after concat: str:%p, str.aux.shared:%p, str.ptr:%s"n",

       str,(rstring(str)->aux).shared,rstring(str)->ptr);

  return self;

}

value ctest;

void init_string_hack(){

  ctest=rb_define_class("test",rb_cobject);

  rb_define_method(ctest,"test",t_test,0);

//string_hack.c

rb_define_class函數定義了一個類test,rb_define_method将t_test方法以test的名稱添加到test類。在

t_test中,通過rb_str_new2每次生成一個rstring結構,然後通過rb_str_cat2将str與"ing"連接配接起來,添加

了一些列印用于跟蹤。利用mkmf産生makefile,寫一個extconf.rb

require 'mkmf'

create_makefile("string_hack");

執行ruby extconf.rb,将産生一個makefile,執行make,生成一個string_hack.so的連結庫。擴充寫完了,通過

ruby調用:

require 'string_hack"

t=test.new

(1..3).each{|i| t.test}

輸出:

before concat: str:0x40098a40, str.aux.shared:0x3, str.ptr:str

after concat: str:0x40098a40, str.aux.shared:0x8, str.ptr:string

before concat: str:0x40098a2c, str.aux.shared:0x3, str.ptr:str

after concat: str:0x40098a2c, str.aux.shared:0x8, str.ptr:string

before concat: str:0x40098a18, str.aux.shared:0x3, str.ptr:str

after concat: str:0x40098a18, str.aux.shared:0x8, str.ptr:string

從結果可以看出,在str concat之前之後,str指向的位置沒有改變,改變的僅僅是str中ptr指向的字元串的值

,看看rb_str_cat2函數的實作就一目了然了:

value rb_str_cat(str, ptr, len)

    value str;

    const char *ptr;

    if (len < 0) {

        rb_raise(rb_eargerror, "negative string size (or size too big)");

    }

    if (fl_test(str, str_assoc)) {

        rb_str_modify(str);

        realloc_n(rstring(str)->ptr, char, rstring(str)->len+len);

        memcpy(rstring(str)->ptr + rstring(str)->len, ptr, len);

        rstring(str)->len += len;

        rstring(str)->ptr[rstring(str)->len] = '"0'; /* sentinel */

        return str;

    return rb_str_buf_cat(str, ptr, len);

value rb_str_cat2(str, ptr)

    return rb_str_cat(str, ptr, strlen(ptr));

//string.c

文章轉自莊周夢蝶  ,原文釋出時間2007-09-12