首页 > 解决方案 > C# utf 字符串转换,无法正确显示的字符被转换为“未知字符” - 如何防止这种情况?

问题描述

我有两个从 Windows 文件名派生的字符串,其中包含在 Windows 中无法正确显示的 unicode 字符(它们只显示方框“未知字符”而不是正确的字符)。但是文件名是有效的,并且这些文件在操作系统中没有问题存在,这意味着我需要能够正确准确地处理它们。

我以通常的方式加载文件名:

string path = @"c:\folder";
foreach (FileInfo file in DirectoryInfo.EnumerateFiles(path))
{
    string filename = file.FullName;
}

但为了解释这个问题,这些是我遇到问题的两个文件名:

string filename1 = "\ude18.txt";
string filename2 = "\udca6.txt";

两个字符串,两个文件名,一个 unicode 字符加上一个扩展名,两者都不同。到目前为止这很好,我可以毫无问题地读取和写入这些文件,但是我需要将这些字符串存储在 sqlite db 中,然后再检索它们。我所做的每一次尝试都会导致这两个字符都被更改为“未知字符”,因此原始数据丢失了,我无法再区分这两个字符串。起初我认为这是一个 sqlite 问题,并且我确保我的数据库是 UTF16 格式的,但事实证明是 c# 到 UTF16 的转换导致了问题。

如果我完全忽略 sqlite,只是尝试手动将这些字符串转换为 UTF16(或任何其他编码),这些字符将转换为“未知字符”并且原始数据会丢失。如果我这样做:

System.Text.Encoding enc = System.Text.Encoding.Unicode;
string filename1 = "\ude18.txt";
string filename2 = "\udca6.txt";
byte[] name1Bytes = enc.GetBytes(filename1);
byte[] name2Bytes = enc.GetBytes(filename2);

然后我检查字节数组'name1Bytes'和'name2Bytes'它们都是相同的。我可以看到这两种情况下的 unicode 字符都已转换为一对字节 253 和 255 - 未知字符。果然当我转换回来

string newFilename1 = enc.GetString(name1Bytes);
string newFilename2 = enc.GetString(name2Bytes);

每种情况下的原始 unicode 字符都会丢失,并替换为菱形问号符号。我完全丢失了原始文件名。

似乎这些编码转换依赖于能够显示字符的系统字体,这是一个问题,因为这些字符串已经作为文件名存在,并且无法更改文件名。在将这些数据发送到 sqlite 时,我需要以某种方式保存这些数据,当它发送到 sqlite 时,它​​会经历一个到 UTF16 的转换过程,而正是这种转换,我需要它在不丢失数据的情况下生存下来。

标签: c#stringsqliteunicode

解决方案


如果将 achar转换为 a int,则会绕过 Unicode 转换机制获得数值:

foreach (char ch in filename1)
{
    int i = ch; // 0x0000de18 == 56856 for the first char in filename1
    ... do whatever, e.g., create an int array, store it as base64
}

事实证明这也有效,并且可能更优雅:

foreach (int ch in filename1)
{
    ...
}

所以也许是这样的:

string Encode(string raw)
{
    byte[] bytes = new byte[2 * raw.Length];
    int i = 0;
    foreach (int ch in raw)
    {
        bytes[i++] = (byte)(ch & 0xff);
        bytes[i++] = (byte)(ch >> 8);
    }

    return Convert.ToBase64String(bytes);
}

string Decode(string encoded)
{
    byte[] bytes = Convert.FromBase64String(encoded);
    char[] chars = new char[bytes.Length / 2];
    for (int i = 0; i < chars.Length; ++i)
    {
        chars[i] = (char)(bytes[i * 2] | (bytes[i * 2 + 1] << 8));
    }

    return new string(chars);
}

推荐阅读