是否有任何(可逆)方式(在 c# 中)将字符串转换为更小的字符串,当我说更小时,我的意思是 "with reduced length"?

Is there any (invertible) way (in c#) to convert a string into a smaller one, and when I say smaller I mean "with reduced length"?

让我解释一下:在我的用例中,系统为我提供了许多大小可以变化的字符串(字符数;长度),有时它可能非常大!问题是我必须将这个字符串保存在“SQL 服务器”数据库的 table 的列中,坏消息是我不允许在这个数据库中进行任何迁移,好消息是该列已经具有类型 nvarchar(max).

我之前做了一些研究并按照以下post使用“Gzip”和“Brotli”编写了一个数据压缩器。

https://khalidabuhakmeh.com/compress-strings-with-dotnet-and-csharp

var value = "hello world";
var level = CompressionLevel.SmallestSize;

var bytes = Encoding.Unicode.GetBytes(value);
await using var input = new MemoryStream(bytes);
await using var output = new MemoryStream();

// GZipStream with BrotliStream
await using var stream = new GZipStream(output, level);

await input.CopyToAsync(stream);

var result = output.ToArray();
var resultString = Convert.ToBase64String(result);

实施转换方法后,我创建了生成不同大小(长度)的随机字符串的测试以验证压缩器的性能,此时我注意到以下内容。 “Gzip”和“Brotli”都首先转换为 byte[](字节数组),然后应用压缩,这会按预期提供减小大小的结果向量(字节数组),但随后将结果(byte[])转换为base 64 字符串,在 100% 的测试中,它的字符(长度)比初始字符串多。

我的随机字符串生成器:

var rd_char = new Random();
var rd_length = new Random();
var wordLength = rd_length.Next(randomWordParameters.WordMinLength, randomWordParameters.WordMaxLength);
var sb = new StringBuilder();
int sourceNumber;
for (int i = 0; i < wordLength; i++)
{
    sourceNumber = rd_char.Next(randomWordParameters.CharLowerBound, randomWordParameters.CharUpperBound);
    sb.Append(Convert.ToChar(sourceNumber));
}
var word = sb.ToString();

我的示例字符串并不完全包含手头案例的完美表示,但我相信它们已经足够好了。这是字符串生成器方法,实际上它在给定的大小范围内生成完全随机的字符串,我在测试中使用了从传递给 Convert.ToChar() 方法的 33 ~ 127 个值提供的字符。系统提供的字符串是JSON格式,实际上是一个url列表(有几万个url),url通常是随机的字符序列,所以我尽量随机生成字符串.

事实是,考虑到我尝试在数据库中保存最初(压缩前)大于列中允许的最大大小(长度)的字符串的情况,在数据库中保存时,进入 table 列的“数据”是压缩后生成的结果“base 64”字符串,而不是缩小大小的向量(字节数组),我相信数据库会拒绝字符串(base 64) 因为它的长度(字符数)大于原始字符串的长度。

所以这是我的问题,是否有任何(可逆)方法可以将字符串转换为更小的字符串,当我说更小时,我的意思是“缩短长度”?看来“Gzip”或“Brotli”都不能解决问题。

PS:我一定要多次强调“长度”这个词,以明确在文本中我说的是字符数而不是内存中的长度,因为我在之前阅读的几个论坛中注意到,这种混乱使得很难得出结论。

NVARCHAR(MAX) 类型的列的最大存储空间为 2 GB。

由于 NVARCHAR 每个字符使用 2 个字节,因此大约是。 10 亿个字符。

所以我认为你实际上不需要进行压缩,如果问题是检索数据时的性能,那么你可以使用服务器端缓存系统。

压缩算法正在利用输入流中的重复模式。典型的 URL 中没有太多重复,因此压缩单个 URL 不太可能产生比原始表示短得多的表示。如果 URL 根本没有重复模式(如果它接近随机字符串),压缩算法将产生比输入更大的输出。

下面是此行为的演示,使用 Encoding.UTF8 to convert the URLs to bytes, and the Encoding.Latin1 将压缩字节转换为字符串:

static string Compress(string value)
{
    byte[] bytes = Encoding.UTF8.GetBytes(value);
    using var input = new MemoryStream(bytes);
    using var output = new MemoryStream();
    using (var gz = new GZipStream(output, CompressionLevel.SmallestSize))
        input.CopyTo(gz);
    byte[] result = output.ToArray();
    return Encoding.Latin1.GetString(result);
}

static string Decompress(string compressedValue)
{
    byte[] bytes = Encoding.Latin1.GetBytes(compressedValue);
    using var input = new MemoryStream(bytes);
    using var output = new MemoryStream();
    using (var gz = new GZipStream(input, CompressionMode.Decompress))
        gz.CopyTo(output);
    byte[] result = output.ToArray();
    return Encoding.UTF8.GetString(result);
}

我用了三个相当长的 non-repetitive URLs 来测试:

string[] urls = new string[]
{
    "
    "https://github.com/dotnet/runtime/blob/2d4f2d0c8f60d5f49e39f3ddbe1824648ee2b306/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs#L77",
    "https://sharplab.io/#v2:CYLg1APgAgTAjAWAFBQMwAJabgdmQb2XWMwygBZ0BZAQwEsA7ACgEoiTCkTvsBOJgEQAJAKYAbMQHt0Ad0kAnMcAEsA3O2IBfZJqA===",
};
foreach (var original in urls)
{
    Console.WriteLine($"Original:     {original.Length} chars, {original.Substring(0, 50)}...");
    var compressed = Compress(original);
    double compression = (original.Length - compressed.Length) / (double)original.Length;
    Console.WriteLine($"Compressed:   {compressed.Length} chars, compression: {compression:0.00%}");
    var decompressed = Decompress(compressed);
    Console.WriteLine($"Decompressed: {decompressed.Length} chars");
    Console.WriteLine($"Successful:   {decompressed == original}");
    Console.WriteLine();
}

输出:

Original:     145 chars, 
Compressed:   133 chars, compression: 8.28%
Decompressed: 145 chars
Successful:   True

Original:     148 chars, https://github.com/dotnet/runtime/blob/2d4f2d0c8f6...
Compressed:   143 chars, compression: 3.38%
Decompressed: 148 chars
Successful:   True

Original:     128 chars, https://sharplab.io/#v2:CYLg1APgAgTAjAWAFBQMwAJabg...
Compressed:   141 chars, compression: -10.16%
Decompressed: 128 chars
Successful:   True

Try it on Fiddle.

三个 URL 中的两个在压缩后变得略短,但第三个 URL 却变得臃肿。

您可以在数据库中存储压缩值或原始值,具体取决于哪个更短。您可以在存储值前加上一些标记,例如 'C''U',以便您知道它是压缩的还是未压缩的。