让我解释一下:在我的用例中,一个系统会给我许多大小不同(字符数;长度)的字符串,有时它会非常大!问题是我必须将这个字符串保存在"SQL Server"数据库表的一列中,坏消息是我不允许在此数据库中进行任何迁移,好消息是该列已经有了类型nvarchar(max).

我之前做过一些研究,并在下面的帖子中使用"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"都首先转换为字节[](字节数组),然后应用压缩,这会产生一个按预期大小缩小的结果向量(字节数组),但随后将结果(字节[])转换为一个基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();

我的示例字符串并不完全包含手头 case 的完美表示,但我相信它们已经足够好了.这里是字符串生成器方法,实际上它在给定的大小范围内生成完全随机的字符串,我在测试中使用了33~127个值中提供的字符​​传递给皈依者.ToChar()方法.系统提供的字符串是JSON格式的,实际上它们是URL列表(有上万个URL),URL通常具有随机字符序列,因此我try 尽可能随机地生成字符串.

事实是,考虑到我试图在数据库中保存一个字符串的情况,该字符串最初(在压缩之前)大于列中允许的最大大小(长度),在数据库中保存时,出现问题的表的列中的"数据"是压缩后生成的结果"base 64"字符串,而不是缩减的大小向量(字节数组),我相信数据库会拒绝这个字符串(以64为基数),因为它的长度(以字符数计)大于原始字符串的长度.

所以我的问题是,有没有(可逆的)方法把一个字符串转换成一个更小的字符串,当我说更小的时候,我的意思是"长度减少"?看来"Gzip"或"Brotli"并不能解决这个问题.

附言:我多次强调"长度"一词,以清楚地表明,在这一点上,我指的是文字的数量,而不是记忆中的长度,因为我在之前阅读的几个论坛中注意到,这种混淆使得很难得出结论.

推荐答案

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

下面是此行为的演示,使用Encoding.UTF8将URL转换为字节,使用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);
}

我在测试中使用了三个相当长且不重复的URL:

string[] urls = new string[]
{
    "https://stackoverflow.com/questions/71884821/is-there-any-invertible-way-in-c-to-convert-a-string-into-a-smaller-one-an#comment127033258_71884821",
    "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, https://stackoverflow.com/questions/71884821/is-th...
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',以便知道它是压缩的还是未压缩的.

Csharp相关问答推荐

与C#中的Zip列表并行

C#方法从AJAX调用接收NULL

从依赖项容器在.NET 8中的Program.cs文件中添加IOC

在.NET MAUI.NET 8中如何防止按钮点击时出现灰色反馈

未找到任何HTTP触发器.成功部署Azure Functions Project后(c#)

使用泛型可空类实现接口

当我没有此令牌时,为什么语法报告EOF错误?

ASP.NET Core MVC将值从视图传递到控制器时出现问题

使用System.Text.Json进行序列化时发生StackOverflow异常

将FileStream的特定部分作为字节数组读取

为什么我的用户界面对象移动到略低于实际目标?

为什么C#/MSBuild会自发地为不同的项目使用不同的输出路径?

如何使用.NET Aspire从Blazor应用程序与GRPC API通信?

KeyDown从我的文本框中删除输入,如何停止?

C#如何获取字符串中引号之间的文本?

如何对特定异常使用Polly重试机制?

如何对构建在Clean架构和CQRS之上的控制器进行单元测试?

用C#以编程方式扩展Azure应用服务Web应用

如何使用c#获取Azure中的服务列表?

为什么会出错';日历请求生成器';不包含';请求'";graphClient.Me.Calendars.Request().GetAsync();