C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

UTF-8の全角文字読み込み

[トピック内 9 記事 (1 - 9 表示)]  << 0 >>

■93883 / inTopicNo.1)  UTF-8の全角文字読み込み
  
□投稿者/ tani (1回)-(2020/02/17(Mon) 09:32:09)

分類:[C#] 

はじめまして。

以下のようなコードでUTF-8ファイルから1文字ずつ読み込んでShift-JISファイルへ変換する処理を行っています。
UTF-8に全角文字が含まれる場合の動作について分からないのでご教示いただきたいです。

-----------------------------------------------------
rs = new StreamReader(UTF8Path, Encoding.UTF8);
ws = new StreamWriter(SJISPath, false, Encoding.GetEncoding("Shift_JIS"));
char[] c = new char[1];
while (rs.Read(c, 0, 1) > 0)
{
// 改行コード等の処理
ws.Write(c);
}
-----------------------------------------------------

UTF-8では全角ひらがなは3バイトだと思いますが、C#のchar型のサイズは2バイトです。
上記コードをデバッグすると全角ひらがな1文字がc[0]に正しく格納されます。
これはなぜなのでしょうか?
また、「&#159296;」等の文字が含まれる場合は一度にc[0]に格納されず、2回に分けて格納されているようです。
(出力ファイルもUTF-8にすると「&#159296;」が正しく出力されています)

UTF-8ファイルから1文字ずつ取得する処理としてchar型を使用することは問題ないのでしょうか?
引用返信 編集キー/
■93884 / inTopicNo.2)  Re[1]: UTF-8の全角文字読み込み
□投稿者/ furu (18回)-(2020/02/17(Mon) 09:41:26)
No93883 (tani さん) に返信
C#のchar型はUTF-16なので
全角ひらがな1文字は2バイトでc[0]に正しく格納されます。

 UTF-8ファイル → UTF-16 → Shift-JISファイル

UTF-16で「&#159296;」は4バイトなので
2回に分けて格納されます。
引用返信 編集キー/
■93886 / inTopicNo.3)  Re[2]: UTF-8の全角文字読み込み
□投稿者/ tani (2回)-(2020/02/17(Mon) 10:49:15)
No93884 (furu さん) に返信

furuさん、返信ありがとうございます。
全角ひらがなはUTF-8では3バイトですが、C#上(メモリ上)ではUTF-16で扱われるため2バイトとなるということでよろしいでしょうか?
また、&#159296;(utf8mb4の文字)はC#上でも4バイトのため、2回に分けて格納されるということでしょうか?
(Readメソッドは1文字取得だと思いますが、1文字分(2バイト分)が自動で分割して、出力時に結合してくれるイメージでしょうか)


引用返信 編集キー/
■93887 / inTopicNo.4)  Re[3]: UTF-8の全角文字読み込み
□投稿者/ Hongliang (961回)-(2020/02/17(Mon) 11:02:22)
> 全角ひらがなはUTF-8では3バイトですが、C#上(メモリ上)ではUTF-16で扱われるため2バイトとなるということでよろしいでしょうか?
はい。

> また、&#159296;(utf8mb4の文字)はC#上でも4バイトのため、2回に分けて格納されるということでしょうか?
はい。

> (Readメソッドは1文字取得だと思いますが、1文字分(2バイト分)が自動で分割して、出力時に結合してくれるイメージでしょうか)
文字という単位は頭から捨てたほうが良いと思います。charという単位で考えるべきです。
StreamReaderは内部でバッファリングを行っており、あらかじめ十分な長さをもとになるストリームから読み取り、char配列に変換して内部に保存しています。
Readメソッドが呼ばれたら、その保存しているchar配列の中から必要な長さだけコピーするだけというイメージです。

出力時に結合というのはよくわかりませんが、最終的に出すのがShift_JISならサロゲートペアが必要な文字はどうせ「?」に変換されるだけなので問題ではないですね。

// そもそも、char単位で行うと性能があまり出ないのに分かりやすくなるとか言うこともなく、サロゲートペアなどの問題も出てくるので、敢えてやることではないと思いますが。
引用返信 編集キー/
■93888 / inTopicNo.5)  Re[1]: UTF-8の全角文字読み込み
□投稿者/ 魔界の仮面弁士 (2558回)-(2020/02/17(Mon) 11:10:29)
No93883 (tani さん) に返信
> これはなぜなのでしょうか?

C# では、文字集合として Unicode をベースにしていますが、
その符号化方式は UTF-8 ではありません。


Windows が使う Unicode では、エンコードとして UCS-2 あるいは UTF-16 をベースとしています。
これは細かい事を端折っていえば、すべての文字が 2 バイト(16bit) で表されるエンコーディングであり、
.NET の System.String や System.Char もコレです。



using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;

class Program
{
    static void Main()
    {
        // 'U+0000〜U+007F の範囲の文字は、UTF-8 では 1 バイトとなる
        WriteStringLength("\u0043");
        WriteStringLength("\u0023");
        Debug.WriteLine("");

        // 'U+0080〜U+07FF の範囲の文字は、UTF-8 では 2 バイトとなる
        WriteStringLength("\u03B2");
        WriteStringLength("\u0414");
        Debug.WriteLine("");

        // 'U+0800〜U+FFFF の範囲の文字は、UTF-8 では 3 バイトとなる
        WriteStringLength("\u3042");
        WriteStringLength("\u4f60");
        WriteStringLength("\uFF8F");
        Debug.WriteLine("");

        // 'U+10000〜U+1FFFF の範囲の文字は、UTF-8 では 4 バイトとなる
        WriteStringLength("\U0001F60A");
        WriteStringLength("\U00020BB7");
        WriteStringLength("\U00020B9F");
        Debug.WriteLine("");
    }

    static void WriteStringLength(string s)
    {
        Debug.WriteLine(String.Format(
            "文字=[{0}], 文字数={1}, Char数={2}、UTF-8={3}, UTF-16={4}",
            s,
            new StringInfo(s).LengthInTextElements,
            s.ToCharArray().Length,
            Encoding.GetEncoding("UTF-8").GetByteCount(s),
            Encoding.GetEncoding("UTF-16").GetByteCount(s)
        ));
    }
}

引用返信 編集キー/
■93889 / inTopicNo.6)  Re[3]: UTF-8の全角文字読み込み
□投稿者/ 魔界の仮面弁士 (2559回)-(2020/02/17(Mon) 11:24:01)
2020/02/17(Mon) 11:24:34 編集(投稿者)

No93886 (tani さん) に返信
> また、&#159296;(utf8mb4の文字)はC#上でも4バイトのため、2回に分けて格納されるということでしょうか?

これは「くさかんむり」に「細」な文字ですよね。
C# ではこのように書けます。

// 下記の 2 つは同一の文字列を意味する
string s1 = "\U00026E40";
string s2 = "\uD85B\uDE40";


10進数で 159296 ということは
16進数で 26E40
2 進数で 100110111001000000
と表せます。


No93888 において、U+10000〜U+1FFFFF の範囲の Unicode 文字が
4 バイトで表されると書きましたが、この変換規則については、
下記の「UCSからUTF-8への変換法」などが参考になるかと思います。
http://nomenclator.la.coocan.jp/unicode/ucs_utf.htm
引用返信 編集キー/
■93891 / inTopicNo.7)  Re[4]: UTF-8の全角文字読み込み
□投稿者/ tani (3回)-(2020/02/17(Mon) 11:31:27)
No93887 (Hongliang さん) に返信

Hongliangさん、返信ありがとうございます。
分かりやすい説明のおかげで動作がイメージできました。
char型を使用することに問題がないということで安心しました。
あえて使用することはないと思いますが、諸事情のため質問させていただきました。
引用返信 編集キー/
■93892 / inTopicNo.8)  Re[2]: UTF-8の全角文字読み込み
□投稿者/ tani (4回)-(2020/02/17(Mon) 11:34:03)
No93888 (魔界の仮面弁士 さん) に返信

魔界の仮面弁士さん、返信ありがとうございます。
また、サンプルコードもありがとうございます。
サンプルコードを実行して、各文字の扱いの理解が深まりました。
大変感謝しております。
教えていただいたサイトも読んで、さらに理解を深めたいと思います。
ありがとうございました。
引用返信 編集キー/
■93893 / inTopicNo.9)  Re[3]: UTF-8の全角文字読み込み
□投稿者/ tani (5回)-(2020/02/17(Mon) 11:35:07)
おかげさまで解決できました。
皆様ありがとうございました。
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ