StringIO 클래스

StringIO 클래스

문자열을 스트림(stream)처럼 다루고 싶을 때 쓰는 클래스가 StringIO예요. IO 클래스와 비슷한 점이 많은데, 실제 파일 대신 메모리 안의 문자열을 대상으로 한다는 점만 달라요.

출처: Ruby 4.0 API

본문

클래스 StringIO는 문자열을 스트림으로 접근할 수 있게 해 줘요. 여러모로 IO 클래스와 비슷하죠.

StringIO 인스턴스는 이렇게 만들 수 있어요:

  • StringIO.new: 주어진 문자열을 담은 새 StringIO 객체를 반환해요.
  • StringIO.open: 새 StringIO 객체를 주어진 블록에 넘겨요.

IO 스트림처럼 StringIO 스트림도 몇 가지 속성을 가져요:

  • 읽기/쓰기 모드: 스트림을 읽을 수 있는지, 쓸 수 있는지, 추가만 할 수 있는지 등을 결정해요. Read/Write Mode 참고.
  • 데이터 모드: 텍스트 전용인지 바이너리인지. Data Mode 참고.
  • 인코딩: 내부·외부 인코딩. Encodings 참고.
  • 위치(position): 스트림에서 다음 읽기/쓰기가 일어날 위치. Position 참고.
  • 줄 번호: 줄 단위의 특별한 "위치"(위의 position과는 달라요). Line Number 참고.
  • 열림/닫힘: 스트림이 읽기·쓰기용으로 열려 있는지 닫혀 있는지. Open/Closed Streams 참고.
  • BOM: 바이트 순서 표시(byte order mark). Byte Order Mark 참고.

예시에 관해 (About the Examples)

이 페이지의 예시는 StringIO가 require되어 있다고 가정해요:

require 'stringio'

그리고 이 상수가 정의되어 있다고 가정해요:

TEXT = <<EOT
First line
Second line

Fourth line
Fifth line
EOT

스트림 속성 (Stream Properties)

읽기/쓰기 모드 (Read/Write Mode)

요약 (Summary)

모드 초기 클리어? 읽기 쓰기
'r': 읽기 전용 No 아무데나 Error
'w': 쓰기 전용 Yes Error 아무데나
'a': 추가 전용 No Error 끝만
'r+': 읽기/쓰기 No 아무데나 아무데나
'w+': 읽기-쓰기 Yes 아무데나 아무데나
'a+': 읽기/추가 No 아무데나 끝만

아래 각 절이 읽기/쓰기 모드를 설명해요.

어느 모드든 문자열로 주거나 파일 상수로 줄 수 있어요. 예를 들면:

strio = StringIO.new('foo', 'a')
strio = StringIO.new('foo', File::WRONLY | File::APPEND)

'r': 읽기 전용 (Read-Only)

모드 지정:

  • 문자열: 'r'.
  • 상수: File::RDONLY.

초기 상태:

strio = StringIO.new('foobarbaz', 'r')
strio.pos    # => 0            # Beginning-of-stream.
strio.string # => "foobarbaz"  # Not cleared.

아무데나 읽을 수 있어요:

strio.gets(3) # => "foo"
strio.gets(3) # => "bar"
strio.pos = 9
strio.gets(3) # => nil

쓸 수는 없어요:

strio.write('foo')  # Raises IOError: not opened for writing

'w': 쓰기 전용 (Write-Only)

모드 지정:

  • 문자열: 'w'.
  • 상수: File::WRONLY.

초기 상태:

strio = StringIO.new('foo', 'w')
strio.pos    # => 0   # Beginning of stream.
strio.string # => ""  # Initially cleared.

아무데나 쓸 수 있어요(스트림 끝을 넘어서도):

strio.write('foobar')
strio.string # => "foobar"
strio.rewind
strio.write('FOO')
strio.string # => "FOObar"
strio.pos = 3
strio.write('BAR')
strio.string # => "FOOBAR"
strio.pos = 9
strio.write('baz')
strio.string # => "FOOBAR\u0000\u0000\u0000baz"  # Null-padded.

읽을 수는 없어요:

strio.read  # Raises IOError: not opened for reading

'a': 추가 전용 (Append-Only)

모드 지정:

  • 문자열: 'a'.
  • 상수: File::WRONLY | File::APPEND.

초기 상태:

strio = StringIO.new('foo', 'a')
strio.pos    # => 0      # Beginning-of-stream.
strio.string # => "foo"  # Not cleared.

끝에서만 쓸 수 있어요. 위치는 쓰기에 영향을 주지 않아요:

strio.write('bar')
strio.string # => "foobar"
strio.write('baz')
strio.string # => "foobarbaz"
strio.pos = 400
strio.write('bat')
strio.string # => "foobarbazbat"

읽을 수는 없어요:

strio.gets  # Raises IOError: not opened for reading

'r+': 읽기/쓰기 (Read/Write)

모드 지정:

  • 문자열: 'r+'.
  • 상수: File::RDRW.

초기 상태:

strio = StringIO.new('foobar', 'r+')
strio.pos    # => 0         # Beginning-of-stream.
strio.string # => "foobar"  # Not cleared.

아무데나 쓸 수 있어요(스트림 끝을 넘어서도):

strio.write('FOO')
strio.string # => "FOObar"
strio.write('BAR')
strio.string # => "FOOBAR"
strio.write('BAZ')
strio.string # => "FOOBARBAZ"
strio.pos = 12
strio.write('BAT')
strio.string # => "FOOBARBAZ\u0000\u0000\u0000BAT"  # Null padded.

아무데나 읽을 수 있어요:

strio.pos = 0
strio.gets(3) # => "FOO"
strio.pos = 6
strio.gets(3) # => "BAZ"
strio.pos = 400
strio.gets(3) # => nil

'w+': 읽기/쓰기 (초기 클리어)

모드 지정:

  • 문자열: 'w+'.
  • 상수: File::RDWR | File::TRUNC.

초기 상태:

strio = StringIO.new('foo', 'w+')
strio.pos    # => 0   # Beginning-of-stream.
strio.string # => ""  # Truncated.

아무데나 쓸 수 있고 아무데나 읽을 수 있어요:

strio.write('foobar')
strio.string # => "foobar"
strio.rewind
strio.write('FOO')
strio.string # => "FOObar"
strio.write('BAR')
strio.string # => "FOOBAR"
strio.write('BAZ')
strio.string # => "FOOBARBAZ"
strio.pos = 12
strio.write('BAT')
strio.string # => "FOOBARBAZ\u0000\u0000\u0000BAT"  # Null-padded.
strio.rewind
strio.gets(3) # => "FOO"
strio.gets(3) # => "BAR"
strio.pos = 12
strio.gets(3) # => "BAT"
strio.pos = 400
strio.gets(3) # => nil

'a+': 읽기/추가 (Read/Append)

모드 지정:

  • 문자열: 'a+'.
  • 상수: File::RDWR | File::APPEND.

초기 상태:

strio = StringIO.new('foo', 'a+')
strio.pos    # => 0      # Beginning-of-stream.
strio.string # => "foo"  # Not cleared.

끝에서만 쓸 수 있고, rewind하면 아무데나 읽을 수 있어요:

strio.write('bar')
strio.string # => "foobar"
strio.write('baz')
strio.string # => "foobarbaz"
strio.pos = 400
strio.write('bat')
strio.string # => "foobarbazbat"
strio.rewind
strio.gets(3) # => "foo"
strio.gets(3) # => "bar"
strio.pos = 9
strio.gets(3) # => "bat"
strio.pos = 400
strio.gets(3) # => nil

데이터 모드 (Data Mode)

스트림을 텍스트로 다룰지 바이너리 데이터로 다룰지 지정하려면, 위의 문자열 읽기/쓰기 모드 어느 것에든 다음 중 하나를 붙이면 돼요:

  • 't': 텍스트. 인코딩을 Encoding::UTF_8로 초기화해요.
  • 'b': 바이너리. 인코딩을 Encoding::ASCII_8BIT로 초기화해요.

둘 다 주지 않으면 기본적으로 텍스트 데이터로 처리돼요.

예시:

strio = StringIO.new('foo', 'rt')
strio.external_encoding # => #<Encoding:UTF-8>
data = "\u9990\u9991\u9992\u9993\u9994"
strio = StringIO.new(data, 'rb')
strio.external_encoding # => #<Encoding:BINARY (ASCII-8BIT)>

데이터 모드를 지정하면 읽기/쓰기 모드는 생략할 수 없어요:

StringIO.new(data, 'b')  # Raises ArgumentError: invalid access mode b

텍스트 스트림은 인스턴스 메서드 binmode로 바이너리로 바꿀 수 있어요. 하지만 바이너리 스트림을 텍스트로 바꿀 수는 없어요.

인코딩 (Encodings)

스트림에는 인코딩이 있어요. Encodings 참고.

새로 만들거나 다시 연 스트림의 초기 인코딩은 데이터 모드에 따라 달라요:

  • 텍스트: Encoding::UTF_8.
  • 바이너리: Encoding::ASCII_8BIT.

관련 인스턴스 메서드:

  • external_encoding: 스트림의 현재 인코딩을 Encoding 객체로 반환해요.
  • internal_encoding: nil을 반환해요. 스트림에는 내부 인코딩이 없어요.
  • set_encoding: 스트림의 인코딩을 설정해요.
  • set_encoding_by_bom: 스트림의 인코딩을 스트림의 BOM(바이트 순서 표시)으로 설정해요.

예시:

strio = StringIO.new('foo', 'rt')  # Text mode.
strio.external_encoding # => #<Encoding:UTF-8>
data = "\u9990\u9991\u9992\u9993\u9994"
strio = StringIO.new(data, 'rb') # Binary mode.
strio.external_encoding # => #<Encoding:BINARY (ASCII-8BIT)>
strio = StringIO.new('foo')
strio.external_encoding # => #<Encoding:UTF-8>
strio.set_encoding('US-ASCII')
strio.external_encoding # => #<Encoding:US-ASCII>

위치 (Position)

스트림에는 위치(position) 가 있어요. 스트림 안의 정수 오프셋(바이트 단위)이죠. 스트림의 초기 위치는 0이에요.

위치 가져오기와 설정하기

이 메서드들은 새로 만들거나 다시 연 스트림의 위치를 (0으로) 초기화해요:

  • ::new: 새 스트림을 반환해요.
  • ::open: 새 스트림을 블록에 넘겨요.
  • reopen: 스트림을 다시 초기화해요.

이 메서드들은 스트림을 바꾸지 않으면서 위치를 조회·가져오기·설정해요:

  • eof?: 위치가 스트림 끝인지 여부를 반환해요.
  • pos: 위치를 반환해요.
  • pos=: 위치를 설정해요.
  • rewind: 위치를 0으로 설정해요.
  • seek: 위치를 설정해요.

예시:

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를 제외하면, 스트림 읽기 메서드(기본 읽기 참고)는 현재 위치에서 읽기를 시작해요.

pread를 제외하면, 읽기 메서드는 읽은 부분 문자열만큼 위치를 앞으로 옮겨요.

strio = StringIO.new(TEXT)
strio.string # => "First line\nSecond line\n\nFourth line\nFifth line\n"
strio.pos    # => 0
strio.getc   # => "F"
strio.pos    # => 1
strio.gets   # => "irst line\n"
strio.pos    # => 11
strio.pos = 24
strio.gets   # => "Fourth line\n"
strio.pos    # => 36

멀티바이트 문자가 있으면 위치가 문자 경계에 있지 않을 수 있어요:

strio = StringIO.new('тест') # Four 2-byte characters.
strio.pos = 0 # At first byte of first character.
strio.read    # => "тест"
strio.pos = 1 # At second byte of first character.
strio.read    # => "\x82ест"
strio.pos = 2 # At first of second character.
strio.read    # => "ест"

쓰기 전후의 위치

이 메서드들은 현재 위치에서 쓰기를 시작하고, 쓴 부분 문자열의 끝까지 위치를 옮겨요:

  • putc: 주어진 문자를 써요.
  • write: 주어진 객체들을 문자열로 써요.
  • Kernel#puts: 주어진 객체들을 문자열로 쓰되 각각 개행을 붙여요.

예시:

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
strio.pos = 8
strio.write('foo')
strio.string # => "brew\n\u0000\u0000\u0000foo"
strio.pos    # => 11

이 메서드들은 현재 위치 앞에 쓰고, 위치를 감소시켜서 쓴 데이터가 다음에 읽히게 해요:

  • ungetbyte: 주어진 바이트를 앞으로 밀어 넣어요(unshift).
  • ungetc: 주어진 문자를 앞으로 밀어 넣어요.

예시:

strio = StringIO.new('foo')
strio.pos = 2
strio.ungetc('x')
strio.pos    # => 1
strio.string # => "fxo"
strio.ungetc('x')
strio.pos    # => 0
strio.string # => "xxo"

위치에 영향을 주지 않는 메서드:

  • truncate: 스트림의 문자열을 주어진 크기로 잘라요.
strio = StringIO.new('foobar')
strio.pos    # => 0
strio.truncate(3)
strio.string # => "foo"
strio.pos    # => 0
strio.pos = 500
strio.truncate(0)
strio.string # => ""
strio.pos    # => 500

줄 번호 (Line Number)

스트림에는 줄 번호가 있어요. 처음에는 0이에요:

  • lineno 메서드가 줄 번호를 반환해요.
  • lineno= 메서드가 줄 번호를 설정해요.

줄 번호는 읽기에 영향을 받을 수 있어요(쓰기에는 안 받음). 일반적으로 레코드 구분자(기본값: "\n")를 읽을 때마다 줄 번호가 증가해요.

strio = StringIO.new(TEXT)
strio.string # => "First line\nSecond line\n\nFourth line\nFifth line\n"
strio.lineno # => 0
strio.gets   # => "First line\n"
strio.lineno # => 1
strio.getc   # => "S"
strio.lineno # => 1
strio.gets   # => "econd line\n"
strio.lineno # => 2
strio.gets   # => "\n"
strio.lineno # => 3
strio.gets   # => "Fourth line\n"
strio.lineno # => 4

위치를 설정해도 줄 번호는 바뀌지 않아요:

strio.pos = 0
strio.lineno # => 4
strio.gets   # => "First line\n"
strio.pos    # => 11
strio.lineno # => 5

줄 번호를 설정해도 위치는 바뀌지 않아요:

strio.lineno = 10
strio.pos    # => 11
strio.gets   # => "Second line\n"
strio.lineno # => 11
strio.pos    # => 23

열림/닫힘 (Open/Closed Streams)

새 스트림은 읽기 또는 쓰기 중 하나로 열려 있고, 둘 다로 열려 있을 수도 있어요. 읽기/쓰기 모드 참고.

이 메서드들은 새로 만들거나 다시 연 스트림의 읽기/쓰기 모드를 초기화해요:

  • ::new: 새 스트림을 반환해요.
  • ::open: 새 스트림을 블록에 넘겨요.
  • reopen: 스트림을 다시 초기화해요.

관련 메서드들:

  • close: 읽기와 쓰기 둘 다 닫아요.
  • close_read: 읽기만 닫아요.
  • close_write: 쓰기만 닫아요.
  • closed?: 읽기·쓰기 둘 다 닫혔는지 여부를 반환해요.
  • closed_read?: 읽기로 닫혔는지 여부를 반환해요.
  • closed_write?: 쓰기로 닫혔는지 여부를 반환해요.

BOM (바이트 순서 표시)

::new, ::open, reopen에 주는 문자열은 시작 부분에 선택적 BOM(바이트 순서 표시)을 포함할 수 있어요. BOM은 스트림의 인코딩에 영향을 줄 수 있어요.

BOM(제공된 경우):

  • 스트림의 문자열의 일부로 저장돼요.
  • 인코딩에는 즉시 영향을 주지 않아요.
  • 처음에는 스트림의 일부로 간주돼요.
utf8_bom = "\xEF\xBB\xBF"
string = utf8_bom + 'foo'
string.bytes               # => [239, 187, 191, 102, 111, 111]
strio.string.bytes.take(3) # => [239, 187, 191, 102, 111, 111]  # The BOM.
strio = StringIO.new(string, 'rb')
strio.string.bytes         # => [239, 187, 191, 102, 111, 111]   # BOM is part of the stored string.
strio.external_encoding    # => #<Encoding:BINARY (ASCII-8BIT)>  # Default for a binary stream.
strio.gets                 # => "\xEF\xBB\xBFfoo"                # BOM is part of the stream.

인스턴스 메서드 set_encoding_by_bom을 호출하면 저장된 BOM을 "활성화"할 수 있어요. 그 후 BOM은:

  • 여전히 스트림 문자열의 일부로 저장돼요.
  • 스트림의 인코딩을 결정해요(바뀌었을 수도 있어요).
  • 더 이상 스트림의 일부로 간주되지 않아요.
strio.set_encoding_by_bom
strio.string.bytes      # => [239, 187, 191, 102, 111, 111]  # BOM is still part of the stored string.
strio.external_encoding # => #<Encoding:UTF-8>               # The new encoding.
strio.rewind            # => 0
strio.gets              # => "foo"                           # BOM is not part of the stream.

기본 스트림 IO (Basic Stream IO)

기본 읽기 (Basic Reading)

이 인스턴스 메서드들로 스트림에서 읽을 수 있어요:

  • getbyte: 다음 바이트를 읽고 반환해요.
  • getc: 다음 문자를 읽고 반환해요.
  • gets: 다음 줄의 전체 또는 일부를 읽고 반환해요.
  • read: 스트림에 남은 데이터의 전체 또는 일부를 읽고 반환해요.
  • readlines: 스트림에 남은 데이터를 읽고 그 줄들의 배열을 반환해요.
  • Kernel#readline: gets와 같지만 스트림 끝이면 예외를 발생시켜요.

이 인스턴스 메서드들로 스트림을 반복할 수 있어요:

  • each_byte: 남은 각 바이트를 읽어 블록에 넘겨요.
  • each_char: 남은 각 문자를 읽어 블록에 넘겨요.
  • each_codepoint: 남은 각 코드포인트를 읽어 블록에 넘겨요.
  • each_line: 남은 각 줄의 전체 또는 일부를 읽어 블록에 넘겨요.

멀티스레드 앱에서 유용한 인스턴스 메서드:

  • pread: 스트림의 전체 또는 일부를 읽고 반환해요.

기본 쓰기 (Basic Writing)

위치를 옮기며 스트림에 쓸 수 있는 인스턴스 메서드들:

  • putc: 주어진 문자를 써요.
  • write: 주어진 객체들을 문자열로 써요.
  • Kernel#puts: 주어진 객체들을 문자열로 쓰되 각각 개행을 붙여요.

"unshift"하는 인스턴스 메서드들. 각각 현재 위치 앞에 쓰고 위치를 감소시켜서 쓴 데이터가 다음에 읽히게 해요:

  • ungetbyte: 주어진 바이트를 밀어 넣어요.
  • ungetc: 주어진 문자를 밀어 넣어요.

쓰기 메서드 하나 더:

  • truncate: 스트림의 문자열을 주어진 크기로 잘라요.

줄 IO (Line IO)

읽기:

  • gets: 다음 줄을 읽고 반환해요.
  • Kernel#readline: gets와 같지만 스트림 끝이면 예외를 발생시켜요.
  • readlines: 스트림에 남은 데이터를 읽고 줄 배열을 반환해요.
  • each_line: 남은 각 줄을 읽어 블록에 넘겨요.

쓰기:

  • Kernel#puts: 주어진 객체들을 쓰되 각각 개행을 붙여요.

문자 IO (Character IO)

읽기:

  • each_char: 남은 각 문자를 읽어 블록에 넘겨요.
  • getc: 다음 문자를 읽고 반환해요.

쓰기:

  • putc: 주어진 문자를 써요.
  • ungetc: 주어진 문자를 밀어 넣어요.

바이트 IO (Byte IO)

읽기:

  • each_byte: 남은 각 바이트를 읽어 블록에 넘겨요.
  • getbyte: 다음 바이트를 읽고 반환해요.

쓰기:

  • ungetbyte: 주어진 바이트를 밀어 넣어요.

코드포인트 IO (Codepoint IO)

읽기:

  • each_codepoint: 남은 각 코드포인트를 읽어 블록에 넘겨요.

Class Methods

new(string = '', mode = 'r+') → new_stringio

stringmode로 만들어진 새 StringIO 인스턴스를 반환해요. 더 이상 필요 없을 때 닫아야 해요:

strio = StringIO.new
strio.string        # => ""
strio.closed_read?  # => false
strio.closed_write? # => false
strio.close

string이 frozen이면 기본 mode'r'이에요:

strio = StringIO.new('foo'.freeze)
strio.string        # => "foo"
strio.closed_read?  # => false
strio.closed_write? # => true
strio.close

mode 인자는 유효한 접근 모드여야 해요. 문자열이거나 정수 상수일 수 있어요:

StringIO.new('foo', 'w+')
StringIO.new('foo', File::RDONLY)

관련: StringIO.open(StringIO 객체를 블록에 넘기고, 블록 종료 시 자동으로 닫음).

open(string = '', mode = 'r+') → new_stringio

StringIO.new(string, mode)을 호출해 새 StringIO 인스턴스를 만들어요.

블록이 없으면 새 인스턴스를 반환해요:

strio = StringIO.open # => #<StringIO>

블록을 주면 새 인스턴스와 함께 블록을 호출하고 블록의 값을 반환해요. 블록 종료 시 인스턴스를 닫아요:

StringIO.open('foo') {|strio| strio.string.upcase } # => "FOO"

관련: StringIO.new.

Instance Methods

binmode → self

self의 데이터 모드를 바이너리 모드로 설정해요. Data Mode 참고.

close → nil

읽기와 쓰기 둘 다 닫고 nil을 반환해요:

strio = StringIO.new
strio.closed? # => false
strio.close   # => nil
strio.closed? # => true
strio.read    # Raises IOError: not opened for reading
strio.write   # Raises IOError: not opened for writing

관련: StringIO#close_read, StringIO#close_write, StringIO.closed?.

close_read → nil

읽기만 닫고 nil을 반환해요. 쓰기 닫힘 설정은 그대로 남아요:

strio = StringIO.new
strio.closed_read?  # => false
strio.close_read    # => nil
strio.closed_read?  # => true
strio.closed_write? # => false
strio.read          # Raises IOError: not opened for reading

close_write → nil

쓰기만 닫고 nil을 반환해요. 읽기 닫힘 설정은 그대로 남아요:

strio = StringIO.new
strio.closed_write? # => false
strio.close_write   # => nil
strio.closed_write? # => true
strio.closed_read?  # => false
strio.write('foo')  # Raises IOError: not opened for writing

closed? → true or false

읽기·쓰기 둘 다 닫혔는지 여부를 반환해요:

strio = StringIO.new
strio.closed?     # => false  # Open for reading and writing.
strio.close_read
strio.closed?     # => false  # Still open for writing.
strio.close_write
strio.closed?     # => true   # Now closed for both.

closed_read? → true or false

읽기로 닫혔는지 여부를 반환해요:

strio = StringIO.new
strio.closed_read?   # => false
strio.close_read
strio.closed_read?   # => true

closed_write? → true or false

쓰기로 닫혔는지 여부를 반환해요:

strio = StringIO.new
strio.closed_write? # => false
strio.close_write
strio.closed_write? # => true

each_byte { |byte| ... } → self

블록을 주면 스트림의 남은 각 바이트마다 블록을 호출하고, 스트림을 파일 끝에 위치시키고 self를 반환해요:

bytes = []
strio = StringIO.new('hello')     #  Five 1-byte characters.
strio.each_byte {|byte| bytes.push(byte) }
strio.eof? # => true
bytes # => [104, 101, 108, 108, 111]
bytes = []
strio = StringIO.new('тест')      # Four 2-byte characters.
strio.each_byte {|byte| bytes.push(byte) }
bytes # => [209, 130, 208, 181, 209, 129, 209, 130]
bytes = []
strio = StringIO.new('こんにちは')  # Five 3-byte characters.
strio.each_byte {|byte| bytes.push(byte) }
bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175]

스트림 위치가 중요해요:

bytes = []
strio = StringIO.new('こんにちは')
strio.getc # => "こ"
strio.pos  # => 3  # 3-byte character was read.
strio.each_byte {|byte| bytes.push(byte) }
bytes      # => [227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175]

파일 끝에 있으면 블록을 호출하지 않아요:

strio.eof? # => true
strio.each_byte {|byte| fail 'Boo!' }
strio.eof? # => true

블록이 없으면 새 Enumerator를 반환해요.

each_char { |char| ... } → self

블록을 주면 스트림의 남은 각 문자마다 블록을 호출하고, 스트림을 파일 끝에 위치시키고 self를 반환해요:

chars = []
strio = StringIO.new('hello')
strio.each_char {|char| chars.push(char) }
strio.eof? # => true
chars      # => ["h", "e", "l", "l", "o"]
chars = []
strio = StringIO.new('тест')
strio.each_char {|char| chars.push(char) }
chars      # => ["т", "е", "с", "т"]
chars = []
strio = StringIO.new('こんにちは')
strio.each_char {|char| chars.push(char) }
chars      # => ["こ", "ん", "に", "ち", "は"]

스트림 위치가 중요해요:

chars = []
strio = StringIO.new('こんにちは')
strio.getc # => "こ"
strio.pos  # => 3  # 3-byte character was read.
strio.each_char {|char| chars.push(char) }
chars      # => ["ん", "に", "ち", "は"]

스트림 끝이면 블록을 호출하지 않아요:

strio.eof? # => true
strio.each_char {|char| fail 'Boo!' }
strio.eof? # => true

블록이 없으면 새 Enumerator를 반환해요.

each_codepoint { |codepoint| ... } → self

블록을 주면 스트림에서 연속되는 각 코드포인트마다 블록을 호출하고, 위치를 스트림 끝으로 설정하고 self를 반환해요.

각 코드포인트는 문자의 정수 값이에요:

codepoints = []
strio = StringIO.new('hello')
strio.each_codepoint {|codepoint| codepoints.push(codepoint) }
strio.eof? # => true
codepoints # => [104, 101, 108, 108, 111]
codepoints = []
strio = StringIO.new('тест')
strio.each_codepoint {|codepoint| codepoints.push(codepoint) }
codepoints # => [1090, 1077, 1089, 1090]
codepoints = []
strio = StringIO.new('こんにちは')
strio.each_codepoint {|codepoint| codepoints.push(codepoint) }
codepoints # => [12371, 12435, 12395, 12385, 12399]

블록이 없으면 새 Enumerator를 반환해요.

each_line(sep = $/, chomp: false) { |line| ... } → self

블록을 주면 스트림의 남은 각 줄마다 블록을 호출하고 self를 반환해요. 스트림 위치는 파일 끝에 남겨요.

인자 없음

인자 없이 호출하면 기본 레코드 구분자(전역 변수 $/, 초기값 "\n")로 줄을 읽어요.

strio = StringIO.new(TEXT)
strio.each_line {|line| p line }
strio.eof? # => true

Output:

"First line\n"
"Second line\n"
"\n"
"Fourth line\n"
"Fifth line\n"

인자 sep

문자열 인자 sep만 주면 그 문자열을 레코드 구분자로 써서 줄을 읽어요:

strio = StringIO.new(TEXT)
strio.each_line(' ') {|line| p line }

Output:

"First "
"line\nSecond "
"line\n\nFourth "
"line\nFifth "
"line\n"

인자 limit

정수 인자 limit만 주면 기본 레코드 구분자로 줄을 읽되, 각 줄의 크기(문자 수)를 주어진 limit으로 제한해요:

strio = StringIO.new(TEXT)
strio.each_line(10) {|line| p line }

Output:

"First line"
"\n"
"Second lin"
"e\n"
"\n"
"Fourth lin"
"e\n"
"Fifth line"
"\n"

인자 sep와 limit

seplimit 둘 다 주면 둘 다 적용돼요:

strio = StringIO.new(TEXT)
strio.each_line(' ', 10) {|line| p line }

Output:

"First "
"line\nSecon"
"d "
"line\n\nFour"
"th "
"line\nFifth"
" "
"line\n"

위치

위에서 말했듯 각 예시의 strio는 스트림 시작에 위치해 있어요. 하지만 다른 경우 위치는 어디든 있을 수 있어요:

strio = StringIO.new(TEXT)
strio.pos = 30 # Set stream position to character 30.
strio.each_line {|line| p line }

Output:

" line\n"
"Fifth line\n"

위치가 문자 경계에 있지 않아도 돼요:

s = 'こんにちは'  # Five 3-byte characters.
strio = StringIO.new(s)
strio.pos = 3   # At beginning of second character.
strio.each_line {|line| p line }
strio.pos = 4   # At second byte of second character.
strio.each_line {|line| p line }
strio.pos = 5   # At third byte of second character.
strio.each_line {|line| p line }

Output:

"んにちは"
"\x82\x93にちは"
"\x93にちは"

특별한 레코드 구분자

클래스 IO의 일부 메서드처럼, StringIO.each도 두 가지 특별한 레코드 구분자를 다뤄요. ''는 빈 줄로 구분되는 문단으로, nil은 전체를 한 번에 읽어요:

strio = StringIO.new(TEXT)
strio.each_line('') {|line| p line } # Read as paragraphs (separated by blank lines).

Output:

"First line\nSecond line\n\n"
"Fourth line\nFifth line\n"
strio = StringIO.new(TEXT)
strio.each_line(nil) {|line| p line } # "Slurp"; read it all.

Output:

"First line\nSecond line\n\nFourth line\nFifth line\n"

키워드 인자 chomp

chomp 키워드 인자를 true로 주면(기본값은 false), 각 줄에서 끝의 개행(있으면)을 제거해요:

strio = StringIO.new(TEXT)
strio.each_line(chomp: true) {|line| p line }

Output:

"First line"
"Second line"
""
"Fourth line"
"Fifth line"

블록이 없으면 새 Enumerator를 반환해요.

eof? → true or false

self가 스트림 끝에 위치했는지 여부를 반환해요:

strio = StringIO.new('foo')
strio.pos  # => 0
strio.eof? # => false
strio.read # => "foo"
strio.pos  # => 3
strio.eof? # => true
strio.close_read
strio.eof? # Raises IOError: not opened for reading

external_encoding → encoding or nil

문자열의 인코딩을 나타내는 Encoding 객체를 반환해요:

strio = StringIO.new('foo')
strio.external_encoding # => #<Encoding:UTF-8>

self가 문자열을 갖지 않고 쓰기 모드라면 nil을 반환해요:

strio = StringIO.new(nil, 'w+')
strio.external_encoding # => nil

getbyte → integer or nil

스트림에서 다음 정수 바이트(문자가 아니라)를 읽고 반환해요:

s = 'foo'
s.bytes       # => [102, 111, 111]
strio = StringIO.new(s)
strio.getbyte # => 102
strio.getbyte # => 111
strio.getbyte # => 111

스트림 끝이면 nil을 반환해요:

strio.eof?    # => true
strio.getbyte # => nil

바이트를 반환하지 문자를 반환하지 않아요:

s = 'Привет'
s.bytes
# => [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
strio = StringIO.new(s)
strio.getbyte # => 208
strio.getbyte # => 159

s = 'こんにちは'
s.bytes
# => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175]
strio = StringIO.new(s)
strio.getbyte # => 227
strio.getbyte # => 129

getc → character, byte, or nil

스트림에서 다음 문자(경우에 따라 바이트, 아래 참고)를 읽고 반환해요:

strio = StringIO.new('foo')
strio.getc # => "f"
strio.getc # => "o"
strio.getc # => "o"

스트림 끝이면 nil을 반환해요:

strio.eof? # => true
strio.getc # => nil

문자를 반환하지 바이트를 반환하지 않아요:

strio = StringIO.new('Привет')
strio.getc # => "П"
strio.getc # => "р"

strio = StringIO.new('こんにちは')
strio.getc # => "こ"
strio.getc # => "ん"

위 예시들에서 스트림은 문자의 시작에 위치해 있어요. 다른 경우엔 그렇지 않을 수 있어요:

strio = StringIO.new('こんにちは')  # Five 3-byte characters.
strio.pos = 3 # => 3     # At beginning of second character; returns character.
strio.getc    # => "ん"
strio.pos = 4 # => 4     # At second byte of second character; returns byte.
strio.getc    # => "\x82"
strio.pos = 5 # => 5     # At third byte of second character; returns byte.
strio.getc    # => "\x93"

gets(sep = $/, chomp: false) → string or nil

스트림에서 줄을 읽고 반환해요. 스트림 끝이면 nil을 반환해요.

부작용:

  • 스트림 위치를 읽은 바이트 수만큼 증가시켜요.
  • 반환값을 전역 변수 $_에 할당해요.

인자 없이 호출하면 기본 레코드 구분자(전역 변수 $/, 초기값 "\n")로 줄을 읽어요:

strio = StringIO.new(TEXT)
strio.pos  # => 0
strio.gets # => "First line\n"
strio.pos  # => 11
$_         # => "First line\n"
strio.gets # => "Second line\n"
strio.read # => "\nFourth line\nFifth line\n"
strio.eof? # => true
strio.gets # => nil

strio = StringIO.new('Привет')  # Six 2-byte characters
strio.pos  # => 0
strio.gets # => "Привет"
strio.pos  # => 12

인자 sep: 문자열 인자 sep만 주면 그 문자열을 레코드 구분자로 써서 줄을 읽어요.

strio = StringIO.new(TEXT)
strio.gets(' ') # => "First "
strio.gets(' ') # => "line\nSecond "
strio.gets(' ') # => "line\n\nFourth "

인자 limit: 정수 인자 limit만 주면 기본 레코드 구분자로 줄을 읽되, 각 줄의 크기(문자 수)를 limit으로 제한해요.

strio = StringIO.new(TEXT)
strio.gets(10) # => "First line"
strio.gets(10) # => "\n"
strio.gets(10) # => "Second lin"
strio.gets(10) # => "e\n"

인자 sep와 limit: 둘 다 주면 둘 다 적용돼요.

strio = StringIO.new(TEXT)
strio.gets(' ', 10) # => "First "
strio.gets(' ', 10) # => "line\nSecon"
strio.gets(' ', 10) # => "d "

위치: 위에서 말했듯 gets는 스트림의 다음 줄을 읽고 반환해요. 위치가 어디든 있을 수 있어요:

strio = StringIO.new(TEXT)
strio.pos = 12
strio.gets # => "econd line\n"

위치가 문자 경계에 있지 않아도 돼요:

strio = StringIO.new('Привет') # Six 2-byte characters.
strio.pos = 2                  # At beginning of second character.
strio.gets # => "ривет"
strio.pos = 3                  # In middle of second character.
strio.gets # => "\x80ивет"

특별한 레코드 구분자: IO 클래스의 일부 메서드처럼 gets도 두 가지 특별한 레코드 구분자를 다뤄요:

strio = StringIO.new(TEXT)
strio.gets('')  # Read "paragraph" (up to empty line).
# => "First line\nSecond line\n\n"

strio = StringIO.new(TEXT)
strio.gets(nil) # "Slurp": read all.
# => "First line\nSecond line\n\nFourth line\nFifth line\n"

키워드 인자 chomp: chomptrue로 주면(기본값 false), 반환된 줄에서 끝의 개행(있으면)을 제거해요.

strio = StringIO.new(TEXT)
strio.gets              # => "First line\n"
strio.gets(chomp: true) # => "Second line"

internal_encoding → nil

nil을 반환해요. IO와의 호환용이에요.

lineno → current_line_number

self의 현재 줄 번호를 반환해요. Line Number 참고.

lineno = new_line_number → new_line_number

self의 현재 줄 번호를 주어진 값으로 설정해요. Line Number 참고.

pos → stream_position

현재 위치(바이트)를 반환해요. Position 참고.

pos = new_position → new_position

현재 위치(바이트)를 설정해요. Position 참고.

pread(maxlen, offset) → string

IO#pread 참고.

putc(obj) → obj

IO#putc 참고.

read([length [, outbuf]]) → string, outbuf, or nil

IO#read 참고.

readlines(sep=$/, chomp: false) → array

IO#readlines 참고.

reopen(other, mode = 'r+') → self

주어진 other(문자열 또는 StringIO)와 mode로 스트림을 다시 초기화해요. IO.new 참고:

StringIO.open('foo') do |strio|
  p strio.string
  strio.reopen('bar')
  p strio.string
  other_strio = StringIO.new('baz')
  strio.reopen(other_strio)
  p strio.string
  other_strio.close
end

Output:

"foo"
"bar"
"baz"

rewind → 0

현재 위치와 줄 번호를 0으로 설정해요. Position과 Line Number 참고.

seek(offset, whence = SEEK_SET) → 0

주어진 정수 offset(바이트)로 위치를 설정해요. whence는 주어진 상수에 상대적이에요. IO#seek 참고.

set_encoding(ext_enc, [int_enc[, opt]]) → strio

StringIO의 인코딩을 ext_enc으로 지정해요. ext_enc이 nil이면 기본 외부 인코딩을 사용해요. 2번째 인자 int_enc와 선택적 해시 opt 인자는 무시돼요. IO와의 API 호환용이에요.

set_encoding_by_bom → strio or nil

문자열의 BOM(바이트 순서 표시)에 따라 인코딩을 설정해요. BOM이 있으면 self를, 없으면 nil을 반환해요.

size → integer

self의 문자열 크기(바이트)를 반환해요:

StringIO.new('hello').size     # => 5  # Five 1-byte characters.
StringIO.new('тест').size      # => 8  # Four 2-byte characters.
StringIO.new('こんにちは').size # => 15 # Five 3-byte characters.

string → string

내부 문자열을 반환해요:

StringIO.open('foo') do |strio|
  p strio.string
  strio.string = 'bar'
  p strio.string
end

Output:

"foo"
"bar"

string = other_string → other_string

저장된 문자열을 other_string으로 바꾸고 위치를 0으로 설정한 뒤 other_string을 반환해요:

StringIO.open('foo') do |strio|
  p strio.string
  strio.string = 'bar'
  p strio.string
end

Output:

"foo"
"bar"

sync → true

true를 반환해요. 다른 스트림 클래스와의 호환용으로만 구현됐어요.

truncate(integer) → 0

버퍼 문자열을 최대 integer 바이트로 잘라요. 스트림이 쓰기용으로 열려 있어야 해요.

ungetbyte(byte) → nil

8비트 바이트를 스트림에 다시 밀어 넣어요("unshift"). Byte IO 참고.

ungetc(character) → nil

문자나 정수를 스트림에 다시 밀어 넣어요("unshift"). Character IO 참고.

write(string, ...) → integer

주어진 문자열을 내부 버퍼 문자열에 추가해요. 스트림이 쓰기용으로 열려 있어야 해요. 인자가 문자열이 아니면 to_s로 변환해요. 쓴 바이트 수를 반환해요. IO#write 참고.