1. 程式人生 > 其它 >jquery如何拿到一個物件_【設計模式】原型模式:如何快速的克隆出一個物件?...

jquery如何拿到一個物件_【設計模式】原型模式:如何快速的克隆出一個物件?...

技術標籤:jquery如何拿到一個物件

目錄

  • 概念
  • 什麼時候使用原型模式
  • 沒有使用原型模式的Demo
  • 原型模式兩大資料拷貝
    • 淺拷貝
    • 深拷貝
    • 淺拷貝+深拷貝
  • 總結

概念

原型模式是一種建立型設計模式,Prototype模式允許一個物件再建立另外一個可定製的物件,根本無需知道任何如何建立的細節,工作原理是:通過將一個原型物件傳給那個要發動建立的物件,這個要發動建立的物件通過請求原型物件拷貝它們自己來實施建立。

也就是說,當物件的建立過於複雜的時候,我們可以通過複製的方式建立新物件,這樣可以大大的提升程式的效率。

什麼時候使用原型模式

1.物件建立過於複雜:物件建立的時候需要經過計算、排序等操作。2.物件時間過長

:需要查詢資料庫或者通過rpc呼叫。3.物件過多:比如一個集合中有一百萬個物件,我現在需要修改這一百萬的物件的資料,但是原資料不能動,所以這個時候使用原型模式比較適合。

具體的使用場景還是需要看專案情況而定,不能為了讓程式碼看著高大上而去使用設計模式,比如就只有一個物件的建立(沒有任何計算,沒有呼叫資料庫和rpc),這個時候如果使用原型模式,雖然效率也會有一點點的提升,但是這樣做有點得不償失,因為還要考慮到程式碼的可讀性和可維護性多重條件。

這麼說還是比較空洞,大家可能還是不知道在什麼方面使用原型模式,接下來我們將以demo的形式來展示什麼是原型模式。

沒有使用原型模式的Demo

假設我java記憶體裡面存放著部落格的一些資訊,點贊數、評論數、瀏覽數等等資訊,現在我們有一個需求,我們需要每一個小時更新一次部落格的資訊,我們應該怎麼做呢?

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {

    /** * 記憶體中存放的資料 * key:部落格id * value:部落格資訊 */
    private static Map<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /** * 資料庫存放的資料 * key:部落格id * value:部落格資訊 */
    private static Map<Integer, BlogStats>  mapMysql = new HashMap<Integer, BlogStats>();

    /** * 最近一次的修改時間 */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化記憶體中java的部落格資訊
        for(int i = 0; i< 10 ;++i){
           blogStats = BlogStats.builder()
                   .id(i)
                   .title("java是世界上最好的語言"+i)
                   .likeNum(i)
                   .viewNum(i)
                   .commentNum(i)
                   .build();
           mapCache.put(i,blogStats);
        }

        //初始化資料庫中java的部落格資訊
        for(int i = 0; i< 10 ;++i){
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的語言"+i)
                    .likeNum(i+2)
                    .viewNum(i+2)
                    .commentNum(i+2)
                    .build();
            mapMysql.put(i,blogStats);
        }

    }

    /** * 每天定時更新 */
    private static void update(){
        //我們需要將資料庫存放的最新資料獲取出來
        Map<Integer, BlogStats> newData = mapMysql;
        //將新獲取到的資料替換掉記憶體中的資料
        mapCache = newData;
    }


    public static void main(String[] args) {
        System.out.println("記憶體中的原始資料:"+mapCache);
        //更新記憶體資料
        update();
        System.out.println("更新完之後記憶體中的資料:"+mapCache);
    }


}
package com.ymy.entity;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Builder
public class BlogStats {

    /** * 部落格id */
    private Integer id;

    /** * 部落格標題 */
    private String title;

    /** * 點贊數量 */
    private Integer likeNum;

    /** * 瀏覽數量 */
    private Integer viewNum;


    /** * 評論數 */
    private Integer commentNum;

    /** * 更新的時間(時間戳) */
    private Long updateTime;


}

@Getter、@Setter、@ToString、@Builder需要引入依賴

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

上面的程式碼很簡單,就是記憶體中存放了一套資料,用於展示給使用者看,不是實時的,按時間段更新,更新的方式就是在資料庫中獲取到最新的資訊,然後替換掉記憶體中的資料,看似很簡單,但是這種方式存在著很大的問題,那就是效能問題,為什麼這麼說,現在只有10篇部落格,假設存在100w部落格呢?我們也需要在資料庫中獲取出來,然後再替換掉記憶體中的資料?這樣的做法是相當不推薦的,我們想想有沒有什麼比較好一點的方法呢?

我們記憶體中的資料並不是都需要修改的,100w資料真正需要修改的可能只有幾百條或者幾十條,如果從資料庫全部取出替換,實在是沒有必要,所以能不能,只取出變化的資料,然後做修改呢?答案是肯定的,我們一起來改造一下程式碼

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {



    /** * 記憶體中存放的資料 * key:部落格id * value:部落格資訊 */
    private static Map<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /** * 資料庫存放的資料 * key:部落格id * value:部落格資訊 */
    private static Map<Integer, BlogStats>  mapMysql = new HashMap<Integer, BlogStats>();

    /** * 最近一次的修改時間 */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化記憶體中java的部落格資訊
        for(int i = 0; i< 10 ;++i){
           blogStats = BlogStats.builder()
                   .id(i)
                   .title("java是世界上最好的語言"+i)
                   .likeNum(i)
                   .viewNum(i)
                   .commentNum(i)
                   .status(1)
                   .build();
           mapCache.put(i,blogStats);
        }

        //初始化資料庫中java的部落格資訊
        for(int i = 0; i<= 10 ;++i){
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的語言"+i)
                    .likeNum(i+2)
                    .viewNum(i+2)
                    .commentNum(i+2)
                    .status(i >= 8 ? 0 : 1)
                    .build();
            mapMysql.put(i,blogStats);
        }
    }

    /** * 每天定時更新 */
    private static void update(Map<Integer, BlogStats> changeData){
        for(Map.Entry<Integer, BlogStats> entry : changeData.entrySet()){
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            blogStats.setStatus(1);
            if(mapCache.containsKey(id)){
                mapCache.replace(id, blogStats);
            }else{
                mapCache.put(id,blogStats);
            }
        }
    }


    /** * 根據更新的狀態獲取到最新需要修改的資料 * 由於這裡僅僅只是模擬,所以這裡我就隨便造幾條資料,你們主要看思想即可 * @return */
    public static Map<Integer,BlogStats> getChangeData(){

        //在資料庫中查詢還沒有更新過的資料
        //由於我是模擬,所以我直接偽造了幾條資料充當需要修改的資料,還請見諒
        Map<Integer,BlogStats> changeDatas= new HashMap<Integer,BlogStats>();
        for(Map.Entry<Integer, BlogStats> entry : mapMysql.entrySet()){
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            if(blogStats.getStatus() == 0){
                changeDatas.put(id,blogStats);
            }
        }

        return changeDatas;
    }


    public static void main(String[] args) {
        print(mapCache);
        //獲取修改的資料
        Map<Integer, BlogStats> changeData = getChangeData();
        //更新記憶體資料
        update(changeData);
         //將修改的部落格狀態改為1並寫回資料庫,這段省略
         
        System.out.println("更新過後=====================");
        print(mapCache);
    }


    private static void print( Map<Integer, BlogStats> map ){
        for(Map.Entry<Integer, BlogStats> entry : map.entrySet()){
            System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());
        }

    }
}

這是改造之後的程式碼,解釋一下上面的程式碼,
第一:我不在往資料庫中獲取所有的部落格資料,而是通過一個欄位status表示這條記錄是否更新過,只查詢需要更新的部落格資訊即可。
第二:再修改的時候需要判斷再資料庫中查詢出來的資料是否在記憶體中存在,如果存在,執行修改操作,如果不存在,執行新增操作。

按照上面的思路,我們的執行結果應該是部落格id在0-7的資訊無變化,部落格id在8-9被修改,部落格id等於10的被新增,那結果是不是這樣呢?我們直接來看執行結果

key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=8, viewNum=8, commentNum=8, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=9, viewNum=9, commentNum=9, status=1)
更新過後=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的語言10, likeNum=12, viewNum=12, commentNum=12, status=1)

Process finished with exit code 0

從列印的結果來看,id在0-7的沒有發生改變,8-9發生了改變,並且新增了一條id為10的新資料,所以這次的改造時成功的,現在已經優化的很好的,但還沒有使用到設計模式中的原型模式,那我們還有必要使用嗎?

我們之前的操作是直接基於成員變數mapCache,但是我們現在需求稍微做了一下改動,我現在希望,我們需要一次性計算好結果在替換掉成員變數mapCache,而不是一次一次的替換,所以這個時候上面的這種方法就不行了,out了,那我們應該怎麼辦呢?很多人都想到了

  • 第一步:從資料庫中獲取到status = 1 的所有資料,也就是已經更新過的資料,這個資料和記憶體中的一樣。
  • 第二步:查詢出status=0的所有資料,也就是沒有更新過的資料。
  • 第三步:新建一個map用於計算修改後的結果。
  • 第四步:將新建的map替換掉成員變數mapCache。

這樣基本上就大功告成了,但是你發現一個問題沒有,那就是在第一步的時候會耗費大量的時間,為什麼這麼說呢?如果資料量過大,HashMap的儲存是需要進行hash的計算以及還有可能發生的hash碰撞,所以物件的建立會耗費大量的時間,我們的記憶體中又存在著一份和我們資料庫中差不多的資料,為什麼我們不使用它來進行操作呢?這個時候你可能疑惑了,不是說不能對他進行修改嗎,要等所有資料修改完成之後一次性替換嗎?這個時候原型模式就要上場了。

原型模式兩大資料拷貝

不知道大家有沒有聽過這句話,new出來的在堆裡,引用的在棧中,那我們一起來看看Java的兩種拷貝模式,淺拷貝和深拷貝。

淺拷貝

淺拷貝就是拷貝指向物件的指標(java中的引用地址),意思就是說:拷貝出來的目標物件的指標和源物件的指標指向的記憶體空間是同一塊空間,淺拷貝只是一種簡單的拷貝,讓幾個物件公用一個記憶體,然而當記憶體銷燬的時候,指向這個記憶體空間的所有指標需要重新定義,不然會造成野指標錯誤。

我們來畫個圖說明一下,算了,不畫了,實在找不到比較好的畫圖工具,畫的太難看了,還不如直接講,如果有好的畫圖工具,可以評論告訴我哦,十分感謝。

通俗一點來說,淺拷貝就是拷貝資料的引用地址,並沒有拷貝真正的資料,也就是說,拷貝出來的物件和原始物件共享一套資料,也就是說拷貝的資料發生改變,原資料也會發生改變,原資料發生改變,拷貝的資料也會發生改變,因為他們共享一套資料。

下面我們使用淺拷貝來實現一下我們上面的demo,由於Map是沒有clone()方法的,所以我需要將之前的Map改成HashMap,請看demo

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {



    /** * 記憶體中存放的資料 * key:部落格id * value:部落格資訊 */
    private static HashMap<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /** * 資料庫存放的資料 * key:部落格id * value:部落格資訊 */
    private static HashMap<Integer, BlogStats>  mapMysql = new HashMap<Integer, BlogStats>();

    /** * 最近一次的修改時間 */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化記憶體中java的部落格資訊
        for(int i = 0; i< 10 ;++i){
           blogStats = BlogStats.builder()
                   .id(i)
                   .title("java是世界上最好的語言"+i)
                   .likeNum(i)
                   .viewNum(i)
                   .commentNum(i)
                   .status(1)
                   .build();
           mapCache.put(i,blogStats);
        }

        //初始化資料庫中java的部落格資訊
        for(int i = 0; i<= 10 ;++i){
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的語言"+i)
                    .likeNum(i+2)
                    .viewNum(i+2)
                    .commentNum(i+2)
                    .status(i >= 8 ? 0 : 1)
                    .build();
            mapMysql.put(i,blogStats);
        }
    }

    /** * 每天定時更新 */
    private static void update(Map<Integer, BlogStats> changeData){
        //這是淺拷貝
        HashMap<Integer,BlogStats> oldData = (HashMap<Integer, BlogStats>) mapCache.clone();

        for(Map.Entry<Integer, BlogStats> entry : changeData.entrySet()){
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            blogStats.setStatus(1);
            if(oldData.containsKey(id)){
                BlogStats bs = oldData.get(id);
                bs.setLikeNum(blogStats.getLikeNum());
                bs.setViewNum(blogStats.getViewNum());
                bs.setCommentNum(blogStats.getCommentNum());
            }else{
                oldData.put(id,blogStats);
            }
        }
        System.out.println("克隆的HashMap最終結果如下=====================");
        print(oldData);
        System.out.println("克隆物件修改完成之後,原資料如下================");
        print(mapCache);
        mapCache = oldData;
    }


    /** * 根據更新的狀態獲取到最新需要修改的資料 * 由於這裡僅僅只是模擬,所以這裡我就隨便造幾條資料,你們主要看思想即可 * @return */
    public static Map<Integer,BlogStats> getChangeData(){

        //在資料庫中查詢還沒有更新過的資料
        //由於我是模擬,所以我直接偽造了幾條資料充當需要修改的資料,還請見諒
        Map<Integer,BlogStats> changeDatas= new HashMap<Integer,BlogStats>();
        for(Map.Entry<Integer, BlogStats> entry : mapMysql.entrySet()){
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            if(blogStats.getStatus() == 0){
                changeDatas.put(id,blogStats);
            }
        }
        return changeDatas;
    }


    public static void main(String[] args) {
        System.out.println("記憶體中最開始存在的資料");
        print(mapCache);
        //獲取修改的資料
        Map<Integer, BlogStats> changeData = getChangeData();
        //更新記憶體資料
        update(changeData);

        //將修改的部落格狀態改為1並寫回資料庫,這段省略
        System.out.println("更新過後記憶體中的資料=====================");
        print(mapCache);
    }


    private static void print( Map<Integer, BlogStats> map ){
        for(Map.Entry<Integer, BlogStats> entry : map.entrySet()){
            System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());
        }

    }
}

這裡面做的修改就是使用了一個區域性變數oldData接收mapCache克隆結果,然後對克隆的結果做修改,程式碼中使用的mapCache.clone()屬於淺拷貝,之前說過淺拷貝的資料是共享的,那我們對拷貝過後的資料進行修改,那原資料會不會受到影響呢?我們一起看列印結果

記憶體中最開始存在的資料
key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=8, viewNum=8, commentNum=8, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=9, viewNum=9, commentNum=9, status=1)
克隆的HashMap最終結果如下=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的語言10, likeNum=12, viewNum=12, commentNum=12, status=1)
克隆物件修改完成之後,原資料如下================
key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=11, viewNum=11, commentNum=11, status=1)
更新過後記憶體中的資料=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的語言10, likeNum=12, viewNum=12, commentNum=12, status=1)

Process finished with exit code 0

我們來看:克隆的HashMap最終結果如下=====================

90c1a20386b40d82cccea1c4a7fbb0d4.png


修改了兩行,新增了一行,從上面的列印結果可以看出原資料也發生了相應的變化

62b20a1e1b8b5c4f489e09717bb62333.png


雖然克隆物件中新增的物件沒有在原物件中增加,但是修改的屬性在原物件中也發生了改變,使用淺拷貝的同學一定要注意這一點。

所以淺拷貝就不能實現我們的需求了,前面還有一個深拷貝還沒有說,那它能不能滿足我們的要求 呢?

深拷貝

一個引用物件一般來說由兩個部分組成:一個具名的Handle,也就是我們所說的宣告(如變數)和一個內部(不具名)的物件,也就是具名Handle的內部物件。它在Manged Heap(託管堆)中分配,一般由新增引用物件的New方法是進行建立。深拷貝是指源物件與拷貝物件互相獨立,其中任何一個物件的改動都不會對另外一個物件造成影響。舉個例子,一個人名叫張三,後來用他克隆(假設法律允許)了另外一個人,叫李四,不管是張三缺胳膊少腿還是李四缺胳膊少腿都不會影響另外一個人。比較典型的就是Value(值)物件,如預定義型別Int32,Double,以及結構(struct),列舉(Enum)等。

根據介紹來說,深度拷貝是可以滿足我們需求的,那我們因該如何實現深拷貝呢?

序列化
序列化很好理解,就是將物件序列化,然後再反序列化,這兩步之後就會得到一個全新的物件。

核心程式碼

/** * 深拷貝 * * @param obj * @return */
    private static Object deepColne(Object obj) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return objectInputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

這個就是通過序列化再反序列化的核心程式碼,然後再看一下完整demo

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {


    /** * 記憶體中存放的資料 * key:部落格id * value:部落格資訊 */
    private static HashMap<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /** * 資料庫存放的資料 * key:部落格id * value:部落格資訊 */
    private static HashMap<Integer, BlogStats> mapMysql = new HashMap<Integer, BlogStats>();

    /** * 最近一次的修改時間 */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化記憶體中java的部落格資訊
        for (int i = 0; i < 10; ++i) {
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的語言" + i)
                    .likeNum(i)
                    .viewNum(i)
                    .commentNum(i)
                    .status(1)
                    .build();
            mapCache.put(i, blogStats);
        }

        //初始化資料庫中java的部落格資訊
        for (int i = 0; i <= 10; ++i) {
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的語言" + i)
                    .likeNum(i + 2)
                    .viewNum(i + 2)
                    .commentNum(i + 2)
                    .status(i >= 8 ? 0 : 1)
                    .build();
            mapMysql.put(i, blogStats);
        }
    }

    /** * 每天定時更新 */
    private static void update(Map<Integer, BlogStats> changeData) {
        //這是淺拷貝
        HashMap<Integer, BlogStats> oldData = (HashMap<Integer, BlogStats>) deepColne(mapCache);

        for (Map.Entry<Integer, BlogStats> entry : changeData.entrySet()) {
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            blogStats.setStatus(1);
            if (oldData.containsKey(id)) {
                BlogStats bs = oldData.get(id);
                bs.setLikeNum(blogStats.getLikeNum());
                bs.setViewNum(blogStats.getViewNum());
                bs.setCommentNum(blogStats.getCommentNum());
            } else {
                oldData.put(id, blogStats);
            }
        }
        System.out.println("克隆的HashMap最終結果如下=====================");
        print(oldData);
        System.out.println("克隆物件修改完成之後,原資料如下================");
        print(mapCache);
        mapCache = oldData;
    }


    /** * 根據更新的狀態獲取到最新需要修改的資料 * 由於這裡僅僅只是模擬,所以這裡我就隨便造幾條資料,你們主要看思想即可 * * @return */
    public static Map<Integer, BlogStats> getChangeData() {

        //在資料庫中查詢還沒有更新過的資料
        //由於我是模擬,所以我直接偽造了幾條資料充當需要修改的資料,還請見諒
        Map<Integer, BlogStats> changeDatas = new HashMap<Integer, BlogStats>();
        for (Map.Entry<Integer, BlogStats> entry : mapMysql.entrySet()) {
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            if (blogStats.getStatus() == 0) {
                changeDatas.put(id, blogStats);
            }
        }
        return changeDatas;
    }


    public static void main(String[] args) {
        System.out.println("記憶體中最開始存在的資料");
        print(mapCache);
        //獲取修改的資料
        Map<Integer, BlogStats> changeData = getChangeData();
        //更新記憶體資料
        update(changeData);

        //將修改的部落格狀態改為1並寫回資料庫,這段省略
        System.out.println("更新過後記憶體中的資料=====================");
        print(mapCache);
    }


    /** * 深拷貝 * * @param obj * @return */
    private static Object deepColne(Object obj) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return objectInputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


    private static void print(Map<Integer, BlogStats> map) {
        for (Map.Entry<Integer, BlogStats> entry : map.entrySet()) {
            System.out.println("key:" + entry.getKey() + " value:" + entry.getValue());
        }

    }
}

與淺拷貝相比,改動的地方並不多,只有克隆的地方發生了修改,其他地方不變

記憶體中最開始存在的資料
key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=8, viewNum=8, commentNum=8, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=9, viewNum=9, commentNum=9, status=1)
克隆的HashMap最終結果如下=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的語言10, likeNum=12, viewNum=12, commentNum=12, status=1)
克隆物件修改完成之後,原資料如下================
key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=8, viewNum=8, commentNum=8, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=9, viewNum=9, commentNum=9, status=1)
更新過後記憶體中的資料=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的語言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的語言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的語言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的語言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的語言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的語言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的語言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的語言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的語言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的語言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的語言10, likeNum=12, viewNum=12, commentNum=12, status=1)

Process finished with exit code 0

這個時候我們發現,即使拷貝的資料發生了變化,原始資料還是原來的資料,這就是深度拷貝,深度拷貝有一個比較致命的缺點,那就是拷貝的時間太長,因為它不僅需要拷貝引用,同時還需要拷貝資料,相對於淺拷貝而言效率太差,當然深拷貝的實現方式不止這一種,還有很多,我在這裡就不做過多的展示了。

這裡有一點需要特別注意,深度拷貝的物件需要實現序列化,也就是:

cd54e71a2ff061fbda24b4ba422a8ff0.png


否則會導致程式報錯。

既然淺拷貝會導致資料共享,深拷貝印象物件建立的效率,那我們有沒有一種這種的方案來解決上面的這個問題呢?可以先想一想,不要著急看下面,因為下面肯定有答案,沒有的話我也肯定不會讓你們想的。

淺拷貝+深拷貝

對於上面的需求來說,我們需要修改發生改變的部落格資訊,但是需要修改的資料並不是很多,可能只有幾條,也有可能只有幾十條,再幾十萬資料中修改幾條使用深拷貝就有點過糞了,所以這裡我們採用一種比較折中的方式:淺拷貝+深拷貝

如何實現呢?我們首先將所有的資料心境淺拷貝,然後遇到需要修改的資料再進行深拷貝即可。

改造程式碼

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {


    /** * 記憶體中存放的資料 * key:部落格id * value:部落格資訊 */
    private static HashMap<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /** * 資料庫存放的資料 * key:部落格id * value:部落格資訊 */
    private static HashMap<Integer, BlogStats> mapMysql = new HashMap<Integer, BlogStats>();

    /** * 最近一次的修改時間 */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化記憶體中java的部落格資訊
        for (int i = 0; i < 10; ++i) {
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的語言" + i)
                    .likeNum(i)
                    .viewNum(i)
                    .commentNum(i)
                    .status(1)
                    .build();
            mapCache.put(i, blogStats);
        }

        //初始化資料庫中java的部落格資訊
        for (int i = 0; i <= 10; ++i) {
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的語言" + i)
                    .likeNum(i + 2)
                    .viewNum(i + 2)
                    .commentNum(i + 2)
                    .status(i >= 8 ? 0 : 1)
                    .build();
            mapMysql.put(i, blogStats);
        }
    }

    /** * 每天定時更新 */
    private static void update(Map<Integer, BlogStats> changeData) {
        //這是淺拷貝
        HashMap<Integer, BlogStats> oldData = (HashMap<Integer, BlogStats>) mapCache.clone();

        for (Map.Entry<Integer, BlogStats> entry : changeData.entrySet()) {
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            blogStats.setStatus(1);
            if (oldData.containsKey(id)) {
                //我們先刪除資料,因為淺拷貝對刪除/新增是不會改變原資料的屬性的
               oldData.remove(id);
            }
            //這裡再重新賦值
            oldData.put(id,blogStats);
        }
        System.out.println("克隆的HashMap最終結果如下=====================");
        print(oldData);
        System.out.println("克隆物件修改完成之後,原資料如下================");
        print(mapCache);
        mapCache = oldData;
    }


    /** * 根據更新的狀態獲取到最新需要修改的資料 * 由於這裡僅僅只是模擬,所以這裡我就隨便造幾條資料,你們主要看思想即可 * * @return */
    public static Map<Integer, BlogStats> getChangeData() {

        //在資料庫中查詢還沒有更新過的資料
        //由於我是模擬,所以我直接偽造了幾條資料充當需要修改的資料,還請見諒
        Map<Integer, BlogStats> changeDatas = new HashMap<Integer, BlogStats>();
        for (Map.Entry<Integer, BlogStats> entry : mapMysql.entrySet()) {
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            if (blogStats.getStatus() == 0) {
                changeDatas.put(id, blogStats);
            }
        }
        return changeDatas;
    }


    public static void main(String[] args) {
        System.out.println("記憶體中最開始存在的資料");
        print(mapCache);
        //獲取修改的資料
        Map<Integer, BlogStats> changeData = getChangeData();
        //更新記憶體資料
        update(changeData);

        //將修改的部落格狀態改為1並寫回資料庫,這段省略
        System.out.println("更新過後記憶體中的資料=====================");
        print(mapCache);
    }


    /** * 深拷貝 * * @param obj * @return */
    private static Object deepColne(Object obj) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return objectInputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


    private static void print(Map<Integer, BlogStats> map) {
        for (Map.Entry<Integer, BlogStats> entry : map.entrySet()) {
            System.out.println("key:" + entry.getKey() + " value:" + entry.getValue());
        }

    }
}

這樣既提高了建立物件的速度,還保證了修改的時候不會對原資料造成影響,是一個非常不錯的解決方案。

總結

什麼時候使用原型模式?1.物件建立過於複雜:物件建立的時候需要經過計算、排序等操作。2.物件時間過長:需要查詢資料庫或者通過rpc呼叫。3.物件過多:比如一個集合中有一百萬個物件,我現在需要修改這一百萬的物件的資料,但是原資料不能動,所以這個時候使用原型模式比較適合。

java的原型模式我們使用的比較少,那是因為java給我們提供了豐富的克隆方法,其實原型模式的核心就是拷貝,掌握了深拷貝與淺拷貝,你差不多也就掌握了原型模式的精髓了,文章中案例可能舉得不是很好,還請大家見諒。

淺拷貝:只拷貝物件的引用,不拷貝物件的資料,拷貝較快。

深拷貝:既拷貝物件的引用也拷貝物件的資料,耗時較久。

這兩種拷貝方式可以根據自己的實際情況選擇。