17076

[code]

    (with-open-file (s "test.txt" :direction :input)
     (let ((buf (make-string (file-length s))))
      (read-sequence buf s)
      buf))
    
[/code]

↑のようにread-
sequenceで日本語で書かれたファイル(ここではtest.txt)を読み込んで文字列に格納すると，その文字列にNULL文字が入ってしまいます．  
例えばtest.txtの中身が

[code]

    日本語で書かれた文章
    
[/code]

であった場合，格納される文字列は

[code]

    "日本語で書かれた文章^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@"
    
[/code]

となってしまいます。

格納する変数のサイズの指定が悪いと思うのですが，どうすればピッタリ格納出来るでしょうか？  
ちなみに処理系はsbclでファイルの文字コードはUTF-8です。


CC BY-SA 3.0
2015-09-29T10:38:39.613
17061
2015-09-30T10:24:38.450
2015-09-30T03:37:10.450
12467
12467
question
common-lisp
ファイルを読み込んで文字列に格納すると終端にNULL文字が入る


失礼しました、上のコメントは忘れて下さい(しばらくしたら削除します)。

文字列をバイト列に変換して表示してみると、確かに 0x00(NULL) が最後に追加されていますね。

[code]

    (with-open-file (s "text.txt" :direction :input)
      (let ((buf (make-string (file-length s))))
        (read-sequence buf s)
        (sb-ext:string-to-octets buf)))
    =>
    #(230 151 165 230 156 172 232 170 158 227 129 167 230 155 184 227 129 139 227
      130 140 227 129 159 230 150 135 231 171 160 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
      0 0 0)
    
[/code]

とはいうものの、`buf` を文字列として扱う場合、`0x00` は null
terminator(文字列の終端)になりますので、特に問題はないかと思います。

ご要望通りにサイズを合わせたい場合はバイト列を使います。

[code]

    (with-open-file (s "text.txt" :direction :input :element-type '(unsigned-byte 8))
      (let ((buf (make-sequence '(vector (unsigned-byte 8)) (file-length s))))
        (read-sequence buf s)
        buf))
    =>
    #(230 151 165 230 156 172 232 170 158 227 129 167 230 155 184 227 129 139 227
      130 140 227 129 159 230 150 135 231 171 160)
    
[/code]

これを文字列に戻すには `sb-ext:octets-to-string` を使います。

[code]

    (with-open-file (s "text.txt" :direction :input :element-type '(unsigned-byte 8))
      (let ((buf (make-sequence '(vector (unsigned-byte 8)) (file-length s))))
        (read-sequence buf s)
        (sb-ext:octets-to-string buf :external-format :utf-8)))
    =>
    "日本語で書かれた文章"
    
[/code]

`read-sequence` に文字列のバッファを渡すと、内部でパディング領域を追加してしまう様です。

**追記**

少し不思議なのは、`read-sequence` の実行後に `buf` のサイズが増えていることなのです。

[code]

    (with-open-file (s "test.txt" :direction :input)
      (let ((buf (make-string (file-length s))))
        (format t "~D ~a~%" (length (sb-ext:string-to-octets buf)) (sb-ext:string-to-octets buf))
        (read-sequence buf s)
        (format t "~D ~a~%" (length (sb-ext:string-to-octets buf)) (sb-ext:string-to-octets buf))))
    =>
    30 #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
    50 #(230 151 165 230 156 172 232 170 158 227 129 167 230 155 184 227 129 139
         227 130 140 227 129 159 230 150 135 231 171 160 0 0 0 0 0 0 0 0 0 0 0 0 0
         0 0 0 0 0 0 0)
    
[/code]

30 byte(UTF-8 10文字)から 50 byte に増えていて、`0x00` が 20 byte 追加されています。

`initial-element` を指定しても同様です。

[code]

    (with-open-file (s "test.txt" :direction :input)
     (let ((buf (make-string (file-length s) :initial-element #\Space)))
      (format t "~D ~a~%" (length (sb-ext:string-to-octets buf)) (sb-ext:string-to-octets buf))
      (read-sequence buf s)
      (format t "~D ~a~%" (length (sb-ext:string-to-octets buf)) (sb-ext:string-to-octets buf))))
    =>
    30 #(32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32
           32 32 32 32 32 32)
    50 #(230 151 165 230 156 172 232 170 158 227 129 167 230 155 184 227 129 139
         227 130 140 227 129 159 230 150 135 231 171 160 32 32 32 32 32 32 32 32 32
         32 32 32 32 32 32 32 32 32 32 32)
    
[/code]

**更に追記**

SBCL の `read-sequence` の実装を確認してみました。

[code]

    "日本語で書かれた文章"/UTF-8 => 30 byte
    "日本語で書かれた文章"/UTF-8 => 10 文字
    
[/code]

byte 単位で見れば 30 byte なのですが、UTF-8 encoding で見れば 10 文字となります。  
`read-sequence` が何をしているかというと、

[code]

    make-string 直後の buf => #[0 0 ...] (30 byte)
    日 => E6 97 A5 => 230 151 165
    
[/code]

まず、日(230 151 165) というシーケンスを buf の最初の要素(`buf[0]`)と置き換えています。そして、"本"、"語"、…と続きます。  
つまり、最終的に `30 byte - 10 byte(10 文字分) + 30 byte(UTF-8 10文字) = 50 byte`
に置き換わることになります。

では、test.txt の内容が ASCII 文字のみの場合はどうなるのでしょうか。

[code]

    alphabet.txt => "Hello World!"
    
    (with-open-file (s "alphabet.txt" :direction :input)
     (let ((buf (make-string (file-length s) :initial-element #\Space)))
      (format t "~D ~a~%" (length (sb-ext:string-to-octets buf)) (sb-ext:string-to-octets buf))
      (read-sequence buf s)
      (format t "~D ~a~%" (length (sb-ext:string-to-octets buf)) (sb-ext:string-to-octets buf))))
    =>
    12 #(32 32 32 32 32 32 32 32 32 32 32 32)
    12 #(72 101 108 108 111 32 87 111 114 108 100 33)
    
[/code]

バイト列のサイズは作成時と同じになっています(1 文字 = 1 byte)。

なので、wide char を含む文字列の場合は(結果的に)文字列が拡張されてしまう、という事になりますでしょうか。おそらく Lisp
処理系ごとで挙動が違うのだと思いますが。。。


CC BY-SA 3.0
2015-09-29T14:58:25.527
17069
2015-09-30T04:45:48.540
2015-09-30T04:45:48.540
17061
answer


現象として、日本語のファイルを読んだらNull文字でパディングされたように見えているだけで、実際に起きていることは違います。

### make-string

`make-string`は指定した長さの文字列を作りますが、未指定の場合の初期値は処理系によって違います。  
SBCLの場合は、Null文字であった、というだけです。

[code]

    (make-string 10)
    ;=> "^@^@^@^@^@^@^@^@^@^@" 
    
[/code]

### read-sequence

`read-sequence`は、用意されたシーケンス(文字列もシーケンス)に読み込んだものを植え付けます。

この2つが合さって、Null文字でパディングされているように見えた、ということになります。  
日本語かどうかと、何でパディングされるか、は無関係ということになります。

[code]

    (with-open-file (s "/tmp/j.txt" :direction :input)
     (let ((buf (make-string (file-length s) :initial-element #\Space))); 初期値に空白を与えた場合
      (read-sequence buf s)
      buf))
    ;=> "日本語で書かれた文章                    " 
    
[/code]

## 日本語のテキストを読み込むには

日本語のテキストを読み書きするのに、オクテットに変換することはまず無く、大抵は、単にエンコーディングを合わせて`read-line`するだけです。

[code]

    (with-open-file (s "test.txt" :direction :input :external-format :utf-8)
      (let ((buf (read-line s)))
        buf))
    ;=> "日本語で書かれた文章" 
    
[/code]

なお、複数行の場合はファイル終端まで繰り返します。

[code]

    (with-open-file (s "test.txt" :direction :input :external-format :utf-8)
      (loop :for line := (read-line s nil) :while line
            :collect line))
    ;=> ("日本語で書かれた文章") 
    
[/code]

もしくは read-sequence で一気に読み込みます

[code]

    (with-open-file (s "/tmp/j.txt"
                       :direction :input
                       :element-type '(unsigned-byte 8)
                       :external-format :utf-8)
      (let ((buf (make-array (file-length s) :element-type '(unsigned-byte 8))))
        (read-sequence buf s)
        (sb-ext:octets-to-string buf :external-format :utf-8)))
    ;=> "日本語で書かれた文章
    
[/code]

; "


CC BY-SA 3.0
2015-09-29T19:45:28.010
17076
2015-09-30T10:24:38.450
2015-09-30T10:24:38.450
3510
3510
17061
answer