java深拷貝和淺拷貝
1. 深拷貝與淺拷貝的實現(一)
最近的學習中,仔細研究了下深拷貝和淺拷貝,下面就來簡單的總結下。
首先我們了解下兩種 數據類型 :
1、基本類型:像Number、String、Boolean等這種為基本類型
2、復雜類型:Object和Array
接著我們分別來了解下淺拷貝和深拷貝,深拷貝和淺拷貝是只針對Object和Array這樣的復雜類型的。
淺拷貝 :
可以看出,對於對象或數組類型,當我們將a賦值給b,然後更改b中的屬性,a也會隨著變化。也就是說a和b指向了同一塊內存,所以修改其中任意的值,另一個值都會隨之變化,這就是淺拷貝。
深拷貝 :
剛剛我們了解了什麼是淺拷貝,那麼相應的,如果給b放到新的內存中,將a的各個屬性都復制到新內存里,就是深拷貝。
也就是說,當b中的屬性有變化的時候,a內的屬性不會發生變化。
那麼除了上面簡單的賦值引用,還有哪些方法使用了 淺拷貝 呢?
Object.assign()
在MDN上介紹Object.assign():」Object.assign() 方法用於將所有可枚舉的屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象。」
復制一個對象
可以看到,Object.assign()拷貝的只是屬性值,假如源對象的屬性值是一個指向對象的引用,它也只拷貝那個引用值。所以Object.assign()只能用於淺拷貝或是合並對象。這是Object.assign()值得注意的地方。
那麼下面我們就來說說復雜的 深拷貝 。
jQuery.extend()
說到深拷貝,第一想到的就是jQuery.extend()方法,下面我們簡單看下jQuery.extend()的使用。
jQuery.extend( [deep ], target, object1 [, objectN ] ),其中deep為Boolean類型,如果是true,則進行深拷貝。
我們還是用上面的數據來看下extend()方法。
通過上面的對比可以看出,當使用extend()進行深拷貝的時候,對象的所有屬性都添加到target中了。
我們知道了extend()可以進行深拷貝,那麼extend()是如何實現深拷貝的呢?
先來看下jQuery.extend()源碼
主要看下關於深拷貝的部分,取第一個參數,如果是boolean類型的,就賦值給deep,下面如果deep為true(也就是進行深拷貝),就遞歸調用extend(),這樣就將對象的所有屬性都添加到了target中實現了深拷貝。
JSON.parse()和JSON.stringify()
上面的jQuery源碼是否讓你眼花繚亂?有沒有什麼辦法無腦實現深拷貝呢?JSON.parse()和JSON.stringify()給了我們一個基本的解決辦法。
可以看到改變targetCopy並沒有改變原始的target,繼承的屬性也沒有丟失,因此實現了基本的深拷貝。
但是用JSON.parse()和JSON.stringify()會有一個問題。
JSON.parse()和JSON.stringify()能正確處理的對象只有Number、String、Array等能夠被json表示的數據結構,因此函數這種不能被json表示的類型將不能被正確處理。
上面的例子可以看出,hello這個屬性由於是函數類型,使用JSON.parse()和JSON.stringify()後丟失了。
因此JSON.parse()和JSON.stringify()還是需要謹慎使用。
2. 深拷貝、淺拷貝的理解與使用場景
通俗解釋:深拷貝是內容拷貝,淺拷貝是地址拷貝
區別點:
深拷貝會創建一個新的內存空間,拷貝的值是一樣的,但是內存地址不一樣。
淺拷貝只是拷貝指向原來對象的地址,使原對象的引用計數+1
像NSString、NSNumber這些不能包含其他對象的叫做非容器類對象
像NSArray、NSDictionary這些可以包含其他對象的叫容器類對象
列印結果如下:
通過對比不難發現:
上面我們使用的是不可變的NSString,下面我們再使用可變的NSMutableString對比一下:
列印結果如下:
不難發現,對於NSMutableString, 無論是還是mutableCopy都會創建一個新對象,屬於深拷貝
列印結果如下:
不難發現,是淺拷貝,mutableCopy是深拷貝,不過需要注意的是容器對象的成員元素都指向相同的地址
列印結果如下:
對比可見,容器對象與非容器對象類似,可變對象的復制都是深拷貝,不可變對象是淺拷貝,mutableCopy是深拷貝
需要注意的是對容器而言,元素對象始終是指針復制
正如前面所說,容器對象中的元素對象無論是還是mutableCopy都是指針復制,如何實現容器對象的完全深拷貝呢?
系統為我們實現容器對象的完全深拷貝提供了方法
3. java 中淺拷貝與深拷貝有什麼區別
淺拷貝 指的是你的類本身被拷貝,而沒有拷貝類本身屬性中的類
深拷貝 指的是包含類本身和屬性類在內的所有類的拷貝。
簡單點說:
就是淺拷貝的兩個對象中的屬性還會指向同一個類,而深拷貝則全部單獨了。也就是說深拷貝把關聯關系也拷貝了。
具體例子你可以參看我的blog,裡面篇文章介紹:)
4. 編程裡面的深拷貝和淺拷貝各是什麼意思,怎麼理解這兩個
1、淺拷貝:默認的拷貝就是淺拷貝。 僅僅多了個指針指向原來的空間。
2、深拷貝:自己寫的拷貝,自己申請了動態內存空間,用了new 或 malloc 。不但多了指針,而且多了空間。
3、用深拷貝的話,最好用自己寫的析構,記得在裡面釋放內存,也可以用默認析構。
4.用淺拷貝(即默認隱藏的拷貝),最好用默認析構,若用自己寫的析構裡面 ,記得不要釋放內存,不然會造成重復釋放內存而報錯。
5. 徹底講明白淺拷貝與深拷貝
數據分為基本數據類型(String, Number, Boolean, Null, Undefined,Symbol)和對象數據類型。
1、基本數據類型的特點:直接存儲在棧(stack)中的數據
2、引用數據類型的特點: 存儲的是該對象在棧中引用,真實的數據存放在堆內存里
引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中獲得實體。
深拷貝和淺拷貝是只針對Object和Array這樣的引用數據類型的 。
深拷貝和淺拷貝的示意圖大致如下:
淺拷貝只復制指向某個對象的指針,而不復制對象本身,新舊對象還是共享同一塊內存。但深拷貝會另外創造一個一模一樣的對象,新對象跟原對象不共享內存,修改新對象不會改到原對象。
當我們把一個對象賦值給一個新的變數時, 賦的其實是該對象的在棧中的地址,而不是堆中的數據 。也就是兩個對象指向的是同一個存儲空間,無論哪個對象發生改變,其實都是改變的存儲空間的內容,因此,兩個對象是聯動的。
淺拷貝是按位拷貝對象, 它會創建一個新對象 ,這個對象有著原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值;如果屬性是內存地址(引用類型),拷貝的就是內存地址 ,因此如果其中一個對象改變了這個地址,就會影響到另一個對象。即默認拷貝構造函數只是對對象進行淺拷貝復制(逐個成員依次拷貝),即只復制對象空間而不復制資源。
我們先來看兩個例子,對比賦值與淺拷貝會對原對象帶來哪些改變?
上面例子中,obj1是原始數據,obj2是賦值操作得到,而obj3淺拷貝得到。我們可以很清晰看到對原始數據的影響,具體請看下錶:
Object.assign() 方法可以把任意多個的源對象自身的可枚舉屬性拷貝給目標對象,然後返回目標對象。但是 Object.assign() 進行的是淺拷貝,拷貝的是對象的屬性的引用,而不是對象本身。
注意:當object只有一層的時候,是深拷貝
修改新對象會改到原對象:
同樣修改新對象會改到原對象:
關於Array的slice和concat方法的補充說明:Array的slice和concat方法不修改原數組,只會返回一個淺復制了原數組中的元素的一個新數組。
原數組的元素會按照下述規則拷貝:
可能這段話晦澀難懂,我們舉個例子,將上面的例子小作修改:
原理: 用JSON.stringify將對象轉成JSON字元串,再用JSON.parse()把字元串解析成對象,一去一來,新的對象產生了,而且對象會開辟新的棧,實現深拷貝。
這種方法雖然可以實現數組或對象深拷貝,但不能處理函數。
這是因為 JSON.stringify() 方法是將一個JavaScript值(對象或者數組)轉換為一個 JSON字元串,不能接受函數。
遞歸方法實現深度克隆原理: 遍歷對象、數組直到里邊都是基本數據類型,然後再去復制,就是深度拷貝。
該函數庫也有提供 _.cloneDeep 用來做 Deep Copy。
閱讀原文
看完文章留完言,還有福利拿,往下看👇👇👇
感興趣的小夥伴可以在公號【grain先森】後台回復【190313】獲取HTML5詳解、CSS3詳解和Vue詳解及實戰項目,可以轉發朋友圈和你的朋友分享哦。
6. 淺復制和深復制的區別 java
淺復制和深復制的區別 java
大體上來說,深拷貝與淺拷貝的區別主要還是在於指針(或與指針)方面,淺拷貝只是簡單的把源對象(這個是指廣義的對象,不僅僅單指類的實例)的指針賦值給目標對象,對目標指針的操作就是對源對象的操作,所以在很多情況下,目標對象析構(或跳出其可見域)之後,源對象相關部分也就一同析構了。而深拷貝,是為目標對象重新分配空間,這樣可以與源對象的操作分開。
7. 什麼是深拷貝和淺拷貝
淺拷貝就是指對象復制的時候只復制一層;深拷貝是指復制對象的所有層級。
深拷貝和淺拷貝,主要是對象發生復制的時候,根據復制的層級不同來區分的。很多人在這里經常變數賦值發生混淆。對於JavaScript數組等復雜的數據類型來說,將其賦值給其它變數,其實只是復制了對象的地址給它,兩個變數指向的是同一個對象,因此普通的賦值既不是深拷貝也不是淺拷貝。
深拷貝和淺拷貝需要注意的地方就是可變元素的拷貝:
在淺拷貝時,拷貝出來的新對象的地址和原對象是不一樣的,但是新對象裡面的可變元素(如列表)的地址和原對象里的可變元素的地址是相同的,也就是說淺拷貝它拷貝的是淺層次的數據結構(不可變元素),對象里的可變元素作為深層次的數據結構並沒有被拷貝到新地址裡面去。
而是和原對象里的可變元素指向同一個地址,所以在新對象或原對象里對這個可變元素做修改時,兩個對象是同時改變的,但是深拷貝不會這樣,這個是淺拷貝相對於深拷貝最根本的區別。
8. 對象賦值、淺拷貝和深拷貝
賦值是將原對象的內存地址直接給到新對象
生成一個新的對象,新對象擁有原對象的所有屬性。如果屬性值的類型是基本類型,就將原屬性的值拷貝過來;如果屬性值是引用類型,就將原屬性值的地址拷貝過來,如果原屬性值發生改變時,新屬性的值也會發生改變。
將原對象中的各個屬性值重新分配內存地址,不論原對象的屬性值是基本類型還是引用類型,原對象屬性值的變化都不會影響新對象的屬性值。
這種方法雖然可以實現數組或對象深拷貝,但不能處理函數和正則,因為這兩者基於JSON.stringify和JSON.parse處理後,得到的正則就不再是正則(變為空對象),得到的函數就不再是函數(變為null)了
9. java深拷貝和淺拷貝的區別
深拷貝和淺拷貝最大的區別在於淺拷貝更多時候拷貝的是地址、引用這種東西,而深拷貝則是拷貝了一個新地址的對象,具體可以參考以下網址
網頁鏈接
10. java深拷貝和淺拷貝的區別
淺拷貝:只復制一個對象,對象內部存在的指向其他對象數組或者引用則不復制
深拷貝:對象,對象內部的引用均復制
示例:
publicstaticObject(ObjectoldObj){
Objectobj=null;
try{
//Writetheobjectouttoabytearray
ByteArrayOutputStreambos=newByteArrayOutputStream();
ObjectOutputStreamout=newObjectOutputStream(bos);
out.writeObject(oldObj);
out.flush();
out.close();
//
//aoftheobjectbackin.
ByteArrayInputStreambis=newByteArrayInputStream(bos.toByteArray());
ObjectInputStreamin=newObjectInputStream(bis);
obj=in.readObject();
}catch(IOExceptione){
e.printStackTrace();
}catch(ClassNotFoundExceptioncnfe){
cnfe.printStackTrace();
}
returnobj;
}