原型模式:如何快速的克隆出一個對象?

概念

原型模式是一種創建型設計模式,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最終結果如下=====================
在這裏插入圖片描述
修改了兩行,新增了一行,從上面的打印結果可以看出原數據也發生了相應的變化
在這裏插入圖片描述
雖然克隆對象中新增的對象沒有在原對象中增加,但是修改的屬性在原對象中也發生了改變,使用淺拷貝的同學一定要注意這一點。

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

深拷貝

一個引用對象一般來說由兩個部分組成:一個具名的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

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

這裏有一點需要特別注意,深度拷貝的對象需要實現序列化,也就是:
在這裏插入圖片描述
否則會導致程序報錯。

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

淺拷貝+深拷貝

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

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

改造代碼

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給我們提供了豐富的克隆方法,其實原型模式的核心就是拷貝,掌握了深拷貝與淺拷貝,你差不多也就掌握了原型模式的精髓了,文章中案例可能舉得不是很好,還請大家見諒。

淺拷貝:只拷貝對象的引用,不拷貝對象的數據,拷貝較快。

深拷貝:既拷貝對象的引用也拷貝對象的數據,耗時較久。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章