java設(shè)計(jì)模式原型模式與享元模式調(diào)優(yōu)系統(tǒng)性能詳解
原型模式與享元模式
原型模式和享元模式,前者是在創(chuàng)建多個(gè)實(shí)例時(shí),對(duì)創(chuàng)建過(guò)程的性能進(jìn)行調(diào)優(yōu);后者是用減少創(chuàng)建實(shí)例的方式,來(lái)調(diào)優(yōu)系統(tǒng)性能。這么看,你會(huì)不會(huì)覺(jué)得兩個(gè)模式有點(diǎn)相互矛盾呢?
其實(shí)不然,它們的使用是分場(chǎng)景的。在有些場(chǎng)景下,我們需要重復(fù)創(chuàng)建多個(gè)實(shí)例,例如在循環(huán)體中賦值一個(gè)對(duì)象,此時(shí)我們就可以采用原型模式來(lái)優(yōu)化對(duì)象的創(chuàng)建過(guò)程;而在有些場(chǎng)景下,我們則可以避免重復(fù)創(chuàng)建多個(gè)實(shí)例,在內(nèi)存中共享對(duì)象就好了。
今天我們就來(lái)看看這兩種模式的適用場(chǎng)景,看看如何使用它們來(lái)提升系統(tǒng)性能。
原型模式
原型模式是通過(guò)給出一個(gè)原型對(duì)象來(lái)指明所創(chuàng)建的對(duì)象的類型,然后使用自身實(shí)現(xiàn)的克隆接口來(lái)復(fù)制這個(gè)原型對(duì)象,該模式就是用這種方式來(lái)創(chuàng)建出更多同類型的對(duì)象。
使用這種方式創(chuàng)建新的對(duì)象的話,就無(wú)需再通過(guò)new實(shí)例化來(lái)創(chuàng)建對(duì)象了。這是因?yàn)镺bject類的clone方法是一個(gè)本地方法,它可以直接操作內(nèi)存中的二進(jìn)制流,所以性能相對(duì)new實(shí)例化來(lái)說(shuō),更佳。
實(shí)現(xiàn)原型模式
我們現(xiàn)在通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)實(shí)現(xiàn)一個(gè)原型模式:
//實(shí)現(xiàn)Cloneable 接口的原型抽象類Prototype
class Prototype implements Cloneable {
//重寫(xiě)clone方法
public Prototype clone(){
Prototype prototype = null;
try{
prototype = (Prototype)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return prototype;
}
}
//實(shí)現(xiàn)原型類
class ConcretePrototype extends Prototype{
public void show(){
System.out.println("原型模式實(shí)現(xiàn)類");
}
}
public class Client {
public static void main(String[] args){
ConcretePrototype cp = new ConcretePrototype();
for(int i=0; i< 10; i++){
ConcretePrototype clonecp = (ConcretePrototype)cp.clone();
clonecp.show();
}
}
}要實(shí)現(xiàn)一個(gè)原型類,需要具備三個(gè)條件:
- 實(shí)現(xiàn)Cloneable接口:Cloneable接口與序列化接口的作用類似,它只是告訴虛擬機(jī)可以安全地在實(shí)現(xiàn)了這個(gè)接口的類上使用clone方法。在JVM中,只有實(shí)現(xiàn)了Cloneable接口的類才可以被拷貝,否則會(huì)拋出CloneNotSupportedException異常。
- 重寫(xiě)Object類中的clone方法:在Java中,所有類的父類都是Object類,而Object類中有一個(gè)clone方法,作用是返回對(duì)象的一個(gè)拷貝。
- 在重寫(xiě)的clone方法中調(diào)用super.clone():默認(rèn)情況下,類不具備復(fù)制對(duì)象的能力,需要調(diào)用super.clone()來(lái)實(shí)現(xiàn)。
從上面我們可以看出,原型模式的主要特征就是使用clone方法復(fù)制一個(gè)對(duì)象。通常,有些人會(huì)誤以為 Object a=new Object();Object b=a; 這種形式就是一種對(duì)象復(fù)制的過(guò)程,然而這種復(fù)制只是對(duì)象引用的復(fù)制,也就是a和b對(duì)象指向了同一個(gè)內(nèi)存地址,如果b修改了,a的值也就跟著被修改了。
我們可以通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)看看普通的對(duì)象復(fù)制問(wèn)題:
class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name= name;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setName("test1");
Student stu2 = stu1;
stu2.setName("test2");
System.out.println("學(xué)生1:" + stu1.getName());
System.out.println("學(xué)生2:" + stu2.getName());
}
}如果是復(fù)制對(duì)象,此時(shí)打印的日志應(yīng)該為:
學(xué)生1:test1
學(xué)生2:test2
然而,實(shí)際上是:
學(xué)生1:test2
學(xué)生2:test2
通過(guò)clone方法復(fù)制的對(duì)象才是真正的對(duì)象復(fù)制,clone方法賦值的對(duì)象完全是一個(gè)獨(dú)立的對(duì)象。剛剛講過(guò)了,Object類的clone方法是一個(gè)本地方法,它直接操作內(nèi)存中的二進(jìn)制流,特別是復(fù)制大對(duì)象時(shí),性能的差別非常明顯。我們可以用 clone 方法再實(shí)現(xiàn)一遍以上例子。
//學(xué)生類實(shí)現(xiàn)Cloneable接口
class Student implements Cloneable{
private String name; //姓名
public String getName() {
return name;
}
public void setName(String name) {
this.name= name;
}
//重寫(xiě)clone方法
public Student clone() {
Student student = null;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student(); //創(chuàng)建學(xué)生1
stu1.setName("test1");
Student stu2 = stu1.clone(); //通過(guò)克隆創(chuàng)建學(xué)生2
stu2.setName("test2");
System.out.println("學(xué)生1:" + stu1.getName());
System.out.println("學(xué)生2:" + stu2.getName());
}
}運(yùn)行結(jié)果:
學(xué)生1:test1
學(xué)生2:test2
深拷貝和淺拷貝
在調(diào)用super.clone()方法之后,首先會(huì)檢查當(dāng)前對(duì)象所屬的類是否支持clone,也就是看該類是否實(shí)現(xiàn)了Cloneable接口。
如果支持,則創(chuàng)建當(dāng)前對(duì)象所屬類的一個(gè)新對(duì)象,并對(duì)該對(duì)象進(jìn)行初始化,使得新對(duì)象的成員變量的值與當(dāng)前對(duì)象的成員變量的值一模一樣,但對(duì)于其它對(duì)象的引用以及List等類型的成員屬性,則只能復(fù)制這些對(duì)象的引用了。所以簡(jiǎn)單調(diào)用super.clone()這種克隆對(duì)象方式,就是一種淺拷貝。
所以,當(dāng)我們?cè)谑褂胏lone()方法實(shí)現(xiàn)對(duì)象的克隆時(shí),就需要注意淺拷貝帶來(lái)的問(wèn)題。我們?cè)偻ㄟ^(guò)一個(gè)例子來(lái)看看淺拷貝。
//定義學(xué)生類
class Student implements Cloneable{
private String name; //學(xué)生姓名
private Teacher teacher; //定義老師類
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
//重寫(xiě)克隆方法
public Student clone() {
Student student = null;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
}
//定義老師類
class Teacher implements Cloneable{
private String name; //老師姓名
public String getName() {
return name;
}
public void setName(String name) {
this.name= name;
}
//重寫(xiě)克隆方法,對(duì)老師類進(jìn)行克隆
public Teacher clone() {
Teacher teacher= null;
try {
teacher= (Teacher) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
}
public class Test {
public static void main(String args[]) {
Teacher teacher = new Teacher (); //定義老師1
teacher.setName("劉老師");
Student stu1 = new Student(); //定義學(xué)生1
stu1.setName("test1");
stu1.setTeacher(teacher);
Student stu2 = stu1.clone(); //定義學(xué)生2
stu2.setName("test2");
stu2.getTeacher().setName("王老師");//修改老師
System.out.println("學(xué)生" + stu1.getName + "的老師是:" + stu1.getTeacher().getName);
System.out.println("學(xué)生" + stu1.getName + "的老師是:" + stu2.getTeacher().getName);
}
}運(yùn)行結(jié)果:
學(xué)生test1的老師是:王老師
學(xué)生test2的老師是:王老師
觀察以上運(yùn)行結(jié)果,我們可以發(fā)現(xiàn):在我們給學(xué)生2修改老師的時(shí)候,學(xué)生1的老師也跟著被修改了。這就是淺拷貝帶來(lái)的問(wèn)題。
我們可以通過(guò)深拷貝來(lái)解決這種問(wèn)題,其實(shí)深拷貝就是基于淺拷貝來(lái)遞歸實(shí)現(xiàn)具體的每個(gè)對(duì)象,代碼如下:
public Student clone() {
Student student = null;
try {
student = (Student) super.clone();
Teacher teacher = this.teacher.clone();//克隆teacher對(duì)象
student.setTeacher(teacher);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}適用場(chǎng)景
前面我詳述了原型模式的實(shí)現(xiàn)原理,那到底什么時(shí)候我們要用它呢?
在一些重復(fù)創(chuàng)建對(duì)象的場(chǎng)景下,我們就可以使用原型模式來(lái)提高對(duì)象的創(chuàng)建性能。例如,我在開(kāi)頭提到的,循環(huán)體內(nèi)創(chuàng)建對(duì)象時(shí),我們就可以考慮用clone的方式來(lái)實(shí)現(xiàn)。
例如:
for(int i=0; i<list.size(); i++){
Student stu = new Student();
...
}我們可以優(yōu)化為:
Student stu = new Student();
for(int i=0; i<list.size(); i++){
Student stu1 = (Student)stu.clone();
...
}除此之外,原型模式在開(kāi)源框架中的應(yīng)用也非常廣泛。例如Spring中,@Service默認(rèn)都是單例的。用了私有全局變量,若不想影響下次注入或每次上下文獲取bean,就需要用到原型模式,我們可以通過(guò)以下注解來(lái)實(shí)現(xiàn),@Scope(“prototype”)。
享元模式
享元模式是運(yùn)用共享技術(shù)有效地最大限度地復(fù)用細(xì)粒度對(duì)象的一種模式。該模式中,以對(duì)象的信息狀態(tài)劃分,可以分為內(nèi)部數(shù)據(jù)和外部數(shù)據(jù)。內(nèi)部數(shù)據(jù)是對(duì)象可以共享出來(lái)的信息,這些信息不會(huì)隨著系統(tǒng)的運(yùn)行而改變;外部數(shù)據(jù)則是在不同運(yùn)行時(shí)被標(biāo)記了不同的值。
享元模式一般可以分為三個(gè)角色,分別為 Flyweight(抽象享元類)、ConcreteFlyweight(具體享元類)和 FlyweightFactory(享元工廠類)。抽象享元類通常是一個(gè)接口或抽象類,向外界提供享元對(duì)象的內(nèi)部數(shù)據(jù)或外部數(shù)據(jù);具體享元類是指具體實(shí)現(xiàn)內(nèi)部數(shù)據(jù)共享的類;享元工廠類則是主要用于創(chuàng)建和管理享元對(duì)象的工廠類。
實(shí)現(xiàn)享元模式
我們還是通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)實(shí)現(xiàn)一個(gè)享元模式:
//抽象享元類
interface Flyweight {
//對(duì)外狀態(tài)對(duì)象
void operation(String name);
//對(duì)內(nèi)對(duì)象
String getType();
}//具體享元類
class ConcreteFlyweight implements Flyweight {
private String type;
public ConcreteFlyweight(String type) {
this.type = type;
}
@Override
public void operation(String name) {
System.out.printf("[類型(內(nèi)在狀態(tài))] - [%s] - [名字(外在狀態(tài))] - [%s]\n", type, name);
}
@Override
public String getType() {
return type;
}
}//享元工廠類
class FlyweightFactory {
private static final Map<String, Flyweight> FLYWEIGHT_MAP = new HashMap<>();//享元池,用來(lái)存儲(chǔ)享元對(duì)象
public static Flyweight getFlyweight(String type) {
if (FLYWEIGHT_MAP.containsKey(type)) {//如果在享元池中存在對(duì)象,則直接獲取
return FLYWEIGHT_MAP.get(type);
} else {//在響應(yīng)池不存在,則新創(chuàng)建對(duì)象,并放入到享元池
ConcreteFlyweight flyweight = new ConcreteFlyweight(type);
FLYWEIGHT_MAP.put(type, flyweight);
return flyweight;
}
}
}public class Client {
public static void main(String[] args) {
Flyweight fw0 = FlyweightFactory.getFlyweight("a");
Flyweight fw1 = FlyweightFactory.getFlyweight("b");
Flyweight fw2 = FlyweightFactory.getFlyweight("a");
Flyweight fw3 = FlyweightFactory.getFlyweight("b");
fw1.operation("abc");
System.out.printf("[結(jié)果(對(duì)象對(duì)比)] - [%s]\n", fw0 == fw2);
System.out.printf("[結(jié)果(內(nèi)在狀態(tài))] - [%s]\n", fw1.getType());
}
}輸出結(jié)果:
[類型(內(nèi)在狀態(tài))] - [b] - [名字(外在狀態(tài))] - [abc]
[結(jié)果(對(duì)象對(duì)比)] - [true]
[結(jié)果(內(nèi)在狀態(tài))] - [b]
觀察以上代碼運(yùn)行結(jié)果,我們可以發(fā)現(xiàn):如果對(duì)象已經(jīng)存在于享元池中,則不會(huì)再創(chuàng)建該對(duì)象了,而是共用享元池中內(nèi)部數(shù)據(jù)一致的對(duì)象。這樣就減少了對(duì)象的創(chuàng)建,同時(shí)也節(jié)省了同樣內(nèi)部數(shù)據(jù)的對(duì)象所占用的內(nèi)存空間。
適用場(chǎng)景
享元模式在實(shí)際開(kāi)發(fā)中的應(yīng)用也非常廣泛。例如Java的String字符串,在一些字符串常量中,會(huì)共享常量池中字符串對(duì)象,從而減少重復(fù)創(chuàng)建相同值對(duì)象,占用內(nèi)存空間。代碼如下:
String s1 = "hello"; String s2 = "hello"; System.out.println(s1==s2);//true
還有,在日常開(kāi)發(fā)中的應(yīng)用。例如,池化技術(shù)中的線程池就是享元模式的一種實(shí)現(xiàn);將商品存儲(chǔ)在應(yīng)用服務(wù)的緩存中,那么每當(dāng)用戶獲取商品信息時(shí),則不需要每次都從redis緩存或者數(shù)據(jù)庫(kù)中獲取商品信息,并在內(nèi)存中重復(fù)創(chuàng)建商品信息了。
總結(jié)
原型模式和享元模式,在開(kāi)源框架,和實(shí)際開(kāi)發(fā)中,應(yīng)用都十分廣泛。
在不得已需要重復(fù)創(chuàng)建大量同一對(duì)象時(shí),我們可以使用原型模式,通過(guò)clone方法復(fù)制對(duì)象,這種方式比用new和序列化創(chuàng)建對(duì)象的效率要高;在創(chuàng)建對(duì)象時(shí),如果我們可以共用對(duì)象的內(nèi)部數(shù)據(jù),那么通過(guò)享元模式共享相同的內(nèi)部數(shù)據(jù)的對(duì)象,就可以減少對(duì)象的創(chuàng)建,實(shí)現(xiàn)系統(tǒng)調(diào)優(yōu)。
以上就是java設(shè)計(jì)模式原型模式與享元模式調(diào)優(yōu)系統(tǒng)性能詳解的詳細(xì)內(nèi)容,更多關(guān)于java原型模式享元模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java每7天日志自動(dòng)清理的項(xiàng)目實(shí)踐
在實(shí)際項(xiàng)目中由于服務(wù)器內(nèi)存有限,人工清理常會(huì)忘記,本文主要介紹了Java每7天日志自動(dòng)清理的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
Spring Boot集成tablesaw插件快速入門(mén)示例代碼
Tablesaw是一款Java的數(shù)據(jù)可視化庫(kù),數(shù)據(jù)解析庫(kù),主要用于加載數(shù)據(jù),對(duì)數(shù)據(jù)進(jìn)行操作(轉(zhuǎn)化,過(guò)濾,匯總等),類比Python中的Pandas庫(kù),本文介紹Spring Boot集成tablesaw插件快速入門(mén)Demo,感興趣的朋友一起看看吧2024-06-06
Java如何優(yōu)雅的實(shí)現(xiàn)微信登錄注冊(cè)
這篇文章主要給大家介紹了關(guān)于Java如何優(yōu)雅的實(shí)現(xiàn)微信登錄注冊(cè)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02
Springboot使用Selenium+ChormeDriver在服務(wù)器端將網(wǎng)頁(yè)保存為圖片或PDF
這篇文章主要為大家詳細(xì)介紹了Springboot使用Selenium+ChormeDriver在服務(wù)器端將網(wǎng)頁(yè)保存為圖片或PDF的相關(guān)方法,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-10-10
Jmeter解析返回參數(shù)以及解析并操作json方式
本文總結(jié)了Java或JMeter的BeanShell腳本中處理JSON字符串的常用操作,包括解析JSON數(shù)組、JSON對(duì)象和嵌套的JSON字符串,并提供了示例代碼和打印結(jié)果2025-02-02
Java 實(shí)現(xiàn)RSA非對(duì)稱加密算法
RSA解決了對(duì)稱加密的一個(gè)不足,比如AES算法加密和解密時(shí)使用的是同一個(gè)秘鑰,因此這個(gè)秘鑰不能公開(kāi),因此對(duì)于需要公開(kāi)秘鑰的場(chǎng)合,我們需要在加密和解密過(guò)程中使用不同的秘鑰,加密使用的公鑰可以公開(kāi),解密使用的私鑰要保密,這就是非對(duì)稱加密的好處?!?/div> 2021-06-06
Java 數(shù)據(jù)結(jié)構(gòu)算法Collection接口迭代器示例詳解
這篇文章主要為大家介紹了Java 數(shù)據(jù)結(jié)構(gòu)算法Collection接口迭代器示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Maven是什么?Maven的概念+作用+倉(cāng)庫(kù)的介紹+常用命令的詳解
Maven是一個(gè)項(xiàng)目管理工具,它包含了一個(gè)對(duì)象模型。一組標(biāo)準(zhǔn)集合,一個(gè)依賴管理系統(tǒng)。和用來(lái)運(yùn)行定義在生命周期階段中插件目標(biāo)和邏輯.,本文給大家介紹Maven的概念+作用+倉(cāng)庫(kù)的介紹+常用命令,感興趣的的朋友跟隨小編一起看看吧2020-09-09最新評(píng)論

