首页 > 编程知识 正文

对象深拷贝和浅拷贝,core i9

时间:2023-05-04 03:48:53 阅读:22006 作者:1123

只要语言只有基本类型,没有引用类型,用任何编程语言克隆对象都很困难。

1、前言“老沈是什么是深克隆,GX滴滴涕是什么? ”

“哈,你迷路了吗? 这种深浅的体验让你晕过去了吗? ”

“嗯,这是什么程序员的黑话? ”

“这是专业术语! 由于有“引用类型”的概念,出现了暗克隆和GX滴滴涕的名词。 ”

“先谈谈堆栈(Heap )和堆栈)吧。 ”

“好了,冷静点,听我说! ”

2、堆和堆栈堆:net准确地说是主机堆,由CLR管理,满后自动清理垃圾,所以做. net开发,几乎不用在意内存的释放。 原理还是需要理解。 此外,根据引用类型实例的大小,将“堆”分为“GC堆”和“loh(largeobjectheap )”堆,如果引用类型实例的大小小于85000字节,则实例为“GC堆” 如果实例的大小大于或等于85000字节,则会将该实例分配给“loh(largeobjectheap )堆”。

堆栈:翻译后应该是堆栈。 因为旧的,叠起来的,容易混在一起,所以现在一般简称堆栈。 堆和堆栈是运行程序时主要存储数据的两个存储区域。 存储堆区引用类型的对象主要由CLR/GC释放; 包含栈区函数的参数、局部变量和返回数据。 那个内存不需要我们管理,也不接受GC的管理。 堆栈元素弹出后,它会立即释放。

堆栈空间的大小在32位APP应用程序中为1MB,在64位APP应用程序中为4MB。 为什么会这样呢? 请看下面的David Cutler。 是他制定的规则。 这是windwosNT系统的统一标准配置,与. net关系不大。

如果在程序的EXE堆栈大小或CreateThread ()调用中未明确指定堆栈大小,则会选择1MB字节,大多数程序员会让操作系统选择大小。

当然有很多1MB字节。 真正的线程很少占用数千字节以上的内存。 因此,兆字节实际上相当浪费。 此外,由于Windows内存机制,按需分页的虚拟内存操作系统可以承受这种浪费。 兆字节是虚拟内存,是处理器的编号,在实际寻址之前,不会实际使用物理内存(计算机中的RAM )。

在. net世界中,我们的程序不接受程序员自己分配堆栈空间,除非在不安全的模式下使用stackalloc关键字。

你可能觉得1MB有点小,但实际上恰恰相反。 1MB太多,大大降低了操作系统创建线程的能力。 实际上,asp.net只有256KB~512KB之间,实际使用可能为4KB左右。 另外,操作系统倾向于优化为只提交所需的堆栈大小。

管理线程堆栈时的windows自动添加映像。

在ILDASM中,查看PE报头可以看到实际分配的堆栈区域的大小。

也可以更改. net运行时的堆栈提交方法。

运行公共语言时的默认行为是在线程启动时提交完整的线程堆栈。 如果需要在内存有限的服务器上创建大量线程,并且使用较少的堆栈空间,则在运行公共语言时执行线程后不立即提交完整的线程堆栈可能会提高服务器的性能。

configuration runtimedisablecommitthreadstackenabled='1'//runtime/configuration堆栈资源代表了什么? 简而言之,每个线程需要1MB的堆栈大小。 其他消耗暂且不论,在windows 32位系统上,内存地址空间只有2GB,因此最多只能创建2048个线程。 当然,实际上会比这个小。 这样,windwos 64位操作系统也只能创建4096个线程。 当然,这没有考虑到windows堆栈提交的优化。 根据需要提交时,在同一内存下支持更多线程创建。

因此,要提高windwos的线程创建能力,必须降低默认的1MB设置。 当然,有修改这个设置的工具。 感兴趣的人可以试着找Testlimit。

为什么要从内存迁移到堆栈或“加载”? 另一方面,为什么要从堆栈迁移到内存或“存储”呢? 为什么不把它们全部放进内存里?

因为很简单!

因为在概念上,堆栈对语言编译器的作者来说非常简单。 堆栈是用于说明计算的简单易懂的结构。 对于JIT编译器的作者来说,堆栈在概念上也非常简单。 堆栈的使用是一个简单的抽象,因此也降低了成本。

“为什么要叠呢? ’为什么不直接用完所有内存?

是的,让我想想。 假设要生成以下CIL代码:

intx=a(b ) c ) ) 10;

只有堆栈的约定是,“添加”、“呼叫”、“存储”等始终将其参数从堆栈中移出,如果有结果,则将其放置在堆栈中。 要为此C#生成CIL代码,只需说以下内容:

loadtheaddressofx//thestacknowcontainsaddressofxcalla (

// The stack contains address of x and result of A()call B() // Address of x, result of A(), result of B()add // Address of x, result of A() + B()call C() // Address of x, result of A() + B(), result of C()add // Address of x, result of A() + B() + C()load 10 // Address of x, result of A() + B() + C(), 10add // Address of x, result of A() + B() + C() + 10store in address // The result is now stored in x, and the stack is empty.

现在,我们将按照您的方式进行操作,其中每个操作码都将获取其操作数的地址以及将其结果存储到的地址:

Allocate temporary store T1 for result of A()Call A() with the address of T1Allocate temporary store T2 for result of B()Call B() with the address of T2Allocate temporary store T3 for the result of the first additionAdd contents of T1 to T2, then store the result into the address of T3Allocate temporary store T4 for the result of C()Call C() with the address of T4Allocate temporary store T5 for result of the second addition...

这是怎么回事吗?我们的代码越来越庞大,因为我们必须显式分配通常按照约定会放在栈上的所有临时存储。更糟糕的是,我们的操作码本身变得越来越庞大,因为它们现在都必须将要写入结果的地址以及每个操作数的地址作为参数。一条“ add”指令知道它将要从堆栈中取出两件事并放在一件事上,这可以是一个字节。一个带有两个操作数地址和一个结果地址的加法指令将非常庞大。

我们使用基于栈的操作码,因为栈可以解决常见的问题。即:我想分配一些临时存储,请尽快使用它,然后在完成后迅速删除它。假设我们有可用的栈,我们可以使操作码非常小,并使代码非常简洁。

3、值类型、引用类型

切入整体,bool 、byte 、char 、decimal 、double 、enum 、float 、int 、long 、sbyte 、short 、struct 、uint 、ulong 、ushort这些值类型都存储在栈内,而class 、interface 、delegate 、object 、string这些类型均存储在堆中。

对于引用类型,都会定义一个指针指向堆内存,因此在我们成为浅拷贝的时候,拷贝的实际是引用类型的指针。而值类型是直接拷贝的。
一个例子:

class Person{ public int Age { get; set; } public Person Father { get; set; } public Person Mother { get; set; }}

如果我对该对象进行了gxddt并更改了使用期限,则原始对象的使用期限将不会更改。
但是,如果我随后更改了克隆对象的父亲的属性,那么我也会影响原始对象的父亲,因为未克隆引用。

换一种方式思考,在C#中,当您对对象进行gxddt时,您相比深克隆“浅一层”,因为变浅了,您要克隆的对象内的任何对象本身也不会递归地克隆。

深度克隆显然是相反的。它一直尝试克隆对象的所有属性,然后克隆该属性的属性。

4、成员克隆

如果您对C#中的克隆进行了研究,则可能遇到了“成员方式”克隆方法。它对每个类均可用,但“仅在该类内部”可用,因为它是Object的受保护方法。您不能在另一个类的对象上调用它。

class Person{ public string Name { get; set; } public Person Father { get; set; } public Person Mother { get; set; } public Person Clone() { return (Person)this.MemberwiseClone(); }}

然而,快速浏览一下智能感知就可以告诉我们一些…

创建当前对象的浅表副本。

因此,在此对象上调用clone只会对其进行gxddt,而不会进行深克隆。
如果您的对象是纯粹的值对象,那么这实际上可以为您工作,您可以在这里停下来。但是在大多数情况下,我们正在寻找更深的克隆。

5、手动克隆

反正,你最了解你的类,不是吗?

class Person{ public string Name { get; set; } public Person Father { get; set; } public Person Mother { get; set; } public Person Clone() { return new Person { Name = this.Name, Father = this.Father == null ? null : new Person { Name = this.Father.Name }, Mother = this.Mother == null ? null : new Person { Name = this.Mother.Name } }; }}

如果属性不多,不失为一个好办法。

6、二进制序列化器克隆 [Serializable]class Person{ public string Name { get; set; } public Person Father { get; set; } public Person Mother { get; set; } public Person Clone() { IFormatter formatter = new BinaryFormatter(); using (stream = new MemoryStream()) { formatter.Serialize(stream, this); stream.Seek(0, SeekOrigin.Begin); return (Person)formatter.Deserialize(stream); } }}

我们必须用[Serializable]属性来修饰我们的类,否则,我们会得到异常错误,这看起来不Nice!
当然你可以改良为一个静态方法

public static class CloningService{public static T Clone<T>(this T source){// Don't serialize a null object, simply return the default for that objectif (Object.ReferenceEquals(source, null)){return default(T);}IFormatter formatter = new BinaryFormatter();using (stream = new MemoryStream()){formatter.Serialize(stream, source);stream.Seek(0, SeekOrigin.Begin);return (T)formatter.Deserialize(stream);}}}

当然 [Serializable]标记别忘了。

7、json序列化克隆 public static class CloningService{public static T Clone<T>(this T source){// Don't serialize a null object, simply return the default for that objectif (Object.ReferenceEquals(source, null)){return default(T);}var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };var serializeSettings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, serializeSettings), deserializeSettings);}}

我只能说,每次必须这样做时,我都认为“将其转换为JSON并再次返回确实不是很好……”。
但这是可行的。它可以处理您扔给它的所有东西,而且几乎没有错。

我认为,如果您正在寻找可靠的,可重用的方法来在代码中克隆对象,就是这个。

8、json克隆的循环

眼尖的读者会注意到,在上述JSON克隆服务中,我们有一行处理引用循环。

这是非常非常普遍的情况,尤其是在用作DataModel的一部分的模型中,两个类将相互引用。无论您如何决定克隆对象,始终会遇到对象之间相互引用的问题,并且任何克隆尝试都将陷入无休止的循环。

因此,即使在上面的代码中,我们使用一个设置来解决它,也值得指出的是,无论您决定克隆对象的方式如何,这总是一个问题。

9、小结

亲爱的读者,你有什么高招?

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。