1. 程式人生 > >深入解析深拷貝和淺拷貝

深入解析深拷貝和淺拷貝

原文:http://www.cnblogs.com/zhili/p/DeepCopy.html

一、前言

  這個星期參加了一個面試,面試中問到深淺拷貝的區別,然後我就簡單了講述了它們的之間的區別,然後面試官又繼續問,如何實現一個深拷貝呢?當時只回答回答了一種方式,就是使用反射,然後面試官提示還可以通過反序列化和表達樹的方式。然後又繼續問,如果用反射來實現深拷貝的話,如何解決互相引用物件的問題呢? 當時我給出的答案是說那就不用反射去實現唄,用反序列化實現唄,或者直接避免使兩個物件互相引用唄。然後面試官說,如果一定用反射來寫,你是怎麼去解決這個問題呢?這時候我就愣住了。

  這樣也就有了這篇文章。今天就來深入解析下深淺拷貝的問題。

二、深拷貝 Vs 淺拷貝

  首先,講到深淺拷貝,自然就有一個問題來了?什麼是深拷貝,什麼又是淺拷貝呢?下面就具體介紹下它們的定義。

  深拷貝:指的是拷貝一個物件時,不僅僅把物件的引用進行復制,還把該物件引用的值也一起拷貝。這樣進行深拷貝後的拷貝物件就和源物件互相獨立,其中任何一個物件的改動都不會對另外一個物件造成影響。舉個例子,一個人叫張三,然後使用克隆技術以張三來克隆另外一個人叫李四,這樣張三和李四就是相互獨立的,不管張三缺胳膊還是李四少腿了都不會影響另外一個人。在.NET領域,值物件就是典型的例子,如int, Double以及結構體和列舉等。具體例子如下所示:

複製程式碼
int source = 123
; // 值型別賦值內部執行深拷貝 int copy = source; // 對拷貝物件進行賦值不會改變源物件的值 copy = 234; // 同樣對源物件賦值也不會改變拷貝物件的值 source = 345;
複製程式碼

  淺拷貝:指的是拷貝一個物件時,僅僅拷貝物件的引用進行拷貝,但是拷貝物件和源物件還是引用同一份實體。此時,其中一個物件的改變都會影響到另一個物件。例如,一個人一開始叫張三,後來改名字為張老三了,可是他們還是同一個人,不管張三缺胳膊還是張老三少腿,都反應在同一個人身上。在.NET中引用型別就是一個例子。如類型別。具體例子如下所示:

複製程式碼
public class Person
    {
        
public string Name { get; set; } } class Program { static void Main(string[] args) { Person sourceP = new Person() { Name = "張三" }; Person copyP = sourceP; // 淺拷貝 copyP.Name = "張老三"; // 拷貝物件改變Name值 // 結果都是"張老三",因為實現的是淺拷貝,一個物件的改變都會影響到另一個物件 Console.WriteLine("Person.Name: [SourceP: {0}] [CopyP:{1}]", sourceP.Name, copyP.Name); Console.Read(); } }
複製程式碼

三、深淺拷貝的幾種實現方式

   上面已經明白了深淺拷貝的定義,至於他們之間的區別也在定義中也有所體現。介紹完了它們的定義和區別之後,自然也就有了如何去實現它們呢?

  對於,淺拷貝的實現方式很簡單,.NET自身也提供了實現。我們知道,所有物件的父物件都是System.Object物件,這個父物件中有一個MemberwiseClone方法,該方法就可以用來實現淺拷貝,下面具體看看淺拷貝的實現方式,具體演示程式碼如下所示:

複製程式碼
// 繼承ICloneable介面,重新其Clone方法
    class ShallowCopyDemoClass : ICloneable
    {
        public int intValue = 1;
        public string strValue = "1";
        public PersonEnum pEnum = PersonEnum.EnumA;
        public PersonStruct pStruct = new PersonStruct() {  StructValue = 1};
        public Person pClass = new Person("1");
        public int[] pIntArray = new int[] { 1 };
        public string[] pStringArray = new string[] { "1" };

        #region ICloneable成員
        public object Clone()
        {
            return this.MemberwiseClone();
        }

        #endregion 

    }

    class Person
    {
        public string Name;
        public Person(string name)
        {
            Name = name;
        }
    }

    public enum PersonEnum
    {
        EnumA = 0,
        EnumB = 1
    }

    public struct PersonStruct
    {
        public int StructValue;
    }
複製程式碼

  上面類中重寫了IConeable介面的Clone方法,其實現直接呼叫了Object的MemberwiseClone方法來完成淺拷貝,如果想實現深拷貝,也可以在Clone方法中實現深拷貝的邏輯。接下來就是對上面定義的類進行淺拷貝測試了,看看是否是實現的淺拷貝,具體演示程式碼如下所示:

複製程式碼
class Program
    {
        static void Main(string[] args)
        {
            ShallowCopyDemo();
            // List淺拷貝的演示
            ListShallowCopyDemo();
        }

        public static void ListShallowCopyDemo()
        {
            List<PersonA> personList = new List<PersonA>() 
            {
                new PersonA() { Name="PersonA", Age= 10, ClassA= new A() { TestProperty = "AProperty"} },
                new PersonA() { Name="PersonA2", Age= 20, ClassA= new A() { TestProperty = "AProperty2"} }
            };
            // 下面2種方式實現的都是淺拷貝
            List<PersonA> personsCopy = new List<PersonA>(personList);
            PersonA[] personCopy2 = new PersonA[2];
            personList.CopyTo(personCopy2);

       // 由於實現的是淺拷貝,所以改變一個物件的值,其他2個物件的值都會發生改變,因為它們都是使用的同一份實體,即它們指向記憶體中同一個地址  personsCopy.First().ClassA.TestProperty
= "AProperty3"; WriteLog(string.Format("personCopy2.First().ClassA.TestProperty is {0}", personCopy2.First().ClassA.TestProperty)); WriteLog(string.Format("personList.First().ClassA.TestProperty is {0}", personList.First().ClassA.TestProperty)); WriteLog(string.Format("personsCopy.First().ClassA.TestProperty is {0}", personsCopy.First().ClassA.TestProperty));
       Console.Read();  }
public static void ShallowCopyDemo() { ShallowCopyDemoClass DemoA = new ShallowCopyDemoClass(); ShallowCopyDemoClass DemoB = DemoA.Clone() as ShallowCopyDemoClass ; DemoB.intValue = 2; WriteLog(string.Format(" int->[A:{0}] [B:{1}]", DemoA.intValue, DemoB.intValue)); DemoB.strValue = "2"; WriteLog(string.Format(" string->[A:{0}] [B:{1}]", DemoA.strValue, DemoB.strValue)); DemoB.pEnum = PersonEnum.EnumB; WriteLog(string.Format(" Enum->[A: {0}] [B:{1}]", DemoA.pEnum, DemoB.pEnum)); DemoB.pStruct.StructValue = 2; WriteLog(string.Format(" struct->[A: {0}] [B: {1}]", DemoA.pStruct.StructValue, DemoB.pStruct.StructValue)); DemoB.pIntArray[0] = 2; WriteLog(string.Format(" intArray->[A:{0}] [B:{1}]", DemoA.pIntArray[0], DemoB.pIntArray[0])); DemoB.pStringArray[0] = "2"; WriteLog(string.Format("stringArray->[A:{0}] [B:{1}]", DemoA.pStringArray[0], DemoB.pStringArray[0])); DemoB.pClass.Name = "2"; WriteLog(string.Format(" Class->[A:{0}] [B:{1}]", DemoA.pClass.Name, DemoB.pClass.Name));
       Console.WriteLine();
      }
private static void WriteLog(string msg) { Console.WriteLine(msg); }   } }
複製程式碼

  上面程式碼的執行結果如下圖所示:

  從上面執行結果可以看出,.NET中值型別預設是深拷貝的,而對於引用型別,預設實現的是淺拷貝。所以對於類中引用型別的屬性改變時,其另一個物件也會發生改變。

  上面已經介紹了淺拷貝的實現方式,那深拷貝要如何實現呢?在前言部分已經介紹了,實現深拷貝的方式有:反射、反序列化和表示式樹。在這裡,我只介紹反射和反序列化的方式,對於表示式樹的方式在網上也沒有找到,當時面試官說是可以的,如果大家找到了表示式樹的實現方式,麻煩還請留言告知下。下面我們首先來看看反射的實現方式吧:

複製程式碼
// 利用反射實現深拷貝
        public static T DeepCopyWithReflection<T>(T obj)
        {
            Type type = obj.GetType();

            // 如果是字串或值型別則直接返回
            if (obj is string || type.IsValueType) return obj;

            if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);
                for (int i = 0; i < array.Length; i++)
                {
                    copied.SetValue(DeepCopyWithReflection(array.GetValue(i)), i);
                }

                return (T)Convert.ChangeType(copied, obj.GetType());
            }

            object retval = Activator.CreateInstance(obj.GetType());
            
            PropertyInfo[] properties = obj.GetType().GetProperties(
                BindingFlags.Public | BindingFlags.NonPublic
                | BindingFlags.Instance | BindingFlags.Static);
            foreach (var property in properties)
            {
                var propertyValue = property.GetValue(obj, null);
                if (propertyValue == null)
                    continue;
                property.SetValue(retval, DeepCopyWithReflection(propertyValue), null);
            }

            return (T)retval;
        }
複製程式碼

  反序列化的實現方式,反序列化的方式也可以細分為3種,具體的實現如下所示:

複製程式碼
 // 利用XML序列化和反序列化實現
        public static T DeepCopyWithXmlSerializer<T>(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                XmlSerializer xml = new XmlSerializer(typeof(T));
                xml.Serialize(ms, obj);
                ms.Seek(0, SeekOrigin.Begin);
                retval = xml.Deserialize(ms);
                ms.Close();
            }

            return (T)retval;
        }

        // 利用二進位制序列化和反序列實現
        public static T DeepCopyWithBinarySerialize<T>(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                // 序列化成流
                bf.Serialize(ms, obj);
                ms.Seek(0, SeekOrigin.Begin);
                // 反序列化成物件
                retval = bf.Deserialize(ms);
                ms.Close();
            }

            return (T)retval;
        }

        // 利用DataContractSerializer序列化和反序列化實現
        public static T DeepCopy<T>(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                DataContractSerializer ser = new DataContractSerializer(typeof(T));
                ser.WriteObject(ms, obj);
                ms.Seek(0, SeekOrigin.Begin);
                retval = ser.ReadObject(ms);
                ms.Close();
            }
            return (T)retval;
        }
        
        // 表示式樹實現
        // ....
複製程式碼

四、使用反射進行深拷貝如何解決相互引用的問題

  上面反射的實現方式,對於相互引用的物件會出現StackOverflower的錯誤,由於物件的相互引用,會導致方法迴圈呼叫。下面就是一個相互引用物件的例子:

複製程式碼
[Serializable]
    public class DeepCopyDemoClass
    {
        public string Name {get;set;}
        public int[] pIntArray { get; set; }
        public Address Address { get; set; }
        public DemoEnum DemoEnum { get; set; }

        // DeepCopyDemoClass中引用了TestB物件,TestB類又引用了DeepCopyDemoClass物件,從而造成了相互引用
        public TestB TestB {get;set;}

        public override string ToString()
        {
            return "DeepCopyDemoClass";
        }
    }

    [Serializable]
    public class TestB
    {
        public string Property1 { get; set; }

        public DeepCopyDemoClass DeepCopyClass { get; set; }

        public override string ToString()
        {
            return "TestB Class";
        }
    }

    [Serializable]
    public struct Address
    {
        public string City { get; set; }
    }

    public enum DemoEnum
    {
        EnumA = 0,
        EnumB = 1
    }
複製程式碼

  在面試過程中,針對這個問題的解決方式我回答的是不知道,回來之後思考了之後,也就有了點思路。首先想到的是:能不能用一個字典來記錄每個物件被反射的次數,仔細想想可行,於是開始實現,初步修復後的反射實現如下所示:

複製程式碼
  public class DeepCopyHelper
    {
        // 用一個字典來存放每個物件的反射次數來避免反射程式碼的迴圈遞迴
        static Dictionary<Type, int> typereflectionCountDic = new Dictionary<Type, int>();

 public static T DeepCopyWithReflection_Second<T>(T obj)
        {
            Type type = obj.GetType();

            // 如果是字串或值型別則直接返回
            if (obj is string || type.IsValueType) return obj;

            if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);
                for (int i = 0; i < array.Length; i++)
                {
                    copied.SetValue(DeepCopyWithReflection_Second(array.GetValue(i)), i);
                }

                return (T)Convert.ChangeType(copied, obj.GetType());
            }

            // 對於類型別開始記錄物件反射的次數
            int reflectionCount = Add(typereflectionCountDic, obj.GetType());
            if (reflectionCount > 1)
                return obj; // 這裡有錯誤

            object retval = Activator.CreateInstance(obj.GetType());

            PropertyInfo[] properties = obj.GetType().GetProperties(
                BindingFlags.Public | BindingFlags.NonPublic
                | BindingFlags.Instance | BindingFlags.Static);
            foreach (var property in properties)
            {
                var propertyValue = property.GetValue(obj, null);
                if (propertyValue == null)
                    continue;
                property.SetValue(retval, DeepCopyWithReflection_Second(propertyValue), null);
            }

            return (T)retval;
        }
        private static int Add(Dictionary<Type, int> dict, Type key)
        {
            if (key.Equals(typeof(String)) || key.IsValueType) return 0;
            if (!dict.ContainsKey(key))
            {
                dict.Add(key, 1);
                return dict[key];
            }

            dict[key] += 1;
            return dict[key];
        }
}    
複製程式碼

  下面用程式碼來測試下上面的程式碼是否已經解決了迴圈遞迴的問題,具體的測試程式碼如下所示:

複製程式碼
class Program
    {
        static void Main(string[] args)
        {
            //ShallowCopyDemo();
            //ListShallowCopyDemo();
            DeepCopyDemo();
            DeepCopyDemo2();
        }
        private static void WriteLog(string msg)
        {
            Console.WriteLine(msg);
        }

        public static void DeepCopyDemo()
        {
            DeepCopyDemoClass deepCopyClassA = new DeepCopyDemoClass();
            deepCopyClassA.Name = "DeepCopyClassDemo";
            deepCopyClassA.pIntArray = new int[] { 1 };
            deepCopyClassA.DemoEnum = DemoEnum.EnumA;
            deepCopyClassA.Address = new Address() { City = "Shanghai" };

            deepCopyClassA.TestB = new TestB() { Property1 = "TestProperty", DeepCopyClass = deepCopyClassA };

            // 使用反序列化來實現深拷貝
            DeepCopyDemoClass deepCopyClassB = DeepCopyHelper.DeepCopyWithBinarySerialize<DeepCopyDemoClass>(deepCopyClassA);
            deepCopyClassB.Name = "DeepCopyClassDemoB";
            WriteLog(string.Format("    Name->[A:{0}] [B:{1}]", deepCopyClassA.Name, deepCopyClassB.Name));
            deepCopyClassB.pIntArray[0] = 2;
            WriteLog(string.Format("    intArray->[A:{0}] [B:{1}]", deepCopyClassA.pIntArray[0], deepCopyClassB.pIntArray[0]));
            deepCopyClassB.Address = new Address() { City = "Beijing" };
            WriteLog(string.Format("    Addressstruct->[A: {0}] [B: {1}]", deepCopyClassA.Address.City, deepCopyClassB.Address.City));
            deepCopyClassB.DemoEnum = DemoEnum.EnumB;
            WriteLog(string.Format("    DemoEnum->[A: {0}] [B: {1}]", deepCopyClassA.DemoEnum, deepCopyClassB.DemoEnum));
            deepCopyClassB.TestB.Property1 = "TestPropertyB
            
           

相關推薦

深入解析拷貝拷貝

原文:http://www.cnblogs.com/zhili/p/DeepCopy.html 一、前言   這個星期參加了一個面試,面試中問到深淺拷貝的區別,然後我就簡單了講述了它們的之間的區別,然後面試官又繼續問,如何實現一個深拷貝呢?當時只回答回答了一種方式,就

深入剖析javaScript中的拷貝拷貝

在面試時經常會碰到面試官問:什麼是深拷貝和淺拷貝,請舉例說明?如何區分深拷貝與淺拷貝,簡單來說,假設B複製了A,當修改A時,看B是否會發生變化,如果B也跟著變了,說明這是淺拷貝,如果B沒變,那就是深拷貝;我們先看兩個簡單的案例: //案例1(深拷貝) var a1 = 1, a2= a1; conso

結合多個例項深入理解js的拷貝拷貝,多種方法實現物件的拷貝

親們為什麼要研究深拷貝和淺拷貝呢,因為我們專案開發中有許多情況需要拷貝一個數組抑或是物件,但是單純的靠=“賦值”並不會解決所有問題,如果遇到引用型別的物件改變新賦值的物件會造成原始物件也發生同樣改變,而要去除影響就必須用到深拷貝,深拷貝,對於引用物件需要進行深拷貝才會去除影響。如果是值型別直接“=”

解析js中的拷貝拷貝

js中的淺拷貝和深拷貝,只是針對複雜資料型別(Objcet,Array)的複製問題。簡單來講淺拷貝和深拷貝都可以實現在原有物件的基礎上再生成一份的作用。但是根據新生成的物件能否影響到原物件可以分為淺拷貝和深拷貝。 概念1:淺拷貝 淺拷貝就是指拷貝引用,新生成的引用和原來的引

js中的拷貝拷貝

所有 object 簡單的 col images new color 其他 java 深復制和淺復制只針對像 Object, Array 這樣的復雜對象的。簡單來說,淺復制只復制一層對象的屬性,而深復制則遞歸復制了所有層級。 深淺拷貝 的主要區別就是:復制的是引用(地址)還

python學習系列--拷貝拷貝

深拷貝 淺拷貝 copy deepcopy概念普通情下,復制一個對象是不會新開辟內存空間的,只是把新的對象名稱指向原有的內存地址,這種操作其實不是算是拷貝,只是新的引用。把新的對象置於新的內存空間中,才是拷貝。在python中,深淺拷貝的區別實際上是拷貝的深度不同。操作常見的‘=’號就是一種拷貝方式。pyth

js 中引用類型 的拷貝 拷貝的區別

而是 query reac cat 避免 string val this 臨時 一、曾經在讀JQ源碼的時候,對深拷貝算是有了一點的理解。我們在項目中是不是經常會遇到這樣的問題呢? 後臺返回一個數組對象(引用類型).次數在頁面渲染中需要對部分數據進行處理 比如:銀行卡6234

談Java中的拷貝拷貝

detail tle pac err @override 復制對象 deep har 間接   淺談Java中的深拷貝和淺拷貝(轉載) 原文鏈接: http://blog.csdn.net/tounaobun/article/details/8491392 假如說你想復制一

js的命名空間 && 單體模式 && 變量拷貝拷貝 && 頁面彈窗設計

但是 界面 ket 模式 utf 針對 col con prop 說在前面:這是我近期開發或者看書遇到的一些點,覺得還是蠻重要的。 一、為你的 JavaScript 對象提供命名空間 <!DOCTYPE html> <html> <head&

拷貝拷貝

深拷貝 淺拷貝 #include <stdio.h> int main(int argc, char *argv[]) { char *p1="123"; char *p2="123"; char *p3="456"; const char *p4="ab

python的復制,拷貝拷貝的區別(轉)

pla bsp space 數據 深拷貝 淺拷貝 deepcopy 拷貝 tro 在python中,對象賦值實際上是對象的引用。當創建一個對象,然後把它賦給另一個變量的時候,python並沒有拷貝這個對象,而只是拷貝了這個對象的引用 一般有三種方法, alist=[1,2,

python的拷貝拷貝

內存區域 需要 不可變 python3 復制 deepcopy 原始的 pen -m # 對象賦值 a = 'hello world' b = a print('a:',a,', b:',b) # a: hello world

Python-8 拷貝拷貝

water mark alt img 分享 深拷貝 所有 nag copy 淺拷貝 淺拷貝是對於一個對象的頂層拷貝通俗的理解是:拷貝了引用,並沒有拷貝內容 深拷貝 深拷貝是對於一個對象所有層次的拷貝(遞歸) 進一步理解拷貝 拷貝的其他方式 使用copy模

【轉載】圖解 Python 拷貝拷貝

div 原子 總結 但是 home 後來 idt scrip 需要 作者:田小計劃 出處:http://www.cnblogs.com/wilber2013/ Python中,對象的賦值,拷貝(深/淺拷貝)之間是有差異的,如果使用的時候不註意,就可能產生意外的結果。

對象的拷貝拷貝

height post ++ oid 一份 char log pac clu 在copy一個對象時(用一個對象去初始化另外一個對象),會調用類中的拷貝構造函數。如果我們自己沒有在類裏面寫拷貝構造函數,則C++編譯器會調用默認的拷貝構造函數。 淺拷貝:如果類定義的對象包含

拷貝拷貝的區別

log birt 並且 ace UNC 分享 一個 eof 發生 深拷貝和淺拷貝的區別 深拷貝和淺拷貝最根本的區別在於是否真正獲取一個對象的復制實體,和不是引用。 簡單來說 淺拷貝(shallowCopy)只是增加了一個指針指向已存在的內存地址, 深拷貝(deepCop

python的復制,拷貝拷貝的區別

一個 對象賦值 source 深拷貝 對象的引用 數據 拷貝 也會 方法 寫在前面: python中的.copy()拷貝和[:]拷貝皆為淺拷貝 在python中,對象賦值實際上是對象的引用。當創建一個對象,然後把它賦給另一個變量的時候,python並沒有拷貝這個對象,而只是

C++本質:類的賦值運算符=的重載,以及拷貝拷貝

fin 過程 種類 解決 對象的引用 執行 面向 鏈式 alt 關鍵詞:構造函數,淺拷貝,深拷貝,堆棧(stack),堆heap,賦值運算符摘要: 在面向對象程序設計中,對象間的相互拷貝和賦值是經常進行的操作。 如果對象在申明的同時馬上進行的初始化操作,則

JavaScript的拷貝拷貝

復制 nsh book 並不會 turn 字符串 重要 jce 兩種 原文   簡書原文:https://www.jianshu.com/p/3d930756dd8f 大綱   前言  1、對深拷貝和淺拷貝的初步認識  2、深拷貝和淺拷貝的區別  3、淺拷貝存在的缺陷  4

淺析Python中拷貝拷貝

int lis end 四種 都是 變量 內存空間 string -- 按照以下不同情況,在IDE中逐個解除註釋,就明白了 import copy """ 第一種情況,不可變類型變量,都是引用 """ # a = 1 # a = (11, 222, 333) # a =