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")

  此时str就是"abccdf"。Ruby对String是怎么处理的呢?我们只谈谈c ruby中的实现,有兴趣的先看看这篇文章《管窥Ruby——对象基础》。在ruby.h中我们可以看到String对象的结构,Ruby中的对象(包括类也是对象)都是一个一个的struct,String也不能例外:


代码如下:

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其实是用于加快字符串的创建速度,在一个循环中:

  ruby 代码

  whiletruedo重复 a="str"#以“str”为内容创建字符串,赋值给a a.concat("ing")#为a所指向的对象添加“ing” p(a)#显示“string” end

  每次都重新创建一个"str"对象,内部就是重复创建一个char[],这是相当奢侈,aux.shared就是用于共享char[],以字面量创建的字符串会共享一个char[],当要发生变化时,将字符串复制到一个非共享的内存中,变化针对这个新拷贝进行,这就是所谓的“copy-on-write"技术。解释了String的内部构造,貌似还没有介绍String是怎么实现mutable,我们写一个Ruby扩展测试下,我们想写这样一个Ruby类:

  ruby 代码

classTestdefteststr="str"str.concat("ing")endend

  对应的c语言代码就是:

  cpp 代码


代码如下:

#include
#include"ruby.h"staticVALUEt_test(VALUEself){
VALUEstr;str=rb_str_new2("str");
printf("beforeconcat:str:%p,
str.aux.shared:%p,str.ptr:%s"n",str,(RSTRING(str)->aux).shared,RSTRING(str)->ptr);
rb_str_cat2(str,"ing");
printf("afterconcat:str:%p,str.aux.shared:%p,str.ptr:%s"n",
str,(RSTRING(str)->aux).shared,RSTRING(str)->ptr);returnself;
}
VALUEcTest;
voidInit_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

  ruby 代码

require'mkmf'create_makefile("string_hack");

  执行ruby extconf.rb,将产生一个Makefile,执行make,生成一个string_hack.so的链接库。扩展写完了,通过ruby调用:

  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函数的实现就一目了然了:

  cpp 代码


代码如下:

VALUErb_str_cat(str,ptr,len)VALUEstr;
constchar*ptr;
longlen;
{
if(len<0){rb_raise(rb_eArgError,"negativestringsize(orsizetoobig)");
}
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*/
returnstr;
}
returnrb_str_buf_cat(str,ptr,len);
}
VALUErb_str_cat2(str,ptr)VALUEstr;
constchar*ptr;
{
returnrb_str_cat(str,ptr,strlen(ptr));
}
//string.c

(0)

相关推荐

  • 苹果mac OS X上安装metasploit

    安装的几个步骤: 0×00-从github上克隆Metasploit项目到本地: 0×01-安装postgresql并进行配置: 0×02-安装特定版本的ruby,并解决依赖: 0×00 从github上克隆Metasploit项目到本地 话说github真是什么都有,很多好的项目在上面都能找到,首先打开终端并输入下列命令,因为10.9.3自带了git,所以就不需要另外安装了 git clone https://github.com/rapid7/metasploit-framework.git

  • ruby 学习笔记(2) 类的基本使用

    ruby语言跟c#的一些重要差别在于: 1.ruby是动态语言,c#是静态语言--即对象在new出来以后,ruby还可以动态给对象实例添加一些属性或方法(javascript也是如此) 2.ruby中刻意弱化了变量类型这个概念,默认情况下变量/方法都不需要声明具体(返回)类型,但其实在ruby内部,会自动根据变量的值分配类型.(可以通过 "puts 变量.class"查看) 3.ruby相对c#来讲,可能有些雷的地方在于:父类中的private成员,居然是可以在子类中使用的! ...其

  • ruby安装gem包失败的通用解决方法

    ruby语言升级还是比较勤快的.但是数量众多的版本使得程序库的兼容性成了大问题.有些gem表示明确不支持某个特定版本以前的ruby,而有些gem则与较高的版本不兼容.再加上gem本身也有版本,简直是乱成了一锅粥.即使使用了rvm.rbenv之类ruby版本管理工具也避免不了掉入坑中.并且时不时的一些其它环境设置也给你捣乱.所以一般使用ruby程序时,对升级ruby版本或各种gem版本都是比较慎重的,避免一时手贱掉入坑中. 当然你也不能因此就做缩头乌龟,某些情况下还是不得不升级的.比如想使用rub

  • Ruby rails 页面跳转(render和redirect_to)

    Ruby代码 复制代码 代码如下: if @user.update_attributes(:password => params[:user][:password]) flash[:notice] = '密码修改完成' redirect_to :action => 'index' else redirect_to :action => 'change_pass', :id => @user end 后来随手改了下第5行,把redirect_to改为render,居然就OK了.网上找

  • 淘宝网提供的国内RubyGems镜像简介和使用方法

    解决方案是使用淘宝的 RubyGems 镜像,它是一个完整 rubygems.org 镜像,你可以用此代替官方版本,同步频率目前为15分钟一次以保证尽量与官方服务同步. 如何使用 gem 移除旧源,改用新源即可. 复制代码 代码如下: $ gem sources --remove https://rubygems.org/$ gem sources -a http://ruby.taobao.org/$ gem sources -l*** CURRENT SOURCES *** http://r

  • Ruby On Rails上手笔记(安装使用全过程)

    有机会再试一试Rails了,只是原来接触的是2,现在已然变成了4,似乎现在的安装比原来会快些.. Rails 4 安装 针对于安装了RVM 复制代码 代码如下: gem install rails 没有的话应该这样: 复制代码 代码如下: sudo gem install rails 安装RVM可以用句 复制代码 代码如下: curl -L https://get.rvm.io | bash -s stable 查看rails版本 复制代码 代码如下: rails -vRails 4.0.3 似

  • Ruby微信开发的几个开源项目介绍

    最近陆续有不少用Rails开发微信项目的朋友在Github上开始使用weixin_rails_middleware.weixin_authorize,也不少人谈到Ratchet,春节开始到现在,一直有做微信的开发,现在在论坛上发布出来,希望让更多人知道这些gem的存在,更快速的完成你们手中的任务. 1.一(两)条命令搭建Rails微信版本 按照最简洁的速度,10分钟可以跑起一个微信的项目: https://github.com/lanrion/weixin_rails_middleware 自动

  • 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则是操纵同一个对象,比如: 复制代码 代

  • Rust语言中的String和HashMap使用示例详解

    目录 String 新建字符串 更新字符串 使用 + 运算符或 format! 宏拼接字符串 索引字符串 字符串 slice 遍历字符串 HashMap 新建 HashMap HashMap 和 ownership 访问 HashMap 中的值 更新 HashMap 直接覆盖 新插入 更新旧值 总结 String 字符串是比很多开发者所理解的更为复杂的数据结构.加上 UTF-8 的不定长编码等原因,Rust 中的字符串并不如其它语言中那么好理解. Rust 的核心语言中只有一种字符串类型:str

  • 一文带你深入理解Go语言中的sync.Cond

    目录 sync.Cond 是什么 适用场景 sync.Cond 的基本用法 NewCond 创建实例 Wait 等待条件满足 Signal 通知一个等待的 goroutine Broadcast 通知所有等待的 goroutine sync.Cond 使用实例 为什么要用 sync.Cond close channel 广播实例 sync.Cond 基本原理 sync.Cond 的设计与实现 sync.Cond 模型 notifyList 结构体 sync.Cond 的方法 Wait 方法 Si

  • Go语言中int、float、string类型之间相互的转换

    目录 前言 整形转字符串 fmt.Sprintf 使用方法 strconv.Itoa 使用方法 strconv.FormatInt 入参 使用方法 浮点型转字符串 fmt.Sprintf 入参 使用方法 字符串转整形 strconv.Atoi 使用方法 strconv.ParseInt 使用方法 字符串转浮点型 strconv.ParseFloat 使用方法 总结 前言 Go 开发中经常设计到类型转换,本文就重点记录下 整形.浮点型和字符串类型互相转换的方法. 整形转字符串 fmt.Sprint

  • C语言中bool变量的深入理解

    目录 前言 bool类型变量的大小 bool 值与0比较 c语言中bool如何输出 总结 前言 在一些高级语言当中,为了能够完成更好的逻辑判断,因此就有了bool类型,bool类型的变量值只有true和false两种. 而在C语言中,一般认为0为假,非0为真. 这是因为c99之前,c90是没有bool类型的的.但是c99引入了_Bool类型(_Bool就是一个类型,不过在新增头文件stdbool.h中,被重新用宏写成了 bool,为了保证C/C++兼容性). 目前为止大部分C语言书籍采用的标准还

  • C语言中static的作用及C语言中使用静态函数有何好处

    想了解Java中static关键字的作用和用法详细介绍,请点击此处了解详情. 在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条,分别是: 一是隐藏功能,对于static修饰的函数和全局变量而言 二是保持持久性功能,对于static修饰的局部变量而言. 三是因为存放在静态区,全局和局部的static修饰的变量,都默认初始化为0 下面我逐一给大家介绍: (1)先来介绍它的第一条也是最重要的一条:隐藏. 当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有

  • 详解go语言中type关键词的几种使用

    type是go语法里的重要而且常用的关键字,type绝不只是对应于C/C++中的typedef.搞清楚type的使用,就容易理解go语言中的核心概念struct.interface.函数等的使用.以下我用例子代码总结描述,请特别留意代码中的注释. 1.定义结构体 //结构体定义 type person struct { name string //注意后面不能有逗号 age int } func main() { //结构体初始化 p := person{ name: "taozs",

  • Go语言中的UTF-8实现

    计算机刚诞生的时候,计算机内的字符可以全部由 ASCII 来表示,ASCII 字符的长度是 7 位,可以表示 128 个字符,对于美国等国家来说是够了,但是对于世界上的其他国家,特别是东亚国家,文字不是由字母组成,汉字就有几万个,ASCII 码根本不够用. 字符本质就是对应计算机中的一个数值,既然不够用,那么解决方法就是把这个范围扩大,Unicode 的出现就解决了这个问题,它包括了世界上所有的字符,每一个字符都对应一个数值,这个数值被称之为 Unicode 码点. 但是 Unicode 也不是

  • Go 语言中 20 个占位符的整理

    目录 一.概念 二.哪些函数支持 三.占位符使用 四.普通占位符 1. %v.%+v.%#v 2. %T 3. %% 五.布尔占位符 六.整数占位符 1. %b 2. %c 3. %d.%5d.%-5d.%05d 4. %o.%#o 5. %q 6. %x.%#x 7. %X.%#X 8. %U.%#U 七.浮点数与复数 1. %b 2. %e.%E 3. %f.%.2f 4. %g.%.3g 八.字符串与字节切片 1. %s 2. %q 3. %x.%X 九.指针 十.其它标记 1. + 2.

  • 详解Go语言中Get/Post请求测试

    目录 gin安装 Get请求测试 Post请求测试 基础语法差不多了,需要开始实践到一下项目,先来web框架gin吧,做一个后端web服务. 在把项目搭建起来的过程中,我也要结合实际的工作经验,补充一些项目结构.开发组件上的理解. 项目地址:github地址 gin安装 先将gin安装一下,安装依赖go语言还是比较方便的. 在安装之前先配置一下goproxy. 命令如下: go env -w GO111MODULE=on go env -w GOPROXY=https://mirrors.ali

随机推荐