C#对象二进制序列化优化:位域技术实现极限压缩
1. 引言
在操作系统中,进程信息对于系统监控和性能分析至关重要。假设我们需要开发一个监控程序,该程序能够捕获当前操作系统的进程信息,并将其高效地传输到其他端(如服务端或监控端)。在这个过程中,如何将捕获到的进程对象转换为二进制数据,并进行优化,以减小数据包的大小,成为了一个关键问题。本文将通过逐步分析,探讨如何使用位域技术对C#对象进行二进制序列化优化。

首先,我们给出了一个进程对象的字段定义示例。为了通过网络(TCP/UDP)传输该对象,我们需要将其转换为二进制格式。在这个过程中,如何做到最小的数据包大小是一个挑战。
| 字段名 | 说明 | 示例 | 
|---|---|---|
| PID | 进程ID | 10565 | 
| Name | 进程名称 | 码界工坊 | 
| Publisher | 发布者 | 沙漠尽头的狼 | 
| CommandLine | 命令行 | dotnet CodeWF.Tools.dll | 
| CPU | CPU(所有内核的总处理利用率) | 2.3% | 
| Memory | 内存(进程占用的物理内存) | 0.1% | 
| Disk | 磁盘(所有物理驱动器的总利用率) | 0.1 MB/秒 | 
| Network | 网络(当前主要网络上的网络利用率 | 0 Mbps | 
| GPU | GPU(所有GPU引擎的最高利用率) | 2.2% | 
| GPUEngine | GPU引擎 | GPU 0 - 3D | 
| PowerUsage | 电源使用情况(CPU、磁盘和GPU对功耗的影响) | 低 | 
| PowerUsageTrend | 电源使用情况趋势(一段时间内CPU、磁盘和GPU对功耗的影响) | 非常低 | 
| Type | 进程类型 | 应用 | 
| Status | 进程状态 | 效率模式 | 
2. 优化过程
2.1. 进程对象定义与初步分析
我们根据字段的示例值确定了每个字段的数据类型。
| 字段名 | 数据类型 | 说明 | 示例 | 
|---|---|---|---|
| PID | int | 进程ID | 10565 | 
| Name | string? | 进程名称 | 码界工坊 | 
| Publisher | string? | 发布者 | 沙漠尽头的狼 | 
| CommandLine | string? | 命令行 | dotnet CodeWF.Tools.dll | 
| CPU | string? | CPU(所有内核的总处理利用率) | 2.3% | 
| Memory | string? | 内存(进程占用的物理内存) | 0.1% | 
| Disk | string? | 磁盘(所有物理驱动器的总利用率) | 0.1 MB/秒 | 
| Network | string? | 网络(当前主要网络上的网络利用率 | 0 Mbps | 
| GPU | string? | GPU(所有GPU引擎的最高利用率) | 2.2% | 
| GPUEngine | string? | GPU引擎 | GPU 0 - 3D | 
| PowerUsage | string? | 电源使用情况(CPU、磁盘和GPU对功耗的影响) | 低 | 
| PowerUsageTrend | string? | 电源使用情况趋势(一段时间内CPU、磁盘和GPU对功耗的影响) | 非常低 | 
| Type | string? | 进程类型 | 应用 | 
| Status | string? | 进程状态 | 效率模式 | 
创建一个C#类SystemProcess表示进程信息:
public class SystemProcess
{
    public int PID { get; set; }
    public string? Name { get; set; }
    public string? Publisher { get; set; }
    public string? CommandLine { get; set; }
    public string? CPU { get; set; }
    public string? Memory { get; set; }
    public string? Disk { get; set; }
    public string? Network { get; set; }
    public string? GPU { get; set; }
    public string? GPUEngine { get; set; }
    public string? PowerUsage { get; set; }
    public string? PowerUsageTrend { get; set; }
    public string? Type { get; set; }
    public string? Status { get; set; }
}
定义测试数据
private SystemProcess _codeWFObject = new SystemProcess()
{
    PID = 10565,
    Name = "码界工坊",
    Publisher = "沙漠尽头的狼",
    CommandLine = "dotnet CodeWF.Tools.dll",
    CPU = "2.3%",
    Memory = "0.1%",
    Disk = "0.1 MB/秒",
    Network = "0 Mbps",
    GPU = "2.2%",
    GPUEngine = "GPU 0 - 3D",
    PowerUsage = "低",
    PowerUsageTrend = "非常低",
    Type = "应用",
    Status = "效率模式"
};
2.2. 排除Json序列化
将对象转为Json字段串,这在Web开发是最常见的,因为简洁,前后端都方便处理:
public class SysteProcessUnitTest
{
    private readonly ITestOutputHelper _testOutputHelper;
    private SystemProcess _codeWFObject // 前面已给出定义,这里省
    public SysteProcessUnitTest(ITestOutputHelper testOutputHelper)
    {
        _testOutputHelper = testOutputHelper;
    }
    /// <summary>
    /// Json序列化大小测试
    /// </summary>
    [Fact]
    public void Test_SerializeJsonData_Success()
    {
        var jsonData = JsonSerializer.Serialize(_codeWFObject);
        _testOutputHelper.WriteLine($"Json长度:{jsonData.Length}");
        var jsonDataBytes = Encoding.UTF8.GetBytes(jsonData);
        _testOutputHelper.WriteLine($"json二进制长度:{jsonDataBytes.Length}");
    }
}
标准输出: 
Json长度:366
json二进制长度:366
尽管Json序列化在Web开发中非常流行,因为它简洁且易于处理,但在TCP/UDP网络传输中,Json序列化可能导致不必要的数据包大小增加。因此,我们排除了Json序列化,并寻找其他更高效的二进制序列化方法。
{"PID":10565,"Name":"\u7801\u754C\u5DE5\u574A","Publisher":"\u6C99\u6F20\u5C3D\u5934\u7684\u72FC","CommandLine":"dotnet CodeWF.Tools.dll","CPU":"2.3%","Memory":"0.1%","Disk":"0.1 MB/\u79D2","Network":"0 Mbps","GPU":"2.2%","GPUEngine":"GPU 0 - 3D","PowerUsage":"\u4F4E","PowerUsageTrend":"\u975E\u5E38\u4F4E","Type":"\u5E94\u7528","Status":"\u6548\u7387\u6A21\u5F0F"}
2.3. 使用BinaryWriter进行二进制序列化
使用站长前面一篇文章写的二进制序列化帮助类SerializeHelper转换,该类使用BinaryWriter将对象转换为二进制数据。
首先,我们使SystemProcess类实现了一个空接口INetObject,并在类上添加了NetHeadAttribute特性。
/// <summary>
/// 网络对象序列化接口
/// </summary>
public interface INetObject
{
}
[NetHead(1, 1)]
public class SystemProcess : INetObject
{
 	// 省略字段定义
}
然后,我们编写了一个测试方法来验证序列化和反序列化的正确性,并打印了序列化后的二进制数据长度。
/// <summary>
/// 二进制序列化测试
/// </summary>
[Fact]
public void Test_SerializeToBytes_Success()
{
    var buffer = SerializeHelper.SerializeByNative(_codeWFObject, 1);
    _testOutputHelper.WriteLine($"序列化后二进制长度:{buffer.Length}");
    var deserializeObj = SerializeHelper.DeserializeByNative<SystemProcess>(buffer);
    Assert.Equal("码界工坊", deserializeObj.Name);
}
标准输出: 
序列化后二进制长度:152
比Json体积小了一半多(366到152),上面单元测试也测试了数据反序列化后验证数据是否正确,我们就以这个基础继续优化。
2.4. 数据类型调整
为了进一步优化二进制数据的大小,我们对数据类型进行了调整。通过对进程数据示例的分析,我们发现一些字段的数据类型可以更加紧凑地表示。例如,CPU利用率可以只传递数字部分(如2.3),而不需要传递百分号。这种调整可以减小数据包的大小。
| 字段名 | 数据类型 | 说明 | 示例 | 
|---|---|---|---|
| PID | int | 进程ID | 10565 | 
| Name | string? | 进程名称 | 码界工坊 | 
| Publisher | string? | 发布者 | 沙漠尽头的狼 | 
| CommandLine | string? | 命令行 | dotnet CodeWF.Tools.dll | 
| CPU | float | CPU(所有内核的总处理利用率) | 2.3 | 
| Memory | float | 内存(进程占用的物理内存) | 0.1 | 
| Disk | float | 磁盘(所有物理驱动器的总利用率) | 0.1 | 
| Network | float | 网络(当前主要网络上的网络利用率 | 0 | 
| GPU | float | GPU(所有GPU引擎的最高利用率) | 2.2 | 
| GPUEngine | byte | GPU引擎,0:无,1:GPU 0 - 3D | 1 | 
| PowerUsage | byte | 电源使用情况(CPU、磁盘和GPU对功耗的影响),0:非常低,1:低,2:中,3:高,4:非常高 | 1 | 
| PowerUsageTrend | byte | 电源使用情况趋势(一段时间内CPU、磁盘和GPU对功耗的影响),0:非常低,1:低,2:中,3:高,4:非常高 | 0 | 
| Type | byte | 进程类型,0:应用,1:后台进程 | 0 | 
| Status | byte | 进程状态,0:正常运行,1:效率模式,2:挂起 | 1 | 
修改测试数据定义:
[NetHead(1, 2)]
public class SystemProcess2 : INetObject
{
    public int PID { get; set; }
    public string? Name { get; set; }
    public string? Publisher { get; set; }
    public string? CommandLine { get; set; }
    public float CPU { get; set; }
    public float Memory { get; set; }
    public float Disk { get; set; }
    public float Network { get; set; }
    public float GPU { get; set; }
    public byte GPUEngine { get; set; }
    public byte PowerUsage { get; set; }
    public byte PowerUsageTrend { get; set; }
    public byte Type { get; set; }
    public byte Status { get; set; }
}
/// <summary>
/// 普通优化字段数据类型
/// </summary>
private SystemProcess2 _codeWFObject2 = new SystemProcess2()
{
    PID = 10565,
    Name = "码界工坊",
    Publisher = "沙漠尽头的狼",
    CommandLine = "dotnet CodeWF.Tools.dll",
    CPU = 2.3f,
    Memory = 0.1f,
    Disk = 0.1f,
    Network = 0,
    GPU = 2.2f,
    GPUEngine = 1,
    PowerUsage = 1,
    PowerUsageTrend = 0,
    Type = 0,
    Status = 1
};
添加单元测试如下:
/// <summary>
/// 二进制序列化测试
/// </summary>
[Fact]
public void Test_SerializeToBytes2_Success()
{
    var buffer = SerializeHelper.SerializeByNative(_codeWFObject2, 1);
    _testOutputHelper.WriteLine($"序列化后二进制长度:{buffer.Length}");
    var deserializeObj = SerializeHelper.DeserializeByNative<SystemProcess2>(buffer);
    Assert.Equal("码界工坊", deserializeObj.Name);
    Assert.Equal(2.2f, deserializeObj.GPU);
}
测试结果:
标准输出: 
序列化后二进制长度:99
又优化了50%左右(152到99),爽不爽?继续,还有更爽的。
2.5. 再次数据类型调整与位域优化
更进一步地,我们引入了位域技术。位域允许我们更加精细地控制字段在内存中的布局,从而进一步减小二进制数据的大小。我们重新定义了字段规则,并使用位域来表示一些枚举值字段。通过这种方式,我们能够显著地减小数据包的大小。
看前面一张表,部分字段只是一些枚举值,使用的byte表示,即8位(bit),其中比如进程类型只有2个状态(0:应用,1:后台进程),正好可以用1位即表示;像电源使用情况,无非就是5个状态,用3位可表示全,按这个规则我们重新定义字段规则如下:
| 字段名 | 数据类型 | 说明 | 示例 | 
|---|---|---|---|
| PID | int | 进程ID | 10565 | 
| Name | string? | 进程名称 | 码界工坊 | 
| Publisher | string? | 发布者 | 沙漠尽头的狼 | 
| CommandLine | string? | 命令行 | dotnet CodeWF.Tools.dll | 
| Data | byte[8] | 固定大小的几个字段,见下表定义 | 
固定字段(Data)的详细说明如下:
| 字段名 | Offset | Size | 说明 | 示例 | 
|---|---|---|---|---|
| CPU | 0 | 10 | CPU(所有内核的总处理利用率),最后一位表示小数位,比如23表示2.3% | 23 | 
| Memory | 10 | 10 | 内存(进程占用的物理内存),最后一位表示小数位,比如1表示0.1%,值可根据基本信息计算 | 1 | 
| Disk | 20 | 10 | 磁盘(所有物理驱动器的总利用率),最后一位表示小数位,比如1表示0.1%,值可根据基本信息计算 | 1 | 
| Network | 30 | 10 | 网络(当前主要网络上的网络利用率),最后一位表示小数位,比如253表示25.3%,值可根据基本信息计算 | 0 | 
| GPU | 40 | 10 | GPU(所有GPU引擎的最高利用率),最后一位表示小数位,比如253表示25.3 | 22 | 
| GPUEngine | 50 | 1 | GPU引擎,0:无,1:GPU 0 - 3D | 1 | 
| PowerUsage | 51 | 3 | 电源使用情况(CPU、磁盘和GPU对功耗的影响),0:非常低,1:低,2:中,3:高,4:非常高 | 1 | 
| PowerUsageTrend | 54 | 3 | 电源使用情况趋势(一段时间内CPU、磁盘和GPU对功耗的影响),0:非常低,1:低,2:中,3:高,4:非常高 | 0 | 
| Type | 57 | 1 | 进程类型,0:应用,1:后台进程 | 0 | 
| Status | 58 | 2 | 进程状态,0:正常运行,1:效率模式,2:挂起 | 1 | 
上面这张表是位域规则表,Offset表示字段在Data字节数组中的位置(以bit为单位计算),Size表示字段在Data中占有的大小(同样以bit单位计算),如Memory字段,在Data字节数组中,占据10到20位的空间。
修改类定义如下,注意看代码中的注释:
[NetHead(1, 3)]
public class SystemProcess3 : INetObject
{
    public int PID { get; set; }
    public string? Name { get; set; }
    public string? Publisher { get; set; }
    public string? CommandLine { get; set; }
    private byte[]? _data;
    /// <summary>
    /// 序列化,这是实际需要序列化的数据
    /// </summary>
    public byte[]? Data
    {
        get => _data;
        set
        {
            _data = value;
            // 这是关键:在反序列化将byte转换为对象,方便程序中使用
            _processData = _data?.ToFieldObject<SystemProcessData>();
        }
    }
    private SystemProcessData? _processData;
    /// <summary>
    /// 进程数据,添加NetIgnoreMember在序列化会忽略
    /// </summary>
    [NetIgnoreMember]
    public SystemProcessData? ProcessData
    {
        get => _processData;
        set
        {
            _processData = value;
            // 这里关键:将对象转换为位域
            _data = _processData?.FieldObjectBuffer();
        }
    }
}
public record SystemProcessData
{
    [NetFieldOffset(0, 10)] public short CPU { get; set; }
    [NetFieldOffset(10, 10)] public short Memory { get; set; }
    [NetFieldOffset(20, 10)] public short Disk { get; set; }
    [NetFieldOffset(30, 10)] public short Network { get; set; }
    [NetFieldOffset(40, 10)] public short GPU { get; set; }
    [NetFieldOffset(50, 1)] public byte GPUEngine { get; set; }
    [NetFieldOffset(51, 3)] public byte PowerUsage { get; set; }
    [NetFieldOffset(54, 3)] public byte PowerUsageTrend { get; set; }
    [NetFieldOffset(57, 1)] public byte Type { get; set; }
    [NetFieldOffset(58, 2)] public byte Status { get; set; }
}
添加单元测试如下:
/// <summary>
/// 极限优化字段数据类型
/// </summary>
private SystemProcess3 _codeWFObject3 = new SystemProcess3()
{
    PID = 10565,
    Name = "码界工坊",
    Publisher = "沙漠尽头的狼",
    CommandLine = "dotnet CodeWF.Tools.dll",
    ProcessData = new SystemProcessData()
    {
        CPU = 23,
        Memory = 1,
        Disk = 1,
        Network = 0,
        GPU = 22,
        GPUEngine = 1,
        PowerUsage = 1,
        PowerUsageTrend = 0,
        Type = 0,
        Status = 1
    }
};
/// <summary>
/// 二进制极限序列化测试
/// </summary>
[Fact]
public void Test_SerializeToBytes3_Success()
{
    var buffer = SerializeHelper.SerializeByNative(_codeWFObject3, 1);
    _testOutputHelper.WriteLine($"序列化后二进制长度:{buffer.Length}");
    var deserializeObj = SerializeHelper.DeserializeByNative<SystemProcess3>(buffer);
    Assert.Equal("码界工坊", deserializeObj.Name);
    Assert.Equal(23, deserializeObj.ProcessData.CPU);
    Assert.Equal(1, deserializeObj.ProcessData.PowerUsage);
}
测试输出:
标准输出: 
序列化后二进制长度:86
99又优化到86个字节,13个字节哦,有极限网络环境下非常可观,比如100万数据,那不就是12.4MB了?关于位域序列化和反序列的代码这里不细说了,很枯燥,站长可能也说不清楚,代码长这样:
public partial class SerializeHelper
{
    public static byte[] FieldObjectBuffer<T>(this T obj) where T : class
    {
        var properties = typeof(T).GetProperties();
        var totalSize = 0;
        // 计算总的bit长度
        foreach (var property in properties)
        {
            if (!Attribute.IsDefined(property, typeof(NetFieldOffsetAttribute)))
            {
                continue;
            }
            var offsetAttribute =
                (NetFieldOffsetAttribute)property.GetCustomAttribute(typeof(NetFieldOffsetAttribute))!;
            totalSize = Math.Max(totalSize, offsetAttribute.Offset + offsetAttribute.Size);
        }
        var bufferLength = (int)Math.Ceiling((double)totalSize / 8);
        var buffer = new byte[bufferLength];
        foreach (var property in properties)
        {
            if (!Attribute.IsDefined(property, typeof(NetFieldOffsetAttribute)))
            {
                continue;
            }
            var offsetAttribute =
                (NetFieldOffsetAttribute)property.GetCustomAttribute(typeof(NetFieldOffsetAttribute))!;
            dynamic value = property.GetValue(obj)!; // 使用dynamic类型动态获取属性值
            SetBitValue(ref buffer, value, offsetAttribute.Offset, offsetAttribute.Size);
        }
        return buffer;
    }
    public static T ToFieldObject<T>(this byte[] buffer) where T : class, new()
    {
        var obj = new T();
        var properties = typeof(T).GetProperties();
        foreach (var property in properties)
        {
            if (!Attribute.IsDefined(property, typeof(NetFieldOffsetAttribute)))
            {
                continue;
            }
            var offsetAttribute =
                (NetFieldOffsetAttribute)property.GetCustomAttribute(typeof(NetFieldOffsetAttribute))!;
            dynamic value = GetValueFromBit(buffer, offsetAttribute.Offset, offsetAttribute.Size,
                property.PropertyType);
            property.SetValue(obj, value);
        }
        return obj;
    }
    /// <summary>
    /// 将值按位写入buffer
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="value"></param>
    /// <param name="offset"></param>
    /// <param name="size"></param>
    private static void SetBitValue(ref byte[] buffer, int value, int offset, int size)
    {
        var mask = (1 << size) - 1;
        buffer[offset / 8] |= (byte)((value & mask) << (offset % 8));
        if (offset % 8 + size > 8)
        {
            buffer[offset / 8 + 1] |= (byte)((value & mask) >> (8 - offset % 8));
        }
    }
    /// <summary>
    /// 从buffer中按位读取值
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="offset"></param>
    /// <param name="size"></param>
    /// <param name="propertyType"></param>
    /// <returns></returns>
    private static dynamic GetValueFromBit(byte[] buffer, int offset, int size, Type propertyType)
    {
        var mask = (1 << size) - 1;
        var bitValue = (buffer[offset / 8] >> (offset % 8)) & mask;
        if (offset % 8 + size > 8)
        {
            bitValue |= (buffer[offset / 8 + 1] << (8 - offset % 8)) & mask;
        }
        dynamic result = Convert.ChangeType(bitValue, propertyType); // 根据属性类型进行转换
        return result;
    }
}
3. 优化效果与总结
通过逐步优化,我们从最初的Json序列化366字节减小到了使用普通二进制序列化的152字节,再进一步使用位域技术优化到了86字节。这种优化在网络传输中是非常可观的,尤其是在需要传输大量数据的情况下。
本文通过一个示例案例,探讨了C#对象二进制序列化的优化方法。通过使用位域技术,我们实现了对数据包大小的极限压缩,提高了网络传输的效率。这对于开发C/S程序来说是一种乐趣,也是追求极致性能的一种体现。
最后,我们提供了本文测试源码的Github链接,供读者参考和学习。
彩蛋:该仓库有上篇《C#百万对象序列化深度剖析:如何在网络传输中实现速度与体积的完美平衡 (dotnet9.com)》案例代码,也附带了TCP、UDP服务端与客户端联调测试程序哦。
C#对象二进制序列化优化:位域技术实现极限压缩的更多相关文章
- 线程安全使用(四)  [.NET] 简单接入微信公众号开发:实现自动回复  [C#]C#中字符串的操作  自行实现比dotcore/dotnet更方便更高性能的对象二进制序列化  自已动手做高性能消息队列  自行实现高性能MVC WebAPI  面试题随笔  字符串反转
		
线程安全使用(四) 这是时隔多年第四篇,主要是因为身在东软受内网限制,好多文章就只好发到东软内部网站,懒的发到外面,现在一点点把在东软写的文章给转移出来. 这里主要讲解下CancellationT ...
 - 再谈 C# 对象二进制序列化,序列化并进行 AES 加密
		
对象的二进制序列化非常有用,也非常方便. 我们可以把对象序列化为字节数组,也可以把对象序列化到文件,还可以把对象序列化到文件并进行加密. 先引用这些命名空间: using System.IO;usin ...
 - protobuf-net  对象二进制序列化与反序列号(转)
		
概述: Protobuf是google开源的一个项目,用户数据序列化反序列化,google声称google的数据通信都是用该序列化方法.它比xml格式要少的多,甚至比二进制数据格式也小的多. Prot ...
 - java编解码技术,json序列化与二进制序列化
		
1.何为json序列化与二进制序列化 通常我们在程序中采用的以json为传输,将json转为对象的就是json序列化了.而二进制序列化通常是我们将数据转换为二进制进行传输,然后在进行各类转换操作 2. ...
 - 开源!一款功能强大的高性能二进制序列化器Bssom.Net
		
好久没更新博客了,我开源了一款高性能的二进制序列化器Bssom.Net和新颖的二进制协议Bssom,欢迎大家Star,欢迎参与项目贡献! Net开源技术交流群 976304396,禁止水,只能讨论技术 ...
 - Java基础--对象的序列化
		
所有分布式应用常常需要跨平台,跨网络,因此要求所有传的参数.返回值都必须实现序列化. 比如常见的Dubbo分布式平台,里面的对象实体类必须实现序列化才能在网络间传递 一.定义 序列化:把Java对象转 ...
 - (记录)Jedis存放对象和读取对象--Java序列化与反序列化
		
一.理论分析 在学习Redis中的Jedis这一部分的时候,要使用到Protostuff(Protobuf的Java客户端)这一序列化工具.一开始看到序列化这些字眼的时候,感觉到一头雾水.于是,参考了 ...
 - 性能超四倍的高性能.NET二进制序列化库
		
二进制序列化在.NET中有很多使用场景,如我们使用分布式缓存时,通常将缓存对象序列化为二进制数据进行缓存,在ASP.NET中,很多中间件(如认证等)也都是用了二进制序列化. 在.NET中我们通常使用S ...
 - Java基础学习总结——Java对象的序列化和反序列化
		
一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存 ...
 - Hibernate入门5持久化对象关系和批量处理技术
		
Hibernate入门5持久化对象关系和批量处理技术 20131128 代码下载 链接: http://pan.baidu.com/s/1Ccuup 密码: vqlv 前言: 前面学习了Hiberna ...
 
随机推荐
- 华企盾DSC客户端连接服务器正常,控制台找不到客户端
			
1.首先客户端查一下策略状态,看一下客户端在哪个组,是否在回收站,或者无策略组: 2.查看一下数据库的CLIENT_GROUP_TABLE_表中是否有查出来的组,CLIENT_GROUP_TABLE_ ...
 - ubuntu frame 个人开发心得
			
引言 有一次我在树莓派上安装 Ubuntu Core 我给创新创业项目开发一个可视化 gui 看板,用于展示数据. 然后我就找到了我需要的工具 Ubuntu Frame 初次尝试使用 Ubuntu F ...
 - JavaImprove--Lesson01--枚举类,泛型
			
一.枚举 认识枚举类 枚举是一种特殊的类 枚举的格式: 修饰符 enmu 枚举类名{ 名称1,名称2: 其它成员 } //枚举类 public enum A { //枚举类的第一列必须是罗列枚举 ...
 - 文心一言 VS 讯飞星火 VS chatgpt (47)-- 算法导论6.1 5题
			
五.一个已排好序的数组是一个最小堆吗? 文心一言: 一个已排好序的数组不一定是一个最小堆.最小堆是一种特殊的二叉树,它满足以下性质:对于任意节点 x,其父节点 y 的值都小于等于 x 的值.而一个已排 ...
 - flutter中显示年月日、星期与时间
			
代码 import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'dart:async'; imp ...
 - Quartz.Net系列(一):Windows任务计划程序
			
1.使用此电脑=>管理 系统工具=>任务计划程序=>任务计划程序库=>创建任务 创建任务 触发器 操作 条件=>去掉只有在计算机使用交流电源时才启动此任务 创建 ...
 - 日常Bug排查-应用Commit报错事务并没有回滚
			
日常Bug排查-应用Commit报错事务并没有回滚 前言 日常Bug排查系列都是一些简单Bug排查,笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材_. 应用Commit报错并不一定回滚 事 ...
 - Spark 覆盖写Hive分区表,只覆盖部分对应分区
			
要求Spark版本2.3以上,亲测2.2无效 配置 config("spark.sql.sources.partitionOverwriteMode","dynamic& ...
 - 【django drf】 阶段练习
			
目录 需求 settings.py views.py urls.py serializers.py permissions.py page.py authenticate.py model.py 权限 ...
 - BBS项目(一): 表设计 注册功能 登录功能 生成随机验证码
			
目录 表设计 1.确定表的数量 2.确定表的基础字段 自关联字段 3.确定表的外键字段 表关系图 项目初建流程备忘 注册功能 登录功能 生成随机验证码 表设计 # 仿造博客园项目 核心:文章的增删改查 ...