C# 中记录(Record)详解

news2025/2/6 12:23:44

从C#9.0开始,我们有了一个有趣的语法糖:记录(record)  

  为什么提供记录?

  开发过程中,我们往往会创建一些简单的实体,它们仅仅拥有一些简单的属性,可能还有几个简单的方法,比如DTO等等,但是这样的简单实体往往又很有用,我们可能会遇到一些情况:

  比如想要克隆一个新的实体而不是简单的引用传递  

  比如想要简单的比较属性值是否都一致,

  比如在输出,我们希望得到内部数据结构而不是简单的甩给我们一个类型名称

  其实,这说的有些类似结构体的一些特性,那为什么不直接采用结构体来实现呢?这是因为结构体有它的一些不足:  

    1、结构体不支持继承
    2、结构体是值传递过程,因此,这意味着大量的结构体拥有者相同的数据,但是占用这不同内存
    3、结构体内部相等判断使用ValueType.Equals方法,它是使用反射来实现,因此性能不快

  而引用类型记录,正好弥补了这些缺陷。

  在C#9.0中,我们使用record关键字声明一个记录类型,它只能是引用类型:  

    public record Animal;

  从C#10开始,我们不仅有引用类型记录,还有结构体记录:

//使用record class声明为引用类型记录,class关键字是可选的,当缺省时等价于C#9.0中的record用法
    public record Animal;
    //等价于
    public record class Animal;

    //使用record struct声明为结构体类型记录
    public record struct Animal;
    //也可使用readonly record struct声明为只读结构体类型记录
    public readonly record struct Animal;

  至于它们是什么,区别上和普通class、struct有什么不一样,我们慢慢道来

  引用类型记录

  引用类型记录不是一种新的类型,它是class用法的一个新用法,新的语法糖,也就是说record class是引用类型(这个在C#9.0中没有record class的写法,直接使用record)。

  先看看引用类型记录是什么样子的,首先是无构造参数的记录

  //无构造参数,无其它方法属性等
    public record Animal;
    //实例化
    var animal = new Animal();

   在编译时,会生成对应的class,大致等价于下面的例子:  

 public class Animal : IEquatable<Animal>
    {
        public Animal() { }
        protected Animal(Animal original) { }

        protected virtual Type EqualityContract => typeof(Animal);

        public virtual Animal <Clone>$() => new Animal(this);
        public virtual bool Equals(Animal? other) => (other != null) && (this.EqualityContract == other.EqualityContract);
        public override bool Equals(object obj) => this.Equals(obj as Animal);
        public override int GetHashCode() => EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract);
        protected virtual bool PrintMembers(StringBuilder builder) => false;
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("Animal");
            builder.Append(" { ");
            if (this.PrintMembers(builder))
            {
                builder.Append(" ");
            }
            builder.Append("}");
            return builder.ToString();
        }

        public static bool operator ==(Animal r1, Animal r2) => (r1 == r2) || ((r1 != null) && r1.Equals(r2));
        public static bool operator !=(Animal r1, Animal r2) => !(r1 == r2);
    }

  可以看到,除了几个相比较的方法,那么这个记录的作用几乎等价于object了!这里有一个<Clone>$(),方法,这是编译器生成的,作用后面再解释。

  再看看有构造参数的记录:

 //有构造参数,无其它方法属性等
    public record Person(string Name, int Age);
    //实例化
    var person = new Person("zhangsan", 1);

  注:上面的定义可能会报错:

  

  据说这是VS2019的一个小BUG,因为记录会生成 init setter,解决办法是添加一个命名空间是System.Runtime.CompilerServices,名称是IsExternalInit类就行了:  

  namespace System.Runtime.CompilerServices
    {
        class IsExternalInit
        {
        }
    }

  有构造参数的记录在编译时,会生成对应的class,大致等价于下面的例子:

 public class Person : IEquatable<Person>
    {
        public Person(string Name, int Age)
        {
            this.Name = Name;
            this.Age = Age;
        }
        protected Person(Person original)
        {
            this.Name = original.Name;
            this.Age = original.Age;
        }

        protected virtual Type EqualityContract => typeof(Person);
        public string Name { get; init; }
        public int Age { get; init; }

        public virtual Person <Clone>$() => new Person(this);
        public void Deconstruct(out string Name, out int Age) => (Name, Age) = (this.Name, this.Age);
        public virtual bool Equals(Person? other) => (other != null) && (this.EqualityContract == other.EqualityContract) &&
            EqualityComparer<string>.Default.Equals(this.Name, other.Name) && EqualityComparer<int>.Default.Equals(this.Age, other.Age);
        public override bool Equals(object obj) => this.Equals(obj as Person);
        public override int GetHashCode() => (((EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract) * -1521134295) + EqualityComparer<string>.Default.GetHashCode(this.Name)) * -1521134295) + EqualityComparer<int>.Default.GetHashCode(this.Age);
        protected virtual bool PrintMembers(StringBuilder builder)
        {
            builder.Append("Name");
            builder.Append(" = ");
            builder.Append(this.Name);
            builder.Append(", ");
            builder.Append("Age");
            builder.Append(" = ");
            builder.Append(this.Age.ToString());
            return true;
        }
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("Person");
            builder.Append(" { ");
            if (this.PrintMembers(builder))
            {
                builder.Append(" ");
            }
            builder.Append("}");
            return builder.ToString();
        }

        public static bool operator ==(Person r1, Person r2) => (r1 == r2) || ((r1 != null) && r1.Equals(r2));
        public static bool operator !=(Person r1, Person r2) => !(r1 == r2);
    }

  可以看到,相比无构造参数的记录,有构造参数的记录将构造参数生成了属性(setter是init),而且Equals、GetHashCode、ToString等方法重载都有这几个属性参与。

  除此之外,还生成了一个Deconstruct方法,因此,有构造参数的记录就具有解构能力。另外,这里也同样生成了一个<Clone>$方法。

  接下来看看记录的这些属性和方法:

  1、构造函数和属性

  记录会根据给定的参数生成一个构造函数,同时为每一个构造参数生成一个属性(为了规范,参数应采用匈牙利命名法,首字符大写),比如上面的Animal记录,等价于:  

 public class Animal : IEquatable<Animal>
    {
        public Animal(string Name, int Age)
        {
            this.Name = Name;
            this.Age = Age;
        }

        public string Name { get; init; }
        public int Age { get; init; }

        //其他方法属性
    }

  这里的属性的setter是init,也就是说记录具有不可变性,记录一旦初始化完成,那么它的属性值将不可修改(可以通过反射修改)。

  另外,记录允许我们自定义构造方法和属性,但是需要遵循:  

    1、记录在编译时会根据构造参数生成一个默认的构造函数,默认构造函数不能被覆盖,如果有自定义的构造函数,那么需要使用this关键字初始化这个默认的构造函数
    2、记录中可以自定义属性,自定义属性名可以与构造参数名重名,也就是说自定义属性可以覆盖构造参数生成的属性,此时对应构造参数将不起任何作用,但是我们可以通过属性指向这个构造参数来自定义这样一个属性

  比如:    

    public record Person(string Name, int Age)
    {
        //自定义构造函数需要使用this初始化默认构造函数
        public Person(string Name) : this(Name, 18)
        { }

        //覆盖构造参数中的Age,属性不用是init,可以自定义,public也可以改成internal等等
        internal int Age { get; set; } = Age;//这个赋值很重要,如果没有,构造函数中的参数值将不会给到属性,也就是说构造函数中的Age不起任何作用
        //额外的自定义属性
        public DateTime Birth { get; set; }
    }

    //等价于

    public class Person : IEquatable<Person>
    {
        public Person(string Name) : this(Name, 18)
        { }
        public Person(string Name, int Age)
        {
            this.Name = Name;
            this.Age = Age;
        }

        public string Name { get; init; }
        internal int Age { get; set; }//Age改变
        //额外的自定义属性
        public DateTime Birth { get; set; }

        //其他方法及属性
    }

  从上面可以看到,虽然记录具有不可变性,但是我们可以通过自定义属性来覆盖原来的行为,让其属性变为可修改的,Age属性有原来的public和init变为internal和set。

  此外,在创建一个记录时,可以给构造参数指定一些特性标识,在编译时会用这些特性给到生成的对应属性,如:  

public record Person([property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("age")] int Age);
    
    //等价于

    public class Person : IEquatable<Person>
    {
        [JsonPropertyName("name")]
        public string Name { get; set; }
        [JsonPropertyName("age")]
        public int Age { get; set; }

        //其他方法及属性
    }

  其中property表示特性加在属性上,field表示特性加在字段上,param表示特性加在构造函数的参数上

  2、记录可以解构

  上面的例子可以看到,每个记录,在编译时会针对构造参数生成一个Deconstruct 方法,因此记录天生就支持解构:  

 Person person = new Person("zhangsan", 21);
 var (name, age) = person;
 Console.WriteLine($"name={name},age={age}");//name=zhangsan,age=21

  注:解构只针对默认构造函数的构造参数,不计算自定义的属性和构造函数,如果需要,我们还可以重载自己的解构Deconstruct方法

  3、记录可以继承  

  记录可继承,但是需要遵循:  

    1、一条记录可以从另一条记录继承,但不能从一个类中继承,一个类也不能从一个记录继承
    2、继承的子记录必须声明父记录中各参数

  例如:  

 public record Person(string Name, int Age);
 public record Teacher(string Phone, int Age, string Name) : Person(Name, Age);
 public record Student(string Grade, int Age, string Name) : Person(Name, Age);

  4、值相等性

  值相等性一般是值类型的一个概念,而记录是引用类型,要实现值相等性,主要通过三个方面来实现:

  • 重写Object的Equals和GetHashCode方法
  • 重写运算符 == 和 !=
  • 实现了IEquatable<T>接口

  重写Object的Equals方法和重写运算符 == 、!=很好理解,因为引用类型在使用Equals方法或者运算符 == 、!=作判断时,是根据对象是否是同一个对象的引用而返回true或者false,例如: 

  public record Person(string Name, int Age);
    static void Main(string[] args)
    {
        //一般引用类型
        var exception1 = new Exception();
        var exception2 = exception1;
        Console.WriteLine(exception1.Equals(exception2));//true
        Console.WriteLine(exception1 == exception2);//true
        Console.WriteLine(exception1.Equals(new Exception()));//false
        Console.WriteLine(exception1 == new Exception());//false

        //记录
        var person1 = new Person("zhangsan", 18);
        var person2 = person1;
        Console.WriteLine(person1.Equals(person2));//true
        Console.WriteLine(person1 == person2);//true
        Console.WriteLine(person1.Equals(new Person("zhangsan", 18)));//true
        Console.WriteLine(person1 == new Person("zhangsan", 18));//true
    }

  对于实现了IEquatable<T>接口,是为了让记录在泛型集合中,如Dictionary<TKey,TValue>, List<T>等,在使用Contains, IndexOf, LastIndexOf, Remove等方法时可以像string,int,bool等类型一样对待,例如:  

public record Person(string Name, int Age);
    static void Main(string[] args)
    {
        //一般引用类型
        List<Exception> exceptions = new List<Exception>() { new Exception() };
        Console.WriteLine(exceptions.IndexOf(new Exception()));//-1
        Console.WriteLine(exceptions.Contains(new Exception()));//false
        Console.WriteLine(exceptions.Remove(new Exception()));//false

        //记录
        List<Person> persons = new List<Person>() { new Person("zhangsan", 18) };
        Console.WriteLine(persons.IndexOf(new Person("zhangsan", 18)));//0
        Console.WriteLine(persons.Contains(new Person("zhangsan", 18)));//true
        Console.WriteLine(persons.Remove(new Person("zhangsan", 18)));//true
    }

  换句话说,虽然记录是引用类型,但是我们应该将记录按值类型一样去使用。

  注意:  

    1、实现的IEquatable<T>接口的Equals方法和重写的GetHashCode方法中使用的属性不仅仅是构造参数对应的属性,还包含自定义的属性、继承的属性(包括public,internal,protected,private,但是需要有get获取器)
    2、无论是重写Object的Equals方法,还是重写运算符 == 和 !=,最终都是调用实现的IEquatable<T>接口的Equals方法

  虽然记录的值相等性很好用,但是这有个问题,因为记录可继承,那么如果父子记录的属性值一样,如果判定他们相同显然不合理,因此编译时额外生成了一个EqualityContract属性:  

    1、EqualityContract属性指向当前的记录类型(Type),使用protected修饰
    2、如果记录没有从其它记录继承,那么EqualityContract属性会带有virtual修饰,否则将会使用override重写
    3、如果记录指定为sealed,即不可派生,那么EqualityContract属性会带有sealed修饰

  为了保证父子记录的差异性,在实现的IEquatable<T>接口的Equals方法中,处理判断属性值相同外,还会判断记录类型是否一致,即EqualityContract属性

  那如果说,我们需要只考虑属性值,而不考虑类型时,需要判断他们相等,这时只需要重写EqualityContract属性,将它指向同一个Type即可。

  此外,可以自定义Equals方法,这样编译时就不会生成Equals方法。

  5、非破坏性变化:with

  因为记录是引用类型,而属性的setter是init,因此当我们需要克隆一个记录时就出现困难了,我们可以通过自定义属性来修改setter来实现,但这不是记录的初衷。

  记录可以使用with关键字来实现非破坏性的变化:  

  public record Person(string Name, DateTime Birth, int Age, string Phone, string Address);

    static void Main(string[] args)
    {
        //初始化了一个对象
        Person person = new("zhangsan", new DateTime(1999, 1, 1), 22, "13987654321", "中国");
        //如果想改下地址,因为记录的不可变性,不能直接使用属性修改
        //person.Address = "中国深圳";//报错

        //方法一:可以重新初始化,但是不方便
        person = new(person.Name, person.Birth, person.Age, person.Phone, "中国深圳");
        //方法二:可以使用with关键字
        person = person with { Address = "中国深圳" };

        //可以使用with关键字克隆一个对象
        var clone = person with { };
        Console.WriteLine(clone == person);//true
        Console.WriteLine(ReferenceEquals(clone, person));//false
    }

   使用with关键字时会先调用<Clone>$()方法来创建一个对象,然后对这个对象进行指定属性的初始化,这就是最开始的例子中<Clone>$()方法的作用:  

 person = person with { Address = "中国深圳" };

    //在编译后等价于

    var temp=person.<Clone>$();
    temp.Address = "中国深圳";
    person = temp;

  在写代码时,我们当然不能显式的调用<Clone>$()方法,因为名称不合法(它是编译器生成的),<Clone>$()方法其实就是调用一个构造函数来实现初始化的,这表示我们可以通过自定义或者重写这个构造函数来实现我们自己的逻辑:  

 public class Person : IEquatable<Person>
    {
        protected Person(Person original)
        {
            this.Name = original.Name;
            this.Age = original.Age;
        }

        public virtual Person <Clone>$() => new Person(this);

        //其他方法属性
    }

  注意,传入构造函数的参数是原始对象,然后使用原始对象中的属性值来进行初始化,如果属性值是一个引用类型,那么它将进行浅复制过程。

  注:这里with用法针对引用类型记录,值类型记录的with参考后文

  6、内置格式化

  记录还重写了ToString,可以方便查看,输出格式默认是:  

    记录类型 { 属性名1 = 属性值1, 属性名2 = 属性值2, ...}

  例如:  

  public record Person(string Name, int Age);
    static void Main(string[] args)
    {
        //初始化了一个对象
        Person person = new("zhangsan", 22);
        Console.WriteLine(person);
        //输出:Person { Name = zhangsan, Age = 22 }
    }

  编译器还合成了一个PrintMembers方法,如果我们有自己提供PrintMembers方法,编译器就不会合成了,所以如果我们想要实现自己的格式化,只需要实现自己的PrintMembers方法,而不用重写ToString方法。  

  值类型记录

  注:值类型记录只针对C#10及以后的版本有效

  值类型记录也就是结构体记录,大体上,值类型记录与引用类型记录的区别,就跟值类型与引用类型的区别差不多,所以具体不介绍,可以参考上面引用类型的介绍,这里只具体介绍它们的区别。

  值类型记录又分为两种:record struct和readonly record struct,这里结合record class来看看它们的区别:

  比如有三个record:  

public record class Point1(double X, double Y);
public readonly record struct Point2(double X, double Y);
public record struct Point3(double X, double Y);

  这里Point1是record class,Point2是readonly record struct,Point3是record struct,经过编译,它们等价于下面的三个类和结构体(方法体去掉了,具体可参考上面引用类型记录):  

  可以看到,这三种类型的记录主要有共同点有:  

    1、对记录的参数,分别生成了属性
    2、生成了一个包含记录所有属性的构造函数
    3、重写了 Object.Equals(Object)方法和Object.GetHashCode()方法
    4、实现了System.IEquatable<T>接口
    5、实现了==和!=运算操作
    6、实现了Deconstruct方法而实现解构操作
    7、重写了Object.ToString()方法,以及创建了一个PrintMembers用于序列化(但是PrintMembers有些许区别)

  共同点没什么好说的,参考上面引用类型介绍就可以了,接下来说说不同点:

  1、record class和readonly record struct生成的属性是get和init标识,也就是说它们的对象是只读的,而record struct生成的属性是get和set标识,也就是说它的对象是可读可写的

  例如:

    var point1 = new Point1(1, 2);
    point1.X = 2;//报错
    var point2 = new Point2(1, 2);
    point2.X = 2;//报错

    var point3 = new Point3(1, 2);
    point3.X = 2;//编译通过

  2、在构造函数上,record class会生成两个构造函数:一个是protected修饰,用于<Clone>$()方法克隆一个对象,一个public修饰,包含所有的构造参数,而readonly record struct和record struct只包含一个public修饰,包含所有的构造参数的构造函数,但是因为它们的本质还是结构体,因此默认会有一个空构造函数,因此在创建时有区别:  

 //创建时需要指定所有的参数,protected修饰的构造函数不能在记录及子记录外使用
    var point1 = new Point1(1, 2);

    //除了可以指定所有参数的构造函数,还可以使用空构造函数初始化
    var point2 = new Point2(1, 2);
    point2 = new Point2();
    var point3 = new Point3(1, 2);
    point3 = new Point3();

  3、record class类型记录会生成一个<Clone>$()方法,它通过调用一个protected的构造函数来克隆出一个新的引用对象,而我们可以通过自定义或者重写这个protected的构造函数的构造函数来实现我们自己业务逻辑。

  其实这个<Clone>$()方法是在with关键字中使用的:  

    var point1 = new Point1(1, 2);
    var point = point1 with { X = 2 };

    //等价于

    var point1 = new Point1(1, 2);
    var point = point1.<Clone>$();
    point.X = 2;
    //注:编译时给point.X赋值会报错(因为init),这里只是说明

  而对于readonly record struct和record struct类型记录,因为它们的本质是struct,天生是值复制的,因此就不需要这么一个<Clone>$()方法了,与此对应的是,结构体默认会有空构造函数(C#10)。

    var point2 = new Point2(1, 2);
    var point = point2 with { X = 2 };

    //等价于

    var point = point2;//struct是值复制过程
    point.X = 2;
 
   //注:编译时给readonly record struct声明的属性赋值会报错,而给record struct声明的属性赋值不会报错,这里只是说明

  4、record struct,readonly record struct,record class都可以拥有自定义属性,但是有些许区别

    1、record class按类中的属性规则去定义
    2、record struct按结构体中的属性规则去定义,此外,定义的属性必须进行初始化
    3、readonly record struct按结构体中的属性规则去定义,此外,定义的属性必须进行初始化,而且定义的属性只能是只读的

  例如:  

  public record class Point1(double X, double Y)
    {
        public double Z { get; set; }
    }
    public readonly record struct Point2(double X, double Y)
    {
        public double Z { get; } = default;//必须初始化,此外readonly修饰所以只能只读
    }
    public record struct Point3(double X, double Y)
    {
        public double Z { get; set; } = default;//必须初始化
    }

  5、record struct,readonly record struct,record class都实现了System.IEquatable<T>接口,而且重写了Object.Equals(Object)方法(本质上是通过System.IEquatable<T>接口来实现),但是record class中实现的Equals方法除了比较属性以外,还会比较记录的类型是否一致(即比较EqualityContract属性,这一点可以参考上面介绍的引用类型记录的值相等性部分),而对于record struct,readonly record struct,在编译时,并没有生成一个EqualityContract属性,在实现的Equals方法也只是比较了属性值,没有比较类似是否一致。

  其实想想,结构体只能实现接口,而不能从另一个结构体派生,因此在在实现的Equals方法自然就没有进行类型判断的必要了。

  6、record struct,readonly record struct,record class都重写了Object.ToString()方法,而且都是通过创建了一个PrintMembers方法来实现的,但是在PrintMembers方法上表现的行为不一致(这是一点细节,了解即可)。  

    1、如果记录是结构体记录(即record struct和readonly record struct),或者使用sealed关键字修饰,那么生成的PrintMembers方法是:private bool PrintMembers(StringBuilder builder);
    2、如果记录没有使用sealed关键字修饰,且记录直接派生自Object(即没有派生自一个父记录),那么生成的PrintMembers方法是:protected virtual bool PrintMembers(StringBuilder builder);
    3、如果记录派生自一个父记录,那么生成的PrintMembers方法是:protected override bool PrintMembers(StringBuilder builder);

  总结

  记录是一个语法糖,本质上还是class或者struct,它只编译时生效,运行时并没有记录这个东西,此外,根据官网介绍,记录不适合在EntityFrameworkCore中使用,毕竟它重写了Equals方法和相等运算(==和!=),这样可能会对EntityFrameworkCore的实体跟踪机制造成影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2293778.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MQTT协议 03】 抓包分析

一、MQTT测试工具 1、mqtt服务器 emqx 2、mqtt 客户端 mqttx 3、抓包工具 wireshark 搭建参考 【MQTT 协议 01】MQTT 服务器搭建_mqtt服务器搭建-CSDN博客 二、报文测试 2.1、CONNECT &#xff08;客户端连接&#xff09; 2.1.1、抓包 2.1.2、解析 #16进制表示 10300…

手机上运行AI大模型(Deepseek等)

最近deepseek的大火&#xff0c;让大家掀起新一波的本地部署运行大模型的热潮&#xff0c;特别是deepseek有蒸馏的小参数量版本&#xff0c;电脑上就相当方便了&#xff0c;直接ollamaopen-webui这种类似的组合就可以轻松地实现&#xff0c;只要硬件&#xff0c;如显存&#xf…

电商项目-分布式事务(四)基于消息队列实现分布式事务

基于消息队列实现分布式事务&#xff0c;实现消息最终一致性 如何基于消息队列实现分布式事务&#xff1f; 通过消息队列实现分布式事务的话&#xff0c;可以保证当前数据的最终一致性。实现思路&#xff1a;将大的分布式事务&#xff0c;进行拆分&#xff0c;拆分成若干个小…

leetcode_双指针 160.相交链表

160.相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 思路: 本题中&#xff0c;交点不是数值相等&#xff0c;而是指针相等 双指针遍历两遍后必定相遇&#xff0c…

深入理解浮点数:单精度、双精度、半精度和BFloat16详解

文章目录 深入理解浮点数&#xff1a;单精度、双精度、半精度和BFloat16详解 &#x1f522;简介 &#x1f31f;1. 单精度&#xff08;Single Precision&#xff09;&#x1f3af;应用场景 &#x1f680; 2. 双精度&#xff08;Double Precision&#xff09;&#x1f4aa;应用场…

Verilog基础(三):过程

过程(Procedures) - Always块 – 组合逻辑 (Always blocks – Combinational) 由于数字电路是由电线相连的逻辑门组成的,所以任何电路都可以表示为模块和赋值语句的某种组合. 然而,有时这不是描述电路最方便的方法. 两种always block是十分有用的: 组合逻辑: always @(…

拍照对比,X70 PRO与X90 PRO+的细节差异

以下是局部截图&#xff08;上X70P下X90PP&#xff09; 对比1 这里看不出差异。 对比2 X90PP的字明显更清楚。 对比3 中下的字&#xff0c;X90PP显然更清楚。

Node.js与嵌入式开发:打破界限的创新结合

文章目录 一、Node.js的本质与核心优势1.1 什么是Node.js?1.2 嵌入式开发的范式转变二、Node.js与嵌入式结合的四大技术路径2.1 硬件交互层2.2 物联网协议栈2.3 边缘计算架构2.4 轻量化运行时方案三、实战案例:智能农业监测系统3.1 硬件配置3.2 软件架构3.3 核心代码片段四、…

使用java调用deepseek,调用大模型,处理问题。ollama

废话不多&#xff0c;直接上代码 Testpublic void test7171111231233(){// url:放请求地址String url "http://localhost:11434/api/generate";HttpRequest request HttpUtil.createPost(url);Map<String, String> headers new HashMap<>();String a…

Linux驱动---字符设备

目录 一、基础简介 1.1、Linux设备驱动分类 1.2、字符设备驱动概念 二、驱动基本构成 2.1、驱动模块的加载和卸载 2.2、添加LICENNSE以及其他信息 三、字符设备驱动开发步骤 3.1、分配主次设备号 3.1.1 主次设备号 3.1.2静态注册设备号 3.1.3动态注册设备号 3.1.4释…

php7.3安装php7.3-gmp扩展踩坑总结

环境&#xff1a; 容器里面为php7.3.3版本 服务器也为php7.3.3-14版本&#xff0c;但是因为业务量太大需要在服务器里面跑脚本 容器里面为 alpine 系统&#xff0c;安装各种扩展 服务器里面开发服为 ubuntu 16.04.7 LTS (Xenial Xerus) 系统 服务器线上为 ubuntu 20.04.6 LTS (…

javaEE-8.JVM(八股文系列)

目录 一.简介 二.JVM中的内存划分 JVM的内存划分图: 堆区:​编辑 栈区:​编辑 程序计数器&#xff1a;​编辑 元数据区&#xff1a;​编辑 经典笔试题&#xff1a; 三,JVM的类加载机制 1.加载: 2.验证: 3.准备: 4.解析: 5.初始化: 双亲委派模型 概念: JVM的类加…

大语言模型轻量化:知识蒸馏的范式迁移与工程实践

大语言模型轻量化&#xff1a;知识蒸馏的范式迁移与工程实践 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 摘要 在大型语言模型&#xff…

数据结构:时间复杂度

文章目录 为什么需要时间复杂度分析&#xff1f;一、大O表示法&#xff1a;复杂度的语言1.1 什么是大O&#xff1f;1.2 常见复杂度速查表 二、实战分析&#xff1a;解剖C语言代码2.1 循环结构的三重境界单层循环&#xff1a;线性时间双重循环&#xff1a;平方时间动态边界循环&…

[创业之路-276]:从燃油汽车到智能汽车:工业革命下的价值变迁

目录 前言&#xff1a; 从燃油汽车到智能汽车&#xff1a;工业革命下的价值变迁 前言&#xff1a; 燃油汽车&#xff0c;第一次、第二次工业革命&#xff0c;机械化、电气化时代的产物&#xff0c;以机械和电气自动化为核心价值。 智能汽车&#xff0c;第三次、第四次工业革…

vue页面和 iframe多页面无刷新方案和并行 并接入 micro 微前端部分思路

前: 新进了一家公司,公司是做电商平台的, 用的系统竟然还是jsp的网站,每次修改页面还需要我下载idea代码,作为一个前端, 这可不能忍,于是向上申请,意思你们后台做的太辣鸡,我要重做,经领导层商议从去年6月开始到今年12月把系统给重构了 公司系统采用的是每个jsp页面都是一个ifr…

Python 自学秘籍:开启编程之旅,人生苦短,我用python。

从2009年&#xff0c;用了几次python后就放弃了&#xff0c;一直用的php&#xff0c;现在人工智能时代&#xff0c;完全没php什么事情。必须搞python了&#xff0c;虽然已经40多岁了。死磕python了。让滔滔陪着你一起学python 吧。 开启新世界 在当今人工智能化的时代&#xff…

每日一题洛谷P5721 【深基4.例6】数字直角三角形c++

#include<iostream> using namespace std; int main() {int n;cin >> n;int t 1;for (int i 0; i < n; i) {for (int j 0; j < n - i; j) {printf("%02d",t);t;}cout << endl;}return 0; }

解决DeepSeek服务器繁忙问题:本地部署与优化方案

deepseek服务器崩了&#xff0c;手把手教你如何在手机端部署一个VIP通道&#xff01; 引言 随着人工智能技术的快速发展&#xff0c;DeepSeek等大语言模型的应用越来越广泛。然而&#xff0c;许多用户在使用过程中遇到了服务器繁忙、响应缓慢等问题。本文将探讨如何通过本地部…

【后端开发】系统设计101——通信协议,数据库与缓存,架构模式,微服务架构,支付系统(36张图详解)

【后端开发】系统设计101——通信协议&#xff0c;数据库与缓存&#xff0c;架构模式&#xff0c;微服务架构&#xff0c;支付系统&#xff08;36张图&#xff09; 文章目录 1、通信协议通信协议REST API 对比 GraphQL&#xff08;前端-web服务&#xff09;grpc如何工作&#x…