如何用 StringIO 把字符串当 IO 流读写?
【免费下载链接】rubyThe Ruby Programming Language项目地址: https://gitcode.com/GitHub_Trending/ru/ruby
Ruby 的StringIO让你把一个普通字符串当成流(stream)来读写,行为和IO类似,但数据保存在内存里。它属于 Ruby 标准库,源码就在本仓库的 ext/stringio 目录中,README.md 把它描述为 “PseudoIOclass from/toString”,gemspec 声明的最低版本要求是 Ruby>= 2.7。适合的场景是:代码需要一个 IO 对象做参数(比如只接受IO的库函数),但你的数据本身就是一段字符串,不想先写临时文件。
本文的所有示例都假定你已经完成这一步:
require 'stringio'创建流:StringIO.new 与 StringIO.open
创建流有两条路径,见 doc/stringio/stringio.md:
StringIO.new:返回一个包含给定字符串的StringIO对象;StringIO.open:把一个新建的StringIO对象传给块。
strio = StringIO.new('foobarbaz', 'r') strio.pos # => 0 strio.string # => "foobarbaz"选择读写模式:决定初始状态与权限
构造时第二个参数是访问模式,可以给字符串,也可以给File常量,两者等价:
strio = StringIO.new('foo', 'a') strio = StringIO.new('foo', File::WRONLY | File::APPEND)各模式的初始状态与权限(源文档中的汇总表):
| 模式 | 含义 | 初始是否清空字符串 | 读 | 写 |
|---|---|---|---|---|
'r' | 只读 | 否 | 任意位置 | 报错 |
'w' | 只写 | 是 | 报错 | 任意位置 |
'a' | 只追加 | 否 | 报错 | 仅限末尾 |
'r+' | 读/写 | 否 | 任意位置 | 任意位置 |
'w+' | 读/写 | 是 | 任意位置 | 任意位置 |
'a+' | 读/追加 | 否 | 任意位置 | 仅限末尾 |
选模式的依据是两点:数据是否要保留('w'/'w+'初始清空,其余不清空),以及写的位置('a'/'a+'只能追加到末尾,即使pos被改到别处也不受影响)。权限不符时操作会抛IOError,文档示例:
strio = StringIO.new('foobarbaz', 'r') strio.write('foo') # Raises IOError: not opened for writing还有一个值得知道的行为:在流末尾之外写入时,中间用空字符补位:
strio = StringIO.new('foo', 'w') strio.pos = 9 strio.write('baz') strio.string # => "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000baz" # Null-padded(文档示例)数据模式与编码:'t' 与 'b' 后缀
任一读写模式后面还可以追加数据模式后缀:
't':文本,初始编码为Encoding::UTF_8;'b':二进制,初始编码为Encoding::ASCII_8BIT。
两者都不给时,流默认按文本处理。文档示例:
strio = StringIO.new('foo', 'rt') strio.external_encoding # => #<Encoding:UTF-8> strio = StringIO.new(data, 'rb') strio.external_encoding # => #<Encoding:BINARY (ASCII-8BIT)>注意:指定了数据模式后缀时,读写模式不能省略,否则会报错:
StringIO.new(data, 'b') # Raises ArgumentError: invalid access mode b后续可以用#set_encoding改编码(例如strio.set_encoding('US-ASCII'));#internal_encoding固定返回nil,因为流没有内部编码。
定位:pos、seek、rewind 与 eof?
流有一个按字节计的位置(position),新建流初始为 0。文档中列出的定位方法:
#pos:返回当前位置;#pos=:设置位置;#rewind:位置归零;#seek:设置位置;#eof?:判断位置是否已到流末尾。
文档示例(可直接照抄验证):
strio = StringIO.new('foobar') strio.pos # => 0 strio.pos = 3 strio.pos # => 3 strio.eof? # => false strio.rewind strio.pos # => 0 strio.seek(0, IO::SEEK_END) strio.pos # => 6 strio.eof? # => true位置还决定了读写从哪里开始:除#pread外,读方法从当前位置开始并推进位置;#putc、#write、Kernel#puts从当前位置开始写并推进位置;#ungetc/#ungetbyte则在当前位置之前写入并把位置回退,使写入的数据成为接下来要读到的内容。#truncate截断字符串但不改变位置。
从流里读:read、gets、each_line 与 pread
读方法清单(来自 doc/stringio/stringio.md 的 Basic Reading 一节):
#getbyte:读下一个字节;#getc:读下一个字符;#gets:读下一行(或行的一部分);#read:读剩余数据(或其中一部分);#readlines:把剩余数据读成行数组返回;#each_byte/#each_char/#each_codepoint/#each_line:迭代读取;#pread:按偏移量读,不改动流自身。
#read的要点(见 read.rdoc):不给参数读全部剩余数据;给非负maxlen时最多读maxlen个字节,读不到字节返回nil:
strio = StringIO.new('Hello') strio.read # => "Hello" strio.pos # => 5 strio.read # => "" strio.rewind strio.read(3) # => "Hel" strio.read(3) # => "lo" strio.read(3) # => nil#gets支持三种参数形态:无参数用默认记录分隔符$/(初始值"\n");字符串参数sep用作自定义分隔符;整数参数limit限制每行最大字符数。另有chomp: true关键字参数可去掉行尾换行(见 gets.rdoc):
strio = StringIO.new(TEXT) strio.gets # => "First line\n" strio.gets(chomp: true) # => "Second line"#each_line同理支持sep、limit与chomp:,且支持 IO 的两个特殊分隔符:''按段(空行分隔)读、nil一次读完(见 each_line.md):
strio = StringIO.new(TEXT) strio.each_line('') {|line| p line } # 按段读取 strio.each_line(nil) {|line| p line } # "Slurp",一次读完多线程场景下用#pread(maxlen, offset):它从给定偏移读取,不修改流自身,因此多个线程可以安全地读同一个流(见 pread.rdoc)。注意它返回的字符串编码固定为Encoding::ASCII_8BIT,且偏移越界时抛EOFError: end of file reached。
往流里写:write、putc、ungetc 与 truncate
写方法清单:
#write:把对象当作字符串写入当前位置;#putc:写入一个字符(对多字节字符是替换对应字节,细节见 putc.rdoc);Kernel#puts:写入并各自跟随换行;#ungetc/#ungetbyte:在当前位置之前插入数据并回退位置;#truncate:把流的字符串截断到给定大小,位置不变。
文档示例,一条完整的写入路径及其位置变化:
strio = StringIO.new('foo') strio.pos # => 0 strio.putc('b') strio.string # => "boo" strio.pos # => 1 strio.write('r') strio.string # => "bro" strio.pos # => 2 strio.puts('ew') strio.string # => "brew\n" strio.pos # => 5验证结果:用 string、size、eof? 检查
StringIO与真实IO的关键区别在于你可以直接看到背后的字符串,这就是验证手段:
strio.string # 当前流的完整内容 strio.size # 当前字符串的字节数 strio.pos # 当前位置 strio.eof? # 是否已到末尾文档给出的size示例(见 size.rdoc):
StringIO.new('hello').size # => 5 StringIO.new('こんにちは').size # => 15 # 五个 3 字节字符一套可照做的验证顺序(全部来自源文档示例):创建流后检查strio.pos(应为 0)和strio.string(按模式决定是原字符串还是已清空);读写后检查strio.string与strio.pos是否符合上述规则;读完整段数据后strio.eof?应为true。
StringIO 与 IO 的两个差异
ext/stringio/README.md 明确列出了与IO的差别,涉及IO兼容代码时要留意:
fileno会抛NotImplementedError——字符串没有文件描述符,任何通过fileno取文件句柄的用法都不可行;- 编码转换(encoding conversion)未实现,会被静默忽略——所以不要指望
set_encoding同时完成转码,它只改变流的编码标记。
这两条是文档给出的边界;除这两点外,StringIO提供与IO相近的行/字符/字节读写接口(见 doc/stringio/stringio.md 的 Line IO、Character IO、Byte IO 各节)。
【免费下载链接】rubyThe Ruby Programming Language项目地址: https://gitcode.com/GitHub_Trending/ru/ruby
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考